summaryrefslogtreecommitdiff
path: root/drivers/gpu
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu')
-rw-r--r--drivers/gpu/Makefile2
-rw-r--r--drivers/gpu/drm/arm/malidp_crtc.c2
-rw-r--r--drivers/gpu/drm/bridge/Kconfig39
-rw-r--r--drivers/gpu/drm/bridge/Makefile5
-rw-r--r--drivers/gpu/drm/bridge/adv7511/adv7511.h8
-rw-r--r--drivers/gpu/drm/bridge/adv7511/adv7511_cec.c3
-rw-r--r--drivers/gpu/drm/bridge/adv7511/adv7511_drv.c162
-rw-r--r--drivers/gpu/drm/bridge/adv7511/adv7533.c88
-rw-r--r--drivers/gpu/drm/bridge/cadence/Kconfig33
-rw-r--r--drivers/gpu/drm/bridge/cadence/Makefile10
-rw-r--r--drivers/gpu/drm/bridge/cadence/cdns-dp-core.c881
-rw-r--r--drivers/gpu/drm/bridge/cadence/cdns-hdcp-common.c1323
-rw-r--r--drivers/gpu/drm/bridge/cadence/cdns-hdcp-common.h23
-rw-r--r--drivers/gpu/drm/bridge/cadence/cdns-hdmi-core.c827
-rw-r--r--drivers/gpu/drm/bridge/cadence/cdns-mhdp-audio.c423
-rw-r--r--drivers/gpu/drm/bridge/cadence/cdns-mhdp-cec.c365
-rw-r--r--drivers/gpu/drm/bridge/cadence/cdns-mhdp-common.c853
-rw-r--r--drivers/gpu/drm/bridge/cadence/cdns-mhdp-dp.c278
-rw-r--r--drivers/gpu/drm/bridge/cadence/cdns-mhdp-hdcp.c350
-rw-r--r--drivers/gpu/drm/bridge/cadence/cdns-mhdp-hdcp.h29
-rw-r--r--drivers/gpu/drm/bridge/cadence/cdns-mhdp-hdmi.c332
-rw-r--r--drivers/gpu/drm/bridge/cadence/cdns-mhdp.h232
-rw-r--r--drivers/gpu/drm/bridge/fsl-imx-ldb.c281
-rw-r--r--drivers/gpu/drm/bridge/it6161.c2407
-rw-r--r--drivers/gpu/drm/bridge/it6161.h322
-rw-r--r--drivers/gpu/drm/bridge/it6263.c1040
-rw-r--r--drivers/gpu/drm/bridge/nwl-dsi.c1119
-rw-r--r--drivers/gpu/drm/bridge/nwl-dsi.h4
-rw-r--r--drivers/gpu/drm/bridge/nxp-seiko-43wvfig.c265
-rw-r--r--drivers/gpu/drm/bridge/sec-dsim.c2087
-rw-r--r--drivers/gpu/drm/bridge/synopsys/Kconfig11
-rw-r--r--drivers/gpu/drm/bridge/synopsys/Makefile1
-rw-r--r--drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c35
-rw-r--r--drivers/gpu/drm/bridge/synopsys/dw-hdmi-gp-audio.c201
-rw-r--r--drivers/gpu/drm/bridge/synopsys/dw-hdmi.c163
-rw-r--r--drivers/gpu/drm/bridge/synopsys/dw-hdmi.h13
-rw-r--r--drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c16
-rw-r--r--drivers/gpu/drm/drm_of.c31
-rw-r--r--drivers/gpu/drm/imx/Kconfig84
-rw-r--r--drivers/gpu/drm/imx/Makefile17
-rw-r--r--drivers/gpu/drm/imx/dcnano/Kconfig10
-rw-r--r--drivers/gpu/drm/imx/dcnano/Makefile5
-rw-r--r--drivers/gpu/drm/imx/dcnano/dcnano-crtc.c488
-rw-r--r--drivers/gpu/drm/imx/dcnano/dcnano-drv.c364
-rw-r--r--drivers/gpu/drm/imx/dcnano/dcnano-drv.h62
-rw-r--r--drivers/gpu/drm/imx/dcnano/dcnano-kms.c194
-rw-r--r--drivers/gpu/drm/imx/dcnano/dcnano-plane.c178
-rw-r--r--drivers/gpu/drm/imx/dcnano/dcnano-reg.h438
-rw-r--r--drivers/gpu/drm/imx/dcss/Kconfig1
-rw-r--r--drivers/gpu/drm/imx/dcss/Makefile3
-rw-r--r--drivers/gpu/drm/imx/dcss/dcss-crtc.c275
-rw-r--r--drivers/gpu/drm/imx/dcss/dcss-ctxld.c5
-rw-r--r--drivers/gpu/drm/imx/dcss/dcss-dec400d.c270
-rw-r--r--drivers/gpu/drm/imx/dcss/dcss-dev.c92
-rw-r--r--drivers/gpu/drm/imx/dcss/dcss-dev.h180
-rw-r--r--drivers/gpu/drm/imx/dcss/dcss-dpr.c63
-rw-r--r--drivers/gpu/drm/imx/dcss/dcss-drv.c101
-rw-r--r--drivers/gpu/drm/imx/dcss/dcss-dtg.c19
-rw-r--r--drivers/gpu/drm/imx/dcss/dcss-dtrc.c517
-rw-r--r--drivers/gpu/drm/imx/dcss/dcss-hdr10-tables.h3018
-rw-r--r--drivers/gpu/drm/imx/dcss/dcss-hdr10.c395
-rw-r--r--drivers/gpu/drm/imx/dcss/dcss-kms.c54
-rw-r--r--drivers/gpu/drm/imx/dcss/dcss-kms.h43
-rw-r--r--drivers/gpu/drm/imx/dcss/dcss-plane.c366
-rw-r--r--drivers/gpu/drm/imx/dcss/dcss-rdsrc.c119
-rw-r--r--drivers/gpu/drm/imx/dcss/dcss-scaler.c104
-rw-r--r--drivers/gpu/drm/imx/dcss/dcss-ss.c27
-rw-r--r--drivers/gpu/drm/imx/dcss/dcss-wrscl.c158
-rw-r--r--drivers/gpu/drm/imx/dpu/Kconfig6
-rw-r--r--drivers/gpu/drm/imx/dpu/Makefile8
-rw-r--r--drivers/gpu/drm/imx/dpu/dpu-blit.c346
-rw-r--r--drivers/gpu/drm/imx/dpu/dpu-blit.h18
-rw-r--r--drivers/gpu/drm/imx/dpu/dpu-crc.c385
-rw-r--r--drivers/gpu/drm/imx/dpu/dpu-crc.h75
-rw-r--r--drivers/gpu/drm/imx/dpu/dpu-crtc.c1466
-rw-r--r--drivers/gpu/drm/imx/dpu/dpu-crtc.h115
-rw-r--r--drivers/gpu/drm/imx/dpu/dpu-kms.c759
-rw-r--r--drivers/gpu/drm/imx/dpu/dpu-kms.h20
-rw-r--r--drivers/gpu/drm/imx/dpu/dpu-plane.c1024
-rw-r--r--drivers/gpu/drm/imx/dpu/dpu-plane.h210
-rw-r--r--drivers/gpu/drm/imx/dw_hdmi-imx.c233
-rw-r--r--drivers/gpu/drm/imx/dw_mipi_dsi-imx.c621
-rw-r--r--drivers/gpu/drm/imx/imx-drm-core.c290
-rw-r--r--drivers/gpu/drm/imx/imx-drm.h7
-rw-r--r--drivers/gpu/drm/imx/imx8mp-hdmi-pavi.c220
-rw-r--r--drivers/gpu/drm/imx/imx8mp-hdmi-pavi.h38
-rw-r--r--drivers/gpu/drm/imx/imx8mp-ldb.c466
-rw-r--r--drivers/gpu/drm/imx/imx8qm-ldb.c571
-rw-r--r--drivers/gpu/drm/imx/imx8qxp-ldb.c882
-rw-r--r--drivers/gpu/drm/imx/imx93-ldb.c347
-rw-r--r--drivers/gpu/drm/imx/imx93-parallel-disp-fmt.c203
-rw-r--r--drivers/gpu/drm/imx/ipuv3/Kconfig6
-rw-r--r--drivers/gpu/drm/imx/ipuv3/Makefile4
-rw-r--r--drivers/gpu/drm/imx/ipuv3/ipuv3-crtc.c (renamed from drivers/gpu/drm/imx/ipuv3-crtc.c)21
-rw-r--r--drivers/gpu/drm/imx/ipuv3/ipuv3-kms.c94
-rw-r--r--drivers/gpu/drm/imx/ipuv3/ipuv3-kms.h21
-rw-r--r--drivers/gpu/drm/imx/ipuv3/ipuv3-plane.c (renamed from drivers/gpu/drm/imx/ipuv3-plane.c)0
-rw-r--r--drivers/gpu/drm/imx/ipuv3/ipuv3-plane.h (renamed from drivers/gpu/drm/imx/ipuv3-plane.h)3
-rw-r--r--drivers/gpu/drm/imx/lcdif-mux-display.c254
-rw-r--r--drivers/gpu/drm/imx/lcdif/Kconfig8
-rw-r--r--drivers/gpu/drm/imx/lcdif/Makefile4
-rw-r--r--drivers/gpu/drm/imx/lcdif/lcdif-crtc.c461
-rw-r--r--drivers/gpu/drm/imx/lcdif/lcdif-kms.c47
-rw-r--r--drivers/gpu/drm/imx/lcdif/lcdif-kms.h39
-rw-r--r--drivers/gpu/drm/imx/lcdif/lcdif-plane.c261
-rw-r--r--drivers/gpu/drm/imx/lcdif/lcdif-plane.h37
-rw-r--r--drivers/gpu/drm/imx/lcdifv3/Kconfig8
-rw-r--r--drivers/gpu/drm/imx/lcdifv3/Makefile4
-rw-r--r--drivers/gpu/drm/imx/lcdifv3/lcdifv3-crtc.c421
-rw-r--r--drivers/gpu/drm/imx/lcdifv3/lcdifv3-kms.c37
-rw-r--r--drivers/gpu/drm/imx/lcdifv3/lcdifv3-kms.h12
-rw-r--r--drivers/gpu/drm/imx/lcdifv3/lcdifv3-plane.c207
-rw-r--r--drivers/gpu/drm/imx/lcdifv3/lcdifv3-plane.h24
-rw-r--r--drivers/gpu/drm/imx/mhdp/Kconfig12
-rw-r--r--drivers/gpu/drm/imx/mhdp/Makefile5
-rw-r--r--drivers/gpu/drm/imx/mhdp/cdns-mhdp-dp-phy.c534
-rw-r--r--drivers/gpu/drm/imx/mhdp/cdns-mhdp-hdmi-phy.c796
-rw-r--r--drivers/gpu/drm/imx/mhdp/cdns-mhdp-imx.h76
-rw-r--r--drivers/gpu/drm/imx/mhdp/cdns-mhdp-imx8qm.c642
-rw-r--r--drivers/gpu/drm/imx/mhdp/cdns-mhdp-imxdrv.c276
-rw-r--r--drivers/gpu/drm/imx/mhdp/cdns-mhdp-ls1028a.c110
-rw-r--r--drivers/gpu/drm/imx/mhdp/cdns-mhdp-phy.h159
-rw-r--r--drivers/gpu/drm/imx/sec_mipi_dphy_ln14lpp.h227
-rw-r--r--drivers/gpu/drm/imx/sec_mipi_dsim-imx.c583
-rw-r--r--drivers/gpu/drm/imx/sec_mipi_pll_1432x.h49
-rw-r--r--drivers/gpu/drm/mxsfb/mxsfb_drv.c3
-rw-r--r--drivers/gpu/drm/mxsfb/mxsfb_drv.h1
-rw-r--r--drivers/gpu/drm/mxsfb/mxsfb_kms.c247
-rw-r--r--drivers/gpu/drm/mxsfb/mxsfb_regs.h117
-rw-r--r--drivers/gpu/drm/panel/Kconfig29
-rw-r--r--drivers/gpu/drm/panel/Makefile3
-rw-r--r--drivers/gpu/drm/panel/panel-ontat-kd50g21-40nt-a1.c370
-rw-r--r--drivers/gpu/drm/panel/panel-raydium-rm67191.c414
-rw-r--r--drivers/gpu/drm/panel/panel-raydium-rm68200.c67
-rw-r--r--drivers/gpu/drm/panel/panel-rocktech-hx8394f.c404
-rw-r--r--drivers/gpu/drm/panel/panel-simple.c74
-rw-r--r--drivers/gpu/drm/panel/panel-wks-101wx001.c228
-rw-r--r--drivers/gpu/imx/Kconfig19
-rw-r--r--drivers/gpu/imx/Makefile8
-rw-r--r--drivers/gpu/imx/dpu-blit/Kconfig5
-rw-r--r--drivers/gpu/imx/dpu-blit/Makefile5
-rw-r--r--drivers/gpu/imx/dpu-blit/dpu-blit-registers.h284
-rw-r--r--drivers/gpu/imx/dpu-blit/dpu-blit.c611
-rw-r--r--drivers/gpu/imx/dpu-blit/dpu-blit.h67
-rw-r--r--drivers/gpu/imx/dpu/Kconfig8
-rw-r--r--drivers/gpu/imx/dpu/Makefile8
-rw-r--r--drivers/gpu/imx/dpu/dpu-common.c1361
-rw-r--r--drivers/gpu/imx/dpu/dpu-constframe.c253
-rw-r--r--drivers/gpu/imx/dpu/dpu-disengcfg.c158
-rw-r--r--drivers/gpu/imx/dpu/dpu-extdst.c521
-rw-r--r--drivers/gpu/imx/dpu/dpu-fetchdecode.c673
-rw-r--r--drivers/gpu/imx/dpu/dpu-fetcheco.c407
-rw-r--r--drivers/gpu/imx/dpu/dpu-fetchlayer.c294
-rw-r--r--drivers/gpu/imx/dpu/dpu-fetchunit.c346
-rw-r--r--drivers/gpu/imx/dpu/dpu-fetchwarp.c305
-rw-r--r--drivers/gpu/imx/dpu/dpu-framegen.c601
-rw-r--r--drivers/gpu/imx/dpu/dpu-hscaler.c386
-rw-r--r--drivers/gpu/imx/dpu/dpu-layerblend.c346
-rw-r--r--drivers/gpu/imx/dpu/dpu-prv.h475
-rw-r--r--drivers/gpu/imx/dpu/dpu-sc-misc.c93
-rw-r--r--drivers/gpu/imx/dpu/dpu-signature.c392
-rw-r--r--drivers/gpu/imx/dpu/dpu-store.c157
-rw-r--r--drivers/gpu/imx/dpu/dpu-tcon.c330
-rw-r--r--drivers/gpu/imx/dpu/dpu-vscaler.c438
-rw-r--r--drivers/gpu/imx/imx8_dprc.c893
-rw-r--r--drivers/gpu/imx/imx8_pc.c218
-rw-r--r--drivers/gpu/imx/imx8_prg.c452
-rw-r--r--drivers/gpu/imx/ipu-v3/Kconfig (renamed from drivers/gpu/ipu-v3/Kconfig)0
-rw-r--r--drivers/gpu/imx/ipu-v3/Makefile (renamed from drivers/gpu/ipu-v3/Makefile)0
-rw-r--r--drivers/gpu/imx/ipu-v3/ipu-common.c (renamed from drivers/gpu/ipu-v3/ipu-common.c)0
-rw-r--r--drivers/gpu/imx/ipu-v3/ipu-cpmem.c (renamed from drivers/gpu/ipu-v3/ipu-cpmem.c)0
-rw-r--r--drivers/gpu/imx/ipu-v3/ipu-csi.c (renamed from drivers/gpu/ipu-v3/ipu-csi.c)0
-rw-r--r--drivers/gpu/imx/ipu-v3/ipu-dc.c (renamed from drivers/gpu/ipu-v3/ipu-dc.c)0
-rw-r--r--drivers/gpu/imx/ipu-v3/ipu-di.c (renamed from drivers/gpu/ipu-v3/ipu-di.c)0
-rw-r--r--drivers/gpu/imx/ipu-v3/ipu-dmfc.c (renamed from drivers/gpu/ipu-v3/ipu-dmfc.c)0
-rw-r--r--drivers/gpu/imx/ipu-v3/ipu-dp.c (renamed from drivers/gpu/ipu-v3/ipu-dp.c)0
-rw-r--r--drivers/gpu/imx/ipu-v3/ipu-ic-csc.c (renamed from drivers/gpu/ipu-v3/ipu-ic-csc.c)0
-rw-r--r--drivers/gpu/imx/ipu-v3/ipu-ic.c (renamed from drivers/gpu/ipu-v3/ipu-ic.c)0
-rw-r--r--drivers/gpu/imx/ipu-v3/ipu-image-convert.c (renamed from drivers/gpu/ipu-v3/ipu-image-convert.c)0
-rw-r--r--drivers/gpu/imx/ipu-v3/ipu-pre.c (renamed from drivers/gpu/ipu-v3/ipu-pre.c)0
-rw-r--r--drivers/gpu/imx/ipu-v3/ipu-prg.c (renamed from drivers/gpu/ipu-v3/ipu-prg.c)0
-rw-r--r--drivers/gpu/imx/ipu-v3/ipu-prv.h (renamed from drivers/gpu/ipu-v3/ipu-prv.h)0
-rw-r--r--drivers/gpu/imx/ipu-v3/ipu-smfc.c (renamed from drivers/gpu/ipu-v3/ipu-smfc.c)0
-rw-r--r--drivers/gpu/imx/ipu-v3/ipu-vdi.c (renamed from drivers/gpu/ipu-v3/ipu-vdi.c)0
-rw-r--r--drivers/gpu/imx/lcdif/Kconfig9
-rw-r--r--drivers/gpu/imx/lcdif/Makefile3
-rw-r--r--drivers/gpu/imx/lcdif/lcdif-common.c854
-rw-r--r--drivers/gpu/imx/lcdif/lcdif-regs.h153
-rw-r--r--drivers/gpu/imx/lcdifv3/Kconfig10
-rw-r--r--drivers/gpu/imx/lcdifv3/Makefile3
-rw-r--r--drivers/gpu/imx/lcdifv3/lcdifv3-common.c883
-rw-r--r--drivers/gpu/imx/lcdifv3/lcdifv3-regs.h150
192 files changed, 49154 insertions, 648 deletions
diff --git a/drivers/gpu/Makefile b/drivers/gpu/Makefile
index 835c88318cec..b8a7b5c5a029 100644
--- a/drivers/gpu/Makefile
+++ b/drivers/gpu/Makefile
@@ -3,6 +3,6 @@
# taken to initialize them in the correct order. Link order is the only way
# to ensure this currently.
obj-$(CONFIG_TEGRA_HOST1X) += host1x/
+obj-y += imx/
obj-y += drm/ vga/
-obj-$(CONFIG_IMX_IPUV3_CORE) += ipu-v3/
obj-$(CONFIG_TRACE_GPU_MEM) += trace/
diff --git a/drivers/gpu/drm/arm/malidp_crtc.c b/drivers/gpu/drm/arm/malidp_crtc.c
index b5928b52e279..081beb102150 100644
--- a/drivers/gpu/drm/arm/malidp_crtc.c
+++ b/drivers/gpu/drm/arm/malidp_crtc.c
@@ -31,7 +31,7 @@ static enum drm_mode_status malidp_crtc_mode_valid(struct drm_crtc *crtc,
* check that the hardware can drive the required clock rate,
* but skip the check if the clock is meant to be disabled (req_rate = 0)
*/
- long rate, req_rate = mode->crtc_clock * 1000;
+ long rate, req_rate = mode->clock * 1000;
if (req_rate) {
rate = clk_round_rate(hwdev->pxlclk, req_rate);
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index 68ec45abc1fb..3d81471f89d3 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -126,6 +126,15 @@ config DRM_ITE_IT66121
help
Support for ITE IT66121 HDMI bridge.
+config DRM_FSL_IMX_LVDS_BRIDGE
+ tristate "Freescale i.MX LVDS display bridge"
+ depends on MFD_SYSCON
+ depends on OF
+ select DRM_PANEL_BRIDGE
+ help
+ Support Freescale i.MX parallel to LVDS Display Bridge (LDB).
+ This bridge is embedded in a SoC.
+
config DRM_LVDS_CODEC
tristate "Transparent LVDS encoders and decoders support"
depends on OF
@@ -162,6 +171,20 @@ config DRM_NWL_MIPI_DSI
This enables the Northwest Logic MIPI DSI Host controller as
for example found on NXP's i.MX8 Processors.
+config DRM_SEC_MIPI_DSIM
+ tristate "Samsung MIPI DSIM Bridge"
+ depends on OF
+ select DRM_KMS_HELPER
+ select DRM_MIPI_DSI
+ select DRM_PANEL
+ help
+ The Samsung MPI DSIM Bridge driver.
+
+config DRM_NXP_SEIKO_43WVFIG
+ tristate "Legacy Freescale Seiko 43WVFIG panel DPI adapter bridge"
+ select DRM_KMS_HELPER
+ select DRM_PANEL
+
config DRM_NXP_PTN3460
tristate "NXP PTN3460 DP/LVDS bridge"
depends on OF
@@ -323,4 +346,20 @@ source "drivers/gpu/drm/bridge/cadence/Kconfig"
source "drivers/gpu/drm/bridge/synopsys/Kconfig"
+config DRM_ITE_IT6263
+ tristate "ITE IT6263 LVDS/HDMI bridge"
+ depends on OF
+ select DRM_KMS_HELPER
+ select REGMAP_I2C
+ help
+ ITE IT6263 bridge chip driver.
+
+config DRM_ITE_IT6161
+ tristate "ITE IT6161 MIPI/HDMI bridge"
+ depends on OF
+ select DRM_KMS_HELPER
+ select REGMAP_I2C
+ help
+ ITE IT6161 bridge chip driver.
+
endmenu
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index f2c73683cfcb..0f93f88d1815 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -7,6 +7,7 @@ obj-$(CONFIG_DRM_DISPLAY_CONNECTOR) += display-connector.o
obj-$(CONFIG_DRM_LONTIUM_LT8912B) += lontium-lt8912b.o
obj-$(CONFIG_DRM_LONTIUM_LT9611) += lontium-lt9611.o
obj-$(CONFIG_DRM_LONTIUM_LT9611UXC) += lontium-lt9611uxc.o
+obj-$(CONFIG_DRM_FSL_IMX_LVDS_BRIDGE) += fsl-imx-ldb.o
obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o
obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v3-fw.o
obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
@@ -33,3 +34,7 @@ obj-$(CONFIG_DRM_ITE_IT66121) += ite-it66121.o
obj-y += analogix/
obj-y += cadence/
obj-y += synopsys/
+obj-$(CONFIG_DRM_ITE_IT6263) += it6263.o
+obj-$(CONFIG_DRM_ITE_IT6263) += it6161.o
+obj-$(CONFIG_DRM_SEC_MIPI_DSIM) += sec-dsim.o
+obj-$(CONFIG_DRM_NXP_SEIKO_43WVFIG) += nxp-seiko-43wvfig.o
diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511.h b/drivers/gpu/drm/bridge/adv7511/adv7511.h
index fdd8e3d3232e..4aa8c601223f 100644
--- a/drivers/gpu/drm/bridge/adv7511/adv7511.h
+++ b/drivers/gpu/drm/bridge/adv7511/adv7511.h
@@ -221,6 +221,9 @@
#define ADV7511_REG_CEC_SOFT_RESET 0x50
#define ADV7533_REG_CEC_OFFSET 0x70
+#define FORMAT_RATIO(x, y) (((x) * 100) / (y))
+#define RATIO_16_9 FORMAT_RATIO(16, 9)
+#define RATIO_4_3 FORMAT_RATIO(4, 3)
enum adv7511_input_clock {
ADV7511_INPUT_CLOCK_1X,
@@ -333,6 +336,10 @@ struct adv7511 {
struct i2c_client *i2c_packet;
struct i2c_client *i2c_cec;
+ u32 addr_cec;
+ u32 addr_edid;
+ u32 addr_pkt;
+
struct regmap *regmap;
struct regmap *regmap_cec;
enum drm_connector_status status;
@@ -368,6 +375,7 @@ struct adv7511 {
struct device_node *host_node;
struct mipi_dsi_device *dsi;
u8 num_dsi_lanes;
+ u8 channel_id;
bool use_timing_gen;
enum adv7511_type type;
diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c b/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c
index ddd1305b82b2..bd2628de9be1 100644
--- a/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c
+++ b/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c
@@ -302,7 +302,8 @@ static int adv7511_cec_parse_dt(struct device *dev, struct adv7511 *adv7511)
int adv7511_cec_init(struct device *dev, struct adv7511 *adv7511)
{
- unsigned int offset = adv7511->type == ADV7533 ?
+ unsigned int offset = (adv7511->type == ADV7533 ||
+ adv7511->type == ADV7535) ?
ADV7533_REG_CEC_OFFSET : 0;
int ret = adv7511_cec_parse_dt(dev, adv7511);
diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c
index 44762116aac9..dfff98fb2fdc 100644
--- a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c
+++ b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c
@@ -9,7 +9,9 @@
#include <linux/device.h>
#include <linux/gpio/consumer.h>
#include <linux/module.h>
+#include <linux/of.h>
#include <linux/of_device.h>
+#include <linux/of_graph.h>
#include <linux/slab.h>
#include <media/cec.h>
@@ -74,6 +76,25 @@ static const uint8_t adv7511_register_defaults[] = {
0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
+/*
+ * TODO: Currently, filter-out unsupported modes by their clocks.
+ * Need to find a better way to do this.
+ * These are the pixel clocks that the converter can handle successfully.
+ */
+
+static const int valid_clocks[] = {
+ 148500,
+ 135000,
+ 132000,
+ 108000,
+ 78750,
+ 74250,
+ 65000,
+ 49500,
+ 40000,
+ 31500,
+};
+
static bool adv7511_register_volatile(struct device *dev, unsigned int reg)
{
switch (reg) {
@@ -329,6 +350,7 @@ static void __adv7511_power_on(struct adv7511 *adv7511)
{
adv7511->current_edid_segment = -1;
+ /* 01-02 Power */
regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER,
ADV7511_POWER_POWER_DOWN, 0);
if (adv7511->i2c_main->irq) {
@@ -346,6 +368,7 @@ static void __adv7511_power_on(struct adv7511 *adv7511)
}
/*
+ * 01-01 HPD Manual Override
* Per spec it is allowed to pulse the HPD signal to indicate that the
* EDID information has changed. Some monitors do this when they wakeup
* from standby or are enabled. When the HPD goes low the adv7511 is
@@ -427,17 +450,16 @@ static bool adv7511_hpd(struct adv7511 *adv7511)
static void adv7511_hpd_work(struct work_struct *work)
{
struct adv7511 *adv7511 = container_of(work, struct adv7511, hpd_work);
- enum drm_connector_status status;
+ enum drm_connector_status status = connector_status_disconnected;
unsigned int val;
int ret;
ret = regmap_read(adv7511->regmap, ADV7511_REG_STATUS, &val);
- if (ret < 0)
- status = connector_status_disconnected;
- else if (val & ADV7511_STATUS_HPD)
+ if (ret >= 0 && (val & ADV7511_STATUS_HPD))
status = connector_status_connected;
- else
- status = connector_status_disconnected;
+
+ DRM_DEV_DEBUG_DRIVER(adv7511->connector.kdev, "HDMI HPD event: %s\n",
+ drm_get_connector_status_name(status));
/*
* The bridge resets its registers on unplug. So when we get a plug
@@ -639,6 +661,8 @@ static int adv7511_get_modes(struct adv7511 *adv7511,
{
struct edid *edid;
unsigned int count;
+ u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24;
+ int ret;
edid = adv7511_get_edid(adv7511, connector);
@@ -647,6 +671,14 @@ static int adv7511_get_modes(struct adv7511 *adv7511,
kfree(edid);
+ connector->display_info.bus_flags = DRM_BUS_FLAG_DE_LOW |
+ DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE;
+
+ ret = drm_display_info_set_bus_formats(&connector->display_info,
+ &bus_format, 1);
+ if (ret)
+ return ret;
+
return count;
}
@@ -699,9 +731,21 @@ adv7511_detect(struct adv7511 *adv7511, struct drm_connector *connector)
static enum drm_mode_status adv7511_mode_valid(struct adv7511 *adv7511,
const struct drm_display_mode *mode)
{
+ size_t i, num_modes = ARRAY_SIZE(valid_clocks);
+ bool clock_ok = false;
+
if (mode->clock > 165000)
return MODE_CLOCK_HIGH;
+ for (i = 0; i < num_modes; i++)
+ if (mode->clock == valid_clocks[i]) {
+ clock_ok = true;
+ break;
+ }
+
+ if (!clock_ok)
+ return MODE_NOCLOCK;
+
return MODE_OK;
}
@@ -786,8 +830,13 @@ static void adv7511_mode_set(struct adv7511 *adv7511,
else
low_refresh_rate = ADV7511_LOW_REFRESH_RATE_NONE;
- regmap_update_bits(adv7511->regmap, 0xfb,
- 0x6, low_refresh_rate << 1);
+ if (adv7511->type == ADV7535)
+ regmap_update_bits(adv7511->regmap, 0x4a,
+ 0xc, low_refresh_rate << 2);
+ else
+ regmap_update_bits(adv7511->regmap, 0xfb,
+ 0x6, low_refresh_rate << 1);
+
regmap_update_bits(adv7511->regmap, 0x17,
0x60, (vsync_polarity << 6) | (hsync_polarity << 5));
@@ -901,6 +950,14 @@ static void adv7511_bridge_disable(struct drm_bridge *bridge)
adv7511_power_off(adv);
}
+static int adv7511_bridge_get_modes(struct drm_bridge *bridge,
+ struct drm_connector *connector)
+{
+ struct adv7511 *adv = bridge_to_adv7511(bridge);
+
+ return adv7511_get_modes(adv, connector);
+}
+
static void adv7511_bridge_mode_set(struct drm_bridge *bridge,
const struct drm_display_mode *mode,
const struct drm_display_mode *adj_mode)
@@ -968,15 +1025,31 @@ static void adv7511_bridge_hpd_notify(struct drm_bridge *bridge,
cec_phys_addr_invalidate(adv->cec_adap);
}
+static void adv7511_bridge_detach(struct drm_bridge *bridge)
+{
+ struct adv7511 *adv = bridge_to_adv7511(bridge);
+
+ if (adv->i2c_main->irq)
+ regmap_write(adv->regmap, ADV7511_REG_INT_ENABLE(0), 0);
+
+ if (adv->type == ADV7533 || adv->type == ADV7535)
+ adv7533_detach_dsi(adv);
+
+ drm_connector_cleanup(&adv->connector);
+}
+
static const struct drm_bridge_funcs adv7511_bridge_funcs = {
.enable = adv7511_bridge_enable,
.disable = adv7511_bridge_disable,
+ .get_modes = adv7511_bridge_get_modes,
+ .mode_valid = adv7511_bridge_mode_valid,
.mode_set = adv7511_bridge_mode_set,
.mode_valid = adv7511_bridge_mode_valid,
.attach = adv7511_bridge_attach,
.detect = adv7511_bridge_detect,
.get_edid = adv7511_bridge_get_edid,
.hpd_notify = adv7511_bridge_hpd_notify,
+ .detach = adv7511_bridge_detach,
};
/* -----------------------------------------------------------------------------
@@ -1070,7 +1143,7 @@ static int adv7511_init_cec_regmap(struct adv7511 *adv)
int ret;
adv->i2c_cec = i2c_new_ancillary_device(adv->i2c_main, "cec",
- ADV7511_CEC_I2C_ADDR_DEFAULT);
+ adv->addr_cec);
if (IS_ERR(adv->i2c_cec))
return PTR_ERR(adv->i2c_cec);
@@ -1186,6 +1259,14 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id)
struct adv7511_link_config link_config;
struct adv7511 *adv7511;
struct device *dev = &i2c->dev;
+#if IS_ENABLED(CONFIG_OF_DYNAMIC)
+ struct device_node *remote_node = NULL, *endpoint = NULL;
+ struct of_changeset ocs;
+#endif
+ unsigned int main_i2c_addr = i2c->addr << 1;
+ unsigned int edid_i2c_addr = main_i2c_addr + 4;
+ unsigned int cec_i2c_addr = main_i2c_addr - 2;
+ unsigned int pkt_i2c_addr = main_i2c_addr - 0xa;
unsigned int val;
int ret;
@@ -1220,6 +1301,21 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id)
return ret;
}
+ if (adv7511->addr_cec != 0)
+ cec_i2c_addr = adv7511->addr_cec << 1;
+ else
+ adv7511->addr_cec = cec_i2c_addr >> 1;
+
+ if (adv7511->addr_edid != 0)
+ edid_i2c_addr = adv7511->addr_edid << 1;
+ else
+ adv7511->addr_edid = edid_i2c_addr >> 1;
+
+ if (adv7511->addr_pkt != 0)
+ pkt_i2c_addr = adv7511->addr_pkt << 1;
+ else
+ adv7511->addr_pkt = pkt_i2c_addr >> 1;
+
/*
* The power down GPIO is optional. If present, toggle it from active to
* inactive to wake up the encoder.
@@ -1257,25 +1353,28 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id)
adv7511_packet_disable(adv7511, 0xffff);
+ regmap_write(adv7511->regmap, ADV7511_REG_EDID_I2C_ADDR,
+ edid_i2c_addr);
+
adv7511->i2c_edid = i2c_new_ancillary_device(i2c, "edid",
- ADV7511_EDID_I2C_ADDR_DEFAULT);
+ adv7511->addr_edid);
if (IS_ERR(adv7511->i2c_edid)) {
ret = PTR_ERR(adv7511->i2c_edid);
goto uninit_regulators;
}
- regmap_write(adv7511->regmap, ADV7511_REG_EDID_I2C_ADDR,
- adv7511->i2c_edid->addr << 1);
+ regmap_write(adv7511->regmap, ADV7511_REG_PACKET_I2C_ADDR,
+ pkt_i2c_addr);
adv7511->i2c_packet = i2c_new_ancillary_device(i2c, "packet",
- ADV7511_PACKET_I2C_ADDR_DEFAULT);
+ adv7511->addr_pkt);
if (IS_ERR(adv7511->i2c_packet)) {
ret = PTR_ERR(adv7511->i2c_packet);
goto err_i2c_unregister_edid;
}
- regmap_write(adv7511->regmap, ADV7511_REG_PACKET_I2C_ADDR,
- adv7511->i2c_packet->addr << 1);
+ regmap_write(adv7511->regmap, ADV7511_REG_CEC_I2C_ADDR,
+ cec_i2c_addr);
ret = adv7511_init_cec_regmap(adv7511);
if (ret)
@@ -1326,6 +1425,37 @@ err_i2c_unregister_edid:
i2c_unregister_device(adv7511->i2c_edid);
uninit_regulators:
adv7511_uninit_regulators(adv7511);
+#if IS_ENABLED(CONFIG_OF_DYNAMIC)
+ endpoint = of_graph_get_next_endpoint(dev->of_node, NULL);
+ if (endpoint)
+ remote_node = of_graph_get_remote_port_parent(endpoint);
+
+ if (!remote_node)
+ return ret;
+
+ /* Find remote's endpoint connected to us and detach it */
+ endpoint = NULL;
+ while ((endpoint = of_graph_get_next_endpoint(remote_node,
+ endpoint))) {
+ struct device_node *us;
+
+ us = of_graph_get_remote_port_parent(endpoint);
+ if (us == dev->of_node)
+ break;
+ }
+ of_node_put(remote_node);
+
+ if (!endpoint)
+ return ret;
+
+ of_changeset_init(&ocs);
+ of_changeset_detach_node(&ocs, endpoint);
+ ret = of_changeset_apply(&ocs);
+ if (!ret)
+ dev_warn(dev,
+ "Probe failed. Remote port '%s' disabled\n",
+ remote_node->full_name);
+#endif
return ret;
}
@@ -1334,6 +1464,8 @@ static int adv7511_remove(struct i2c_client *i2c)
{
struct adv7511 *adv7511 = i2c_get_clientdata(i2c);
+ i2c_unregister_device(adv7511->i2c_cec);
+ clk_disable_unprepare(adv7511->cec_clk);
if (adv7511->type == ADV7533 || adv7511->type == ADV7535)
adv7533_detach_dsi(adv7511);
diff --git a/drivers/gpu/drm/bridge/adv7511/adv7533.c b/drivers/gpu/drm/bridge/adv7511/adv7533.c
index babc0be0bbb5..731e3d907787 100644
--- a/drivers/gpu/drm/bridge/adv7511/adv7533.c
+++ b/drivers/gpu/drm/bridge/adv7511/adv7533.c
@@ -26,10 +26,8 @@ static const struct reg_sequence adv7533_cec_fixed_registers[] = {
static void adv7511_dsi_config_timing_gen(struct adv7511 *adv)
{
- struct mipi_dsi_device *dsi = adv->dsi;
struct drm_display_mode *mode = &adv->curr_mode;
unsigned int hsw, hfp, hbp, vsw, vfp, vbp;
- u8 clock_div_by_lanes[] = { 6, 4, 3 }; /* 2, 3, 4 lanes */
hsw = mode->hsync_end - mode->hsync_start;
hfp = mode->hsync_start - mode->hdisplay;
@@ -38,9 +36,10 @@ static void adv7511_dsi_config_timing_gen(struct adv7511 *adv)
vfp = mode->vsync_start - mode->vdisplay;
vbp = mode->vtotal - mode->vsync_end;
- /* set pixel clock divider mode */
- regmap_write(adv->regmap_cec, 0x16,
- clock_div_by_lanes[dsi->lanes - 2] << 3);
+ /* 03-01 Enable Internal Timing Generator */
+ regmap_write(adv->regmap_cec, 0x27, 0xcb);
+
+ /* 03-08 Timing Configuration */
/* horizontal porch params */
regmap_write(adv->regmap_cec, 0x28, mode->htotal >> 4);
@@ -61,35 +60,66 @@ static void adv7511_dsi_config_timing_gen(struct adv7511 *adv)
regmap_write(adv->regmap_cec, 0x35, (vfp << 4) & 0xff);
regmap_write(adv->regmap_cec, 0x36, vbp >> 4);
regmap_write(adv->regmap_cec, 0x37, (vbp << 4) & 0xff);
+
+ /* 03-03 Reset Internal Timing Generator */
+ regmap_write(adv->regmap_cec, 0x27, 0xcb);
+ regmap_write(adv->regmap_cec, 0x27, 0x8b);
+ regmap_write(adv->regmap_cec, 0x27, 0xcb);
+
}
void adv7533_dsi_power_on(struct adv7511 *adv)
{
struct mipi_dsi_device *dsi = adv->dsi;
+ struct drm_display_mode *mode = &adv->curr_mode;
+ u8 clock_div_by_lanes[] = { 6, 4, 3 }; /* 2, 3, 4 lanes */
- if (adv->use_timing_gen)
- adv7511_dsi_config_timing_gen(adv);
+ /* Gate DSI LP Oscillator */
+ regmap_update_bits(adv->regmap_cec, 0x03, 0x02, 0x00);
+
+ /* 01-03 Initialisation (Fixed) Registers */
+ regmap_register_patch(adv->regmap_cec, adv7533_cec_fixed_registers,
+ ARRAY_SIZE(adv7533_cec_fixed_registers));
- /* set number of dsi lanes */
+ /* 02-04 DSI Lanes */
regmap_write(adv->regmap_cec, 0x1c, dsi->lanes << 4);
- if (adv->use_timing_gen) {
- /* reset internal timing generator */
- regmap_write(adv->regmap_cec, 0x27, 0xcb);
- regmap_write(adv->regmap_cec, 0x27, 0x8b);
- regmap_write(adv->regmap_cec, 0x27, 0xcb);
- } else {
- /* disable internal timing generator */
+ /* 02-05 DSI Pixel Clock Divider */
+ regmap_write(adv->regmap_cec, 0x16,
+ clock_div_by_lanes[dsi->lanes - 2] << 3);
+
+ if (adv->use_timing_gen)
+ adv7511_dsi_config_timing_gen(adv);
+ else
regmap_write(adv->regmap_cec, 0x27, 0x0b);
- }
- /* enable hdmi */
+ /* 04-01 HDMI Output */
+ regmap_write(adv->regmap, 0xaf, 0x16);
+
+ /* 09-03 AVI Infoframe - RGB - 16-9 Aspect Ratio */
+ regmap_write(adv->regmap, ADV7511_REG_AVI_INFOFRAME(0), 0x10);
+ if (FORMAT_RATIO(mode->hdisplay, mode->vdisplay) == RATIO_16_9)
+ regmap_write(adv->regmap, ADV7511_REG_AVI_INFOFRAME(1), 0x28);
+ else if (FORMAT_RATIO(mode->hdisplay, mode->vdisplay) == RATIO_4_3)
+ regmap_write(adv->regmap, ADV7511_REG_AVI_INFOFRAME(1), 0x18);
+
+ /* 04-04 GC Packet Enable */
+ regmap_write(adv->regmap, ADV7511_REG_PACKET_ENABLE0, 0x80);
+
+ /* 04-06 GC Colour Depth - 24 Bit */
+ regmap_write(adv->regmap, 0x4c, 0x04);
+
+ /* 04-09 Down Dither Output Colour Depth - 8 Bit (default) */
+ regmap_write(adv->regmap, 0x49, 0x00);
+
+ /* 07-01 CEC Power Mode - Always Active */
+ regmap_write(adv->regmap_cec, 0xbe, 0x3d);
+
+ /* 04-03 HDMI Output Enable */
regmap_write(adv->regmap_cec, 0x03, 0x89);
/* disable test mode */
regmap_write(adv->regmap_cec, 0x55, 0x00);
- regmap_register_patch(adv->regmap_cec, adv7533_cec_fixed_registers,
- ARRAY_SIZE(adv7533_cec_fixed_registers));
}
void adv7533_dsi_power_off(struct adv7511 *adv)
@@ -141,7 +171,7 @@ int adv7533_attach_dsi(struct adv7511 *adv)
struct mipi_dsi_device *dsi;
int ret = 0;
const struct mipi_dsi_device_info info = { .type = "adv7533",
- .channel = 0,
+ .channel = adv->channel_id,
.node = NULL,
};
@@ -187,14 +217,24 @@ void adv7533_detach_dsi(struct adv7511 *adv)
int adv7533_parse_dt(struct device_node *np, struct adv7511 *adv)
{
- u32 num_lanes;
+ struct device *dev = &adv->i2c_main->dev;
+ u32 num_lanes = 0, channel_id = 0;
+ of_property_read_u32(np, "adi,dsi-channel", &channel_id);
of_property_read_u32(np, "adi,dsi-lanes", &num_lanes);
- if (num_lanes < 1 || num_lanes > 4)
+ if (num_lanes < 1 || num_lanes > 4) {
+ dev_err(dev, "Invalid dsi-lanes: %d\n", num_lanes);
+ return -EINVAL;
+ }
+
+ if (channel_id > 3) {
+ dev_err(dev, "Invalid dsi-channel: %d\n", channel_id);
return -EINVAL;
+ }
adv->num_dsi_lanes = num_lanes;
+ adv->channel_id = channel_id;
adv->host_node = of_graph_get_remote_node(np, 0, 0);
if (!adv->host_node)
@@ -205,6 +245,10 @@ int adv7533_parse_dt(struct device_node *np, struct adv7511 *adv)
adv->use_timing_gen = !of_property_read_bool(np,
"adi,disable-timing-generator");
+ of_property_read_u32(np, "adi,addr-cec", &adv->addr_cec);
+ of_property_read_u32(np, "adi,addr-edid", &adv->addr_edid);
+ of_property_read_u32(np, "adi,addr-pkt", &adv->addr_pkt);
+
/* TODO: Check if these need to be parsed by DT or not */
adv->rgb = true;
adv->embedded_sync = false;
diff --git a/drivers/gpu/drm/bridge/cadence/Kconfig b/drivers/gpu/drm/bridge/cadence/Kconfig
index ef8c230e0f62..d08aadf32414 100644
--- a/drivers/gpu/drm/bridge/cadence/Kconfig
+++ b/drivers/gpu/drm/bridge/cadence/Kconfig
@@ -22,3 +22,36 @@ config DRM_CDNS_MHDP8546_J721E
initializes the J721E Display Port and sets up the
clock and data muxes.
endif
+
+config DRM_CDNS_MHDP
+ tristate "Cadence MHDP COMMON API driver"
+ select DRM_KMS_HELPER
+ select DRM_PANEL_BRIDGE
+ depends on OF
+ help
+ Support Cadence MHDP API library.
+
+config DRM_CDNS_HDMI
+ tristate "Cadence HDMI DRM driver"
+ depends on DRM_CDNS_MHDP
+
+config DRM_CDNS_DP
+ tristate "Cadence DP DRM driver"
+ depends on DRM_CDNS_MHDP
+
+config DRM_CDNS_AUDIO
+ tristate "Cadence MHDP Audio driver"
+ depends on DRM_CDNS_MHDP
+
+config DRM_CDNS_HDCP
+ tristate "Cadence HDMI/DP HDCP driver"
+ depends on DRM_CDNS_MHDP
+ help
+ Support HDCP for either HDMI or DisplayPort. This
+ requires that the SOC has HDCP keys programmed
+ in production.
+
+config DRM_CDNS_HDMI_CEC
+ tristate "Cadence MHDP HDMI CEC driver"
+ select CEC_CORE
+ select CEC_NOTIFIER
diff --git a/drivers/gpu/drm/bridge/cadence/Makefile b/drivers/gpu/drm/bridge/cadence/Makefile
index 4d2db8df1bc6..4e6df43065c7 100644
--- a/drivers/gpu/drm/bridge/cadence/Makefile
+++ b/drivers/gpu/drm/bridge/cadence/Makefile
@@ -2,3 +2,13 @@
obj-$(CONFIG_DRM_CDNS_MHDP8546) += cdns-mhdp8546.o
cdns-mhdp8546-y := cdns-mhdp8546-core.o cdns-mhdp8546-hdcp.o
cdns-mhdp8546-$(CONFIG_DRM_CDNS_MHDP8546_J721E) += cdns-mhdp8546-j721e.o
+
+cdns_mhdp_drmcore-y := cdns-mhdp-common.o cdns-mhdp-dp.o cdns-mhdp-hdmi.o
+
+cdns_mhdp_drmcore-$(CONFIG_DRM_CDNS_HDMI) += cdns-hdmi-core.o
+cdns_mhdp_drmcore-$(CONFIG_DRM_CDNS_DP) += cdns-dp-core.o
+cdns_mhdp_drmcore-$(CONFIG_DRM_CDNS_AUDIO) += cdns-mhdp-audio.o
+cdns_mhdp_drmcore-$(CONFIG_DRM_CDNS_HDCP) += cdns-mhdp-hdcp.o cdns-hdcp-common.o
+cdns_mhdp_drmcore-$(CONFIG_DRM_CDNS_HDMI_CEC) += cdns-mhdp-cec.o
+
+obj-$(CONFIG_DRM_CDNS_MHDP) += cdns_mhdp_drmcore.o
diff --git a/drivers/gpu/drm/bridge/cadence/cdns-dp-core.c b/drivers/gpu/drm/bridge/cadence/cdns-dp-core.c
new file mode 100644
index 000000000000..0b7afd009995
--- /dev/null
+++ b/drivers/gpu/drm/bridge/cadence/cdns-dp-core.c
@@ -0,0 +1,881 @@
+/*
+ * Cadence Display Port Interface (DP) driver
+ *
+ * Copyright 2019 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+#include <drm/bridge/cdns-mhdp.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_encoder_slave.h>
+#include <drm/drm_hdcp.h>
+#include <drm/drm_of.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
+#include <drm/drm_print.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of_device.h>
+
+#include "cdns-mhdp-hdcp.h"
+#include "cdns-hdcp-common.h"
+
+#define CDNS_DP_HPD_POLL_DWN_LOOP 5
+#define CDNS_DP_HPD_POLL_DWN_DLY_US 200
+#define CDNS_DP_HPD_POLL_UP_LOOP 5
+#define CDNS_DP_HPD_POLL_UP_DLY_US 1000
+
+/*
+ * This function only implements native DPDC reads and writes
+ */
+static ssize_t dp_aux_transfer(struct drm_dp_aux *aux,
+ struct drm_dp_aux_msg *msg)
+{
+ struct cdns_mhdp_device *mhdp = dev_get_drvdata(aux->dev);
+ bool native = msg->request & (DP_AUX_NATIVE_WRITE & DP_AUX_NATIVE_READ);
+ int ret;
+
+ /* Ignore address only message , for native */
+ if ((native == true) && ((msg->size == 0) || (msg->buffer == NULL))) {
+ msg->reply = DP_AUX_NATIVE_REPLY_ACK;
+ return msg->size;
+ }
+
+ /* msg sanity check */
+ if (msg->size > DP_AUX_MAX_PAYLOAD_BYTES) {
+ dev_err(mhdp->dev, "%s: invalid msg: size(%zu), request(%x)\n",
+ __func__, msg->size, (unsigned int)msg->request);
+ return -EINVAL;
+ }
+
+ if (msg->request == DP_AUX_NATIVE_WRITE) {
+ const u8 *buf = msg->buffer;
+ int i;
+ for (i = 0; i < msg->size; ++i) {
+ ret = cdns_mhdp_dpcd_write(mhdp,
+ msg->address + i, buf[i]);
+ if (!ret)
+ continue;
+
+ DRM_DEV_ERROR(mhdp->dev, "Failed to write DPCD\n");
+
+ return ret;
+ }
+ msg->reply = DP_AUX_NATIVE_REPLY_ACK;
+ return msg->size;
+ }
+
+ if (msg->request == DP_AUX_NATIVE_READ) {
+ ret = cdns_mhdp_dpcd_read(mhdp, msg->address, msg->buffer,
+ msg->size);
+ if (ret < 0)
+ return -EIO;
+ msg->reply = DP_AUX_NATIVE_REPLY_ACK;
+ return msg->size;
+ }
+
+ if (((msg->request & ~DP_AUX_I2C_MOT) == DP_AUX_I2C_WRITE)
+ || ((msg->request & ~DP_AUX_I2C_MOT) ==
+ DP_AUX_I2C_WRITE_STATUS_UPDATE)) {
+
+ u8 i2c_status = 0u;
+ u16 respSize = 0u;
+
+ ret = cdns_mhdp_i2c_write(mhdp, msg->address,
+ msg->buffer,
+ !!(msg->request & DP_AUX_I2C_MOT),
+ msg->size, &respSize);
+
+ if (ret < 0) {
+ dev_err(aux->dev, "cdns_mhdp_i2c_write status %d\n",
+ ret);
+ return -EIO;
+ }
+
+ ret = cdns_mhdp_get_last_i2c_status(mhdp, &i2c_status);
+ if (ret < 0) {
+ dev_err(aux->dev,
+ "cdns_mhdp_get_last_i2c_status status %d\n",
+ ret);
+ return -EIO;
+ }
+
+ switch (i2c_status) {
+ case 0u:
+ msg->reply = DP_AUX_I2C_REPLY_ACK;
+ break;
+ case 1u:
+ msg->reply = DP_AUX_I2C_REPLY_NACK;
+ break;
+ case 2u:
+ msg->reply = DP_AUX_I2C_REPLY_DEFER;
+ break;
+ default:
+ msg->reply = DP_AUX_I2C_REPLY_NACK;
+ break;
+ }
+
+ return respSize;
+ }
+
+ if ((msg->request & ~DP_AUX_I2C_MOT) == DP_AUX_I2C_READ) {
+
+ u8 i2c_status = 0u;
+ u16 respSize = 0u;
+
+ ret = cdns_mhdp_i2c_read(mhdp, msg->address, msg->buffer,
+ msg->size,
+ !!(msg->request & DP_AUX_I2C_MOT),
+ &respSize);
+ if (ret < 0)
+ return -EIO;
+
+ ret = cdns_mhdp_get_last_i2c_status(mhdp, &i2c_status);
+
+ if (ret < 0) {
+ dev_err(aux->dev,
+ "cdns_mhdp_get_last_i2c_status ret %d\n", ret);
+ return -EIO;
+ }
+
+ switch (i2c_status) {
+ case 0u:
+ msg->reply = DP_AUX_I2C_REPLY_ACK;
+ break;
+ case 1u:
+ msg->reply = DP_AUX_I2C_REPLY_NACK;
+ break;
+ case 2u:
+ msg->reply = DP_AUX_I2C_REPLY_DEFER;
+ break;
+ default:
+ msg->reply = DP_AUX_I2C_REPLY_NACK;
+ break;
+ }
+
+ return respSize;
+ }
+
+ return 0;
+}
+
+static int dp_aux_init(struct cdns_mhdp_device *mhdp,
+ struct device *dev)
+{
+ int ret;
+
+ mhdp->dp.aux.name = "imx_dp_aux";
+ mhdp->dp.aux.dev = dev;
+ mhdp->dp.aux.drm_dev = mhdp->drm_dev;
+ mhdp->dp.aux.transfer = dp_aux_transfer;
+
+ ret = drm_dp_aux_register(&mhdp->dp.aux);
+
+ return ret;
+}
+
+static int dp_aux_destroy(struct cdns_mhdp_device *mhdp)
+{
+ drm_dp_aux_unregister(&mhdp->dp.aux);
+ return 0;
+}
+
+static void dp_pixel_clk_reset(struct cdns_mhdp_device *mhdp)
+{
+ u32 val;
+
+ /* reset pixel clk */
+ val = cdns_mhdp_reg_read(mhdp, SOURCE_HDTX_CAR);
+ cdns_mhdp_reg_write(mhdp, SOURCE_HDTX_CAR, val & 0xFD);
+ cdns_mhdp_reg_write(mhdp, SOURCE_HDTX_CAR, val);
+}
+
+static void cdns_dp_mode_set(struct cdns_mhdp_device *mhdp)
+{
+ u32 lane_mapping = mhdp->lane_mapping;
+ int ret;
+
+ cdns_mhdp_plat_call(mhdp, pclk_rate);
+
+ /* delay for DP FW stable after pixel clock relock */
+ msleep(50);
+
+ dp_pixel_clk_reset(mhdp);
+
+ /* Get DP Caps */
+ ret = drm_dp_dpcd_read(&mhdp->dp.aux, DP_DPCD_REV, mhdp->dp.dpcd,
+ DP_RECEIVER_CAP_SIZE);
+ if (ret < 0) {
+ DRM_ERROR("Failed to get caps %d\n", ret);
+ return;
+ }
+
+ mhdp->dp.rate = drm_dp_max_link_rate(mhdp->dp.dpcd);
+ mhdp->dp.num_lanes = drm_dp_max_lane_count(mhdp->dp.dpcd);
+ mhdp->dp.link_training_type = DP_TX_FULL_LINK_TRAINING;
+
+ /* check the max link rate */
+ if (mhdp->dp.rate > CDNS_DP_MAX_LINK_RATE)
+ mhdp->dp.rate = CDNS_DP_MAX_LINK_RATE;
+
+ /* Initialize link rate/num_lanes as panel max link rate/max_num_lanes */
+ cdns_mhdp_plat_call(mhdp, phy_set);
+
+ /* Video off */
+ ret = cdns_mhdp_set_video_status(mhdp, CONTROL_VIDEO_IDLE);
+ if (ret) {
+ DRM_DEV_ERROR(mhdp->dev, "Failed to valid video %d\n", ret);
+ return;
+ }
+
+ /* Line swaping */
+ cdns_mhdp_reg_write(mhdp, LANES_CONFIG, 0x00400000 | lane_mapping);
+
+ /* Set DP host capability */
+ ret = cdns_mhdp_set_host_cap(mhdp);
+ if (ret) {
+ DRM_DEV_ERROR(mhdp->dev, "Failed to set host cap %d\n", ret);
+ return;
+ }
+
+ ret = cdns_mhdp_config_video(mhdp);
+ if (ret) {
+ DRM_DEV_ERROR(mhdp->dev, "Failed to config video %d\n", ret);
+ return;
+ }
+
+ return;
+}
+
+void cdns_dp_handle_hpd_irq(struct cdns_mhdp_device *mhdp)
+{
+ u8 status[6];
+
+ mutex_lock(&mhdp->lock);
+ cdns_mhdp_dpcd_read(mhdp, 0x200, &status[0], 6);
+ DRM_DEBUG_DRIVER("DPCD HPD IRQ STATUS: %08x\n", status[1]);
+ cdns_mhdp_dpcd_write(mhdp, 0x201, status[1]);
+ mutex_unlock(&mhdp->lock);
+}
+
+/* -----------------------------------------------------------------------------
+ * dp TX Setup
+ */
+static enum drm_connector_status
+cdns_dp_connector_detect(struct drm_connector *connector, bool force)
+{
+ struct cdns_mhdp_device *mhdp = container_of(connector,
+ struct cdns_mhdp_device, connector.base);
+ u8 hpd = 0xf;
+ mutex_lock(&mhdp->lock);
+ hpd = cdns_mhdp_read_hpd(mhdp);
+ mutex_unlock(&mhdp->lock);
+ DRM_DEBUG_DRIVER("%s hpd = %d\n", __func__, hpd);
+
+ if (mhdp->force_disconnected_sts && (hpd == 1)) {
+ DRM_DEBUG_DRIVER("Returning disconnect status until ready\n");
+ return connector_status_disconnected;
+ }
+ if (hpd == 0)
+ /* Cable Disconnedted */
+ return connector_status_disconnected;
+ else if (hpd == 3) {
+ /* Cable Connected and seen IRQ*/
+ DRM_DEBUG_DRIVER("Warning! Missed HPD IRQ event seen\n");
+ return connector_status_connected;
+ } else if (hpd == 1)
+ /* Cable Connected */
+ return connector_status_connected;
+
+ /* Cable status unknown */
+ DRM_DEBUG_DRIVER("Unknown cable status, hdp=%u\n", hpd);
+ return connector_status_unknown;
+}
+
+static int cdns_dp_connector_get_modes(struct drm_connector *connector)
+{
+ struct cdns_mhdp_device *mhdp = container_of(connector,
+ struct cdns_mhdp_device, connector.base);
+ int num_modes = 0;
+ struct edid *edid;
+
+ edid = drm_do_get_edid(&mhdp->connector.base,
+ cdns_mhdp_get_edid_block, mhdp);
+ if (edid) {
+ DRM_DEBUG_DRIVER("%x,%x,%x,%x,%x,%x,%x,%x\n",
+ edid->header[0], edid->header[1],
+ edid->header[2], edid->header[3],
+ edid->header[4], edid->header[5],
+ edid->header[6], edid->header[7]);
+ drm_connector_update_edid_property(connector, edid);
+ num_modes = drm_add_edid_modes(connector, edid);
+ kfree(edid);
+ }
+
+ if (num_modes == 0)
+ DRM_ERROR("Invalid edid\n");
+ return num_modes;
+}
+
+static bool blob_equal(const struct drm_property_blob *a,
+ const struct drm_property_blob *b)
+{
+ if (a && b)
+ return a->length == b->length &&
+ !memcmp(a->data, b->data, a->length);
+
+ return !a == !b;
+}
+
+static int cdns_dp_connector_atomic_check(struct drm_connector *connector,
+ struct drm_atomic_state *state)
+{
+ struct drm_connector_state *new_con_state =
+ drm_atomic_get_new_connector_state(state, connector);
+ struct drm_connector_state *old_con_state =
+ drm_atomic_get_old_connector_state(state, connector);
+ struct drm_crtc *crtc = new_con_state->crtc;
+ struct drm_crtc_state *new_crtc_state;
+
+ cdns_hdcp_atomic_check(connector, old_con_state, new_con_state);
+ if (!new_con_state->crtc)
+ return 0;
+
+ new_crtc_state = drm_atomic_get_crtc_state(state, crtc);
+ if (IS_ERR(new_crtc_state))
+ return PTR_ERR(new_crtc_state);
+
+ if (!blob_equal(new_con_state->hdr_output_metadata,
+ old_con_state->hdr_output_metadata) ||
+ new_con_state->colorspace != old_con_state->colorspace) {
+
+ new_crtc_state->mode_changed =
+ !new_con_state->hdr_output_metadata ||
+ !old_con_state->hdr_output_metadata ||
+ new_con_state->colorspace != old_con_state->colorspace;
+ }
+
+ /*
+ * These properties are handled by fastset, and might not end up in a
+ * modeset.
+ */
+ if (new_con_state->picture_aspect_ratio !=
+ old_con_state->picture_aspect_ratio ||
+ new_con_state->content_type != old_con_state->content_type ||
+ new_con_state->scaling_mode != old_con_state->scaling_mode)
+ new_crtc_state->mode_changed = true;
+ return 0;
+}
+
+static const struct drm_connector_funcs cdns_dp_connector_funcs = {
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .detect = cdns_dp_connector_detect,
+ .destroy = drm_connector_cleanup,
+ .reset = drm_atomic_helper_connector_reset,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static const struct drm_connector_helper_funcs cdns_dp_connector_helper_funcs = {
+ .get_modes = cdns_dp_connector_get_modes,
+ .atomic_check = cdns_dp_connector_atomic_check,
+};
+
+static int cdns_dp_bridge_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
+{
+ struct cdns_mhdp_device *mhdp = bridge->driver_private;
+ struct drm_encoder *encoder = bridge->encoder;
+ struct drm_connector *connector = &mhdp->connector.base;
+ int ret;
+
+ connector->interlace_allowed = 1;
+
+ if (mhdp->is_hpd)
+ connector->polled = DRM_CONNECTOR_POLL_HPD;
+ else
+ connector->polled = DRM_CONNECTOR_POLL_CONNECT |
+ DRM_CONNECTOR_POLL_DISCONNECT;
+
+ drm_connector_helper_add(connector, &cdns_dp_connector_helper_funcs);
+
+ ret = drm_connector_init(bridge->dev, connector, &cdns_dp_connector_funcs,
+ DRM_MODE_CONNECTOR_DisplayPort);
+ if (ret < 0) {
+ DRM_ERROR("Failed to initialize connector\n");
+ return ret;
+ }
+
+ drm_connector_attach_encoder(connector, encoder);
+
+ drm_connector_attach_content_protection_property(connector, true);
+
+ return 0;
+}
+
+static enum drm_mode_status
+cdns_dp_bridge_mode_valid(struct drm_bridge *bridge,
+ const struct drm_display_info *info,
+ const struct drm_display_mode *mode)
+{
+ enum drm_mode_status mode_status = MODE_OK;
+
+ /* We don't support double-clocked modes */
+ if (mode->flags & DRM_MODE_FLAG_DBLCLK ||
+ mode->flags & DRM_MODE_FLAG_INTERLACE)
+ return MODE_BAD;
+
+ /* MAX support pixel clock rate 594MHz */
+ if (mode->clock > 594000)
+ return MODE_CLOCK_HIGH;
+
+ /* 5120 x 2160 is the maximum supported resulution */
+ if (mode->hdisplay > 5120)
+ return MODE_BAD_HVALUE;
+
+ if (mode->vdisplay > 2160)
+ return MODE_BAD_VVALUE;
+
+ return mode_status;
+}
+
+static void cdns_dp_bridge_mode_set(struct drm_bridge *bridge,
+ const struct drm_display_mode *orig_mode,
+ const struct drm_display_mode *mode)
+{
+ struct cdns_mhdp_device *mhdp = bridge->driver_private;
+ struct drm_display_info *display_info = &mhdp->connector.base.display_info;
+ struct video_info *video = &mhdp->video_info;
+
+ switch (display_info->bpc) {
+ case 10:
+ video->color_depth = 10;
+ break;
+ case 6:
+ video->color_depth = 6;
+ break;
+ default:
+ video->color_depth = 8;
+ break;
+ }
+
+ video->color_fmt = PXL_RGB;
+ video->v_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NVSYNC);
+ video->h_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NHSYNC);
+
+ DRM_DEBUG_DRIVER("Mode: %dx%dp%d\n", mode->hdisplay, mode->vdisplay,
+ mode->clock);
+ memcpy(&mhdp->mode, mode, sizeof(struct drm_display_mode));
+
+ mutex_lock(&mhdp->lock);
+ cdns_dp_mode_set(mhdp);
+ mutex_unlock(&mhdp->lock);
+
+ /* reset force mode set flag */
+ mhdp->force_mode_set = false;
+}
+
+static void cdn_dp_bridge_enable(struct drm_bridge *bridge)
+{
+ struct cdns_mhdp_device *mhdp = bridge->driver_private;
+ struct drm_connector_state *conn_state = mhdp->connector.base.state;
+ int ret;
+
+ mutex_lock(&mhdp->lock);
+ /* Link trainning */
+ ret = cdns_mhdp_train_link(mhdp);
+ if (ret) {
+ DRM_DEV_ERROR(mhdp->dev, "Failed link train %d\n", ret);
+ mutex_unlock(&mhdp->lock);
+ return;
+ }
+ mutex_unlock(&mhdp->lock);
+
+ ret = cdns_mhdp_set_video_status(mhdp, CONTROL_VIDEO_VALID);
+ if (ret) {
+ DRM_DEV_ERROR(mhdp->dev, "Failed to valid video %d\n", ret);
+ return;
+ }
+
+ if (conn_state->content_protection == DRM_MODE_CONTENT_PROTECTION_DESIRED)
+ cdns_hdcp_enable(mhdp);
+
+}
+
+static void cdn_dp_bridge_disable(struct drm_bridge *bridge)
+{
+ struct cdns_mhdp_device *mhdp = bridge->driver_private;
+
+ cdns_hdcp_disable(mhdp);
+
+ cdns_mhdp_set_video_status(mhdp, CONTROL_VIDEO_IDLE);
+}
+
+static const struct drm_bridge_funcs cdns_dp_bridge_funcs = {
+ .attach = cdns_dp_bridge_attach,
+ .enable = cdn_dp_bridge_enable,
+ .disable = cdn_dp_bridge_disable,
+ .mode_set = cdns_dp_bridge_mode_set,
+ .mode_valid = cdns_dp_bridge_mode_valid,
+};
+
+static void hotplug_work_func(struct work_struct *work)
+{
+ struct cdns_mhdp_device *mhdp = container_of(work,
+ struct cdns_mhdp_device,
+ hotplug_work.work);
+ struct drm_connector *connector = &mhdp->connector.base;
+ enum drm_connector_status connector_sts;
+ int loop_cnt, retry;
+
+ DRM_DEBUG_DRIVER("Starting %s\n", __func__);
+
+ if (connector->status == connector_status_connected) {
+ /* Need to check if we had an IRQ event */
+ u8 hpd = 0xf;
+
+ mutex_lock(&mhdp->lock);
+ hpd = cdns_mhdp_read_hpd(mhdp);
+ mutex_unlock(&mhdp->lock);
+ DRM_DEBUG_DRIVER("cdns_mhdp_read_hpd = %d\n", hpd);
+ if (hpd == 3) {
+ /* Cable Connected and seen IRQ*/
+ DRM_DEBUG_DRIVER("HPD IRQ event seen\n");
+ cdns_dp_handle_hpd_irq(mhdp);
+ connector_sts = connector_status_connected;
+ } else if (hpd == 1)
+ connector_sts = connector_status_connected;
+ else if (hpd == 0)
+ connector_sts = connector_status_disconnected;
+ else
+ connector_sts = connector_status_unknown;
+
+ /* if was connected then there may have been unplug event,
+ * either wait 900us then call cdns_dp_connector_detect and if
+ * still connected then just ignore and finish or poll a
+ * certain number of times. Otherwise set status to unconnected
+ * and call the hotplug_event function.
+ */
+ for (loop_cnt = 0; loop_cnt < CDNS_DP_HPD_POLL_DWN_LOOP;
+ loop_cnt++) {
+ udelay(CDNS_DP_HPD_POLL_DWN_DLY_US);
+ if (loop_cnt > 0)
+ connector_sts =
+ cdns_dp_connector_detect(connector, false);
+ if (connector_sts != connector_status_connected) {
+ DRM_DEBUG_DRIVER("HDMI/DP Cable Plug Out\n");
+ break;
+ }
+ }
+ if (connector_sts == connector_status_connected) {
+ DRM_DEBUG_DRIVER("Finished %s - early\n", __func__);
+ return;
+ }
+ /* Disable HDCP when cable plugout,
+ * set content_protection to DESIRED, recovery HDCP state after cable plugin
+ */
+ if (connector->state->content_protection
+ != DRM_MODE_CONTENT_PROTECTION_UNDESIRED) {
+ connector->state->content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED;
+ mhdp->hdcp.state = HDCP_STATE_DISABLING;
+ }
+ DRM_DEBUG_DRIVER("Calling drm_kms_helper_hotplug_event\n");
+ /* Note that before we call the helper functions we need
+ * to force the cdns_dp_connector_detect function from
+ * returning a connected status since the DRM functions
+ * still end up calling that in a roundabout way when we
+ * signal a status change. We need to do this to ensure
+ * that a shutdown proper completes and don't end up in
+ * a strange state.
+ */
+ mhdp->force_disconnected_sts = true;
+ connector->status = connector_sts;
+ drm_kms_helper_hotplug_event(connector->dev);
+ /* There was a disconnection so give some time to allow
+ * things to clean up
+ */
+ DRM_DEBUG_DRIVER("Start sleep\n");
+ msleep(75);
+ DRM_DEBUG_DRIVER("End sleep\n");
+ mhdp->force_disconnected_sts = false;
+ } else {
+ /* Disconnected or unknown status */
+ /* if was disconnected possibly a connect event, call
+ * cdns_dp_connector_detect multiple times to validate before
+ * updating to connected status and calling the hotplug event
+ * function. This is needed since the HW/FW can take some time
+ * to validate.
+ */
+ for (loop_cnt = 0; loop_cnt < CDNS_DP_HPD_POLL_UP_LOOP;
+ loop_cnt++) {
+ msleep(CDNS_DP_HPD_POLL_UP_DLY_US / 1000);
+ connector_sts =
+ cdns_dp_connector_detect(connector, false);
+ if (connector_sts == connector_status_connected) {
+ DRM_DEBUG_DRIVER("HDMI/DP Cable Plug In\n");
+ /* Recovery HDCP state */
+ if (connector->state->content_protection
+ != DRM_MODE_CONTENT_PROTECTION_UNDESIRED)
+ mhdp->hdcp.state = HDCP_STATE_ENABLING;
+ break;
+ }
+ }
+ if (connector_sts == connector_status_connected) {
+ DRM_DEBUG_DRIVER("Calling drm_kms_helper_hotplug_event\n");
+ connector->status = connector_sts;
+ drm_kms_helper_hotplug_event(connector->dev);
+ }
+ }
+
+ /* check connection status one more time in case there had been a short
+ * disconnect that might have caused re-connect to be missed, if so
+ * schedule again.
+ */
+ retry = 1;
+ do {
+ connector_sts = cdns_dp_connector_detect(connector, false);
+ if (connector_sts != connector->status) {
+ DRM_DEBUG_DRIVER("Re-queuing work_func due to possible change\n");
+ queue_delayed_work(system_wq, &mhdp->hotplug_work,
+ usecs_to_jiffies(50));
+ break;
+ }
+ retry--;
+ } while (retry > 0);
+ DRM_DEBUG_DRIVER("Finished %s\n", __func__);
+}
+
+static irqreturn_t cdns_dp_irq_thread(int irq, void *data)
+{
+ struct cdns_mhdp_device *mhdp = data;
+
+ disable_irq_nosync(irq);
+
+ /* Need to handle the enable HERE */
+ if (irq == mhdp->irq[IRQ_IN]) {
+ DRM_DEBUG_DRIVER("HDMI/DP IRQ IN\n");
+ enable_irq(mhdp->irq[IRQ_OUT]);
+ } else if (irq == mhdp->irq[IRQ_OUT]) {
+ DRM_DEBUG_DRIVER("HDMI/DP IRQ OUT\n");
+ enable_irq(mhdp->irq[IRQ_IN]);
+ }
+
+ /* Queue job as long as not already in queue */
+ queue_delayed_work(system_wq, &mhdp->hotplug_work,
+ usecs_to_jiffies(5));
+
+ return IRQ_HANDLED;
+}
+
+static void cdns_dp_parse_dt(struct cdns_mhdp_device *mhdp)
+{
+ struct device_node *of_node = mhdp->dev->of_node;
+ int ret;
+
+ ret = of_property_read_u32(of_node, "lane-mapping",
+ &mhdp->lane_mapping);
+ if (ret) {
+ mhdp->lane_mapping = 0xc6;
+ dev_warn(mhdp->dev, "Failed to get lane_mapping - using default 0xc6\n");
+ }
+ dev_info(mhdp->dev, "lane-mapping 0x%02x\n", mhdp->lane_mapping);
+}
+
+static int __cdns_dp_probe(struct platform_device *pdev,
+ struct cdns_mhdp_device *mhdp)
+{
+ struct device *dev = &pdev->dev;
+ struct resource *iores = NULL;
+ int ret;
+
+ mutex_init(&mhdp->lock);
+ mutex_init(&mhdp->api_lock);
+ mutex_init(&mhdp->iolock);
+
+ INIT_DELAYED_WORK(&mhdp->hotplug_work, hotplug_work_func);
+
+ iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (iores) {
+ mhdp->regs_base = devm_ioremap(dev, iores->start,
+ resource_size(iores));
+ if (IS_ERR(mhdp->regs_base))
+ return -ENOMEM;
+ }
+
+ iores = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (iores) {
+ mhdp->regs_sec = devm_ioremap(dev, iores->start,
+ resource_size(iores));
+ if (IS_ERR(mhdp->regs_sec))
+ return -ENOMEM;
+ }
+
+ mhdp->is_hpd = true;
+ mhdp->is_dp = true;
+ mhdp->is_ls1028a = false;
+
+ mhdp->irq[IRQ_IN] = platform_get_irq_byname(pdev, "plug_in");
+ if (mhdp->irq[IRQ_IN] < 0) {
+ mhdp->is_hpd = false;
+ dev_info(dev, "No plug_in irq number\n");
+ }
+
+ mhdp->irq[IRQ_OUT] = platform_get_irq_byname(pdev, "plug_out");
+ if (mhdp->irq[IRQ_OUT] < 0) {
+ mhdp->is_hpd = false;
+ dev_info(dev, "No plug_out irq number\n");
+ }
+
+ cdns_dp_parse_dt(mhdp);
+
+ cnds_hdcp_create_device_files(mhdp);
+
+ if (of_device_is_compatible(dev->of_node, "cdn,ls1028a-dp"))
+ mhdp->is_ls1028a = true;
+
+ cdns_mhdp_plat_call(mhdp, power_on);
+
+ cdns_mhdp_plat_call(mhdp, firmware_init);
+
+ /* DP FW alive check */
+ ret = cdns_mhdp_check_alive(mhdp);
+ if (ret == false) {
+ DRM_ERROR("NO dp FW running\n");
+ return -ENXIO;
+ }
+
+ ret = cdns_hdcp_init(mhdp, pdev->dev.of_node);
+ if (ret < 0)
+ DRM_WARN("Failed to initialize HDCP\n");
+
+ /* DP PHY init before AUX init */
+ cdns_mhdp_plat_call(mhdp, phy_set);
+
+ /* Enable Hotplug Detect IRQ thread */
+ if (mhdp->is_hpd) {
+ irq_set_status_flags(mhdp->irq[IRQ_IN], IRQ_NOAUTOEN);
+ ret = devm_request_threaded_irq(dev, mhdp->irq[IRQ_IN],
+ NULL, cdns_dp_irq_thread,
+ IRQF_ONESHOT, dev_name(dev),
+ mhdp);
+
+ if (ret) {
+ dev_err(dev, "can't claim irq %d\n",
+ mhdp->irq[IRQ_IN]);
+ return -EINVAL;
+ }
+
+ irq_set_status_flags(mhdp->irq[IRQ_OUT], IRQ_NOAUTOEN);
+ ret = devm_request_threaded_irq(dev, mhdp->irq[IRQ_OUT],
+ NULL, cdns_dp_irq_thread,
+ IRQF_ONESHOT, dev_name(dev),
+ mhdp);
+
+ if (ret) {
+ dev_err(dev, "can't claim irq %d\n",
+ mhdp->irq[IRQ_OUT]);
+ return -EINVAL;
+ }
+
+ // Call to clear any latched values first...
+ cdns_mhdp_read_hpd(mhdp);
+ if (cdns_mhdp_read_hpd(mhdp))
+ enable_irq(mhdp->irq[IRQ_OUT]);
+ else
+ enable_irq(mhdp->irq[IRQ_IN]);
+ }
+
+ mhdp->bridge.base.driver_private = mhdp;
+ mhdp->bridge.base.funcs = &cdns_dp_bridge_funcs;
+#ifdef CONFIG_OF
+ mhdp->bridge.base.of_node = dev->of_node;
+#endif
+
+ dev_set_drvdata(dev, mhdp);
+
+ /* register audio driver */
+ cdns_mhdp_register_audio_driver(dev);
+
+ dp_aux_init(mhdp, dev);
+
+ return 0;
+}
+
+static void __cdns_dp_remove(struct cdns_mhdp_device *mhdp)
+{
+ dp_aux_destroy(mhdp);
+ cdns_mhdp_unregister_audio_driver(mhdp->dev);
+}
+
+/* -----------------------------------------------------------------------------
+ * Probe/remove API, used from platforms based on the DRM bridge API.
+ */
+int cdns_dp_probe(struct platform_device *pdev,
+ struct cdns_mhdp_device *mhdp)
+{
+ int ret;
+
+ ret = __cdns_dp_probe(pdev, mhdp);
+ if (ret)
+ return ret;
+
+ drm_bridge_add(&mhdp->bridge.base);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(cdns_dp_probe);
+
+void cdns_dp_remove(struct platform_device *pdev)
+{
+ struct cdns_mhdp_device *mhdp = platform_get_drvdata(pdev);
+
+ drm_bridge_remove(&mhdp->bridge.base);
+
+ __cdns_dp_remove(mhdp);
+}
+EXPORT_SYMBOL_GPL(cdns_dp_remove);
+
+/* -----------------------------------------------------------------------------
+ * Bind/unbind API, used from platforms based on the component framework.
+ */
+int cdns_dp_bind(struct platform_device *pdev, struct drm_encoder *encoder,
+ struct cdns_mhdp_device *mhdp)
+{
+ int ret;
+
+ ret = __cdns_dp_probe(pdev, mhdp);
+ if (ret < 0)
+ return ret;
+
+ ret = drm_bridge_attach(encoder, &mhdp->bridge.base, NULL, 0);
+ if (ret) {
+ cdns_dp_remove(pdev);
+ DRM_ERROR("Failed to initialize bridge with drm\n");
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(cdns_dp_bind);
+
+void cdns_dp_unbind(struct device *dev)
+{
+ struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev);
+
+ __cdns_dp_remove(mhdp);
+}
+EXPORT_SYMBOL_GPL(cdns_dp_unbind);
+
+MODULE_AUTHOR("Sandor Yu <sandor.yu@nxp.com>");
+MODULE_DESCRIPTION("Cadence Display Port transmitter driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:cdn-dp");
diff --git a/drivers/gpu/drm/bridge/cadence/cdns-hdcp-common.c b/drivers/gpu/drm/bridge/cadence/cdns-hdcp-common.c
new file mode 100644
index 000000000000..73dace56bac9
--- /dev/null
+++ b/drivers/gpu/drm/bridge/cadence/cdns-hdcp-common.c
@@ -0,0 +1,1323 @@
+/*
+ * Cadence HDMI/DP HDCP driver
+ *
+ * Copyright 2021 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+#include <drm/bridge/cdns-mhdp.h>
+#include <drm/drm_hdcp.h>
+#include <drm/drm_print.h>
+#include <linux/firmware.h>
+
+#include "cdns-mhdp-hdcp.h"
+
+/* Default will be to use KM unless it has been explicitly */
+#ifndef HDCP_USE_KMKEY
+ #define HDCP_USE_KMKEY 1
+#endif
+
+#define CDNS_HDCP_ACTIVATE (0x1 << 2)
+
+#define IMX_FW_TIMEOUT_MS (64 * 1000)
+#define IMX_HDCP_PAIRING_FIRMWARE "imx/hdcp-pairing.bin"
+
+#define GENERAL_BUS_SETTINGS_DPCD_BUS_BIT 0
+#define GENERAL_BUS_SETTINGS_DPCD_BUS_LOCK_BIT 1
+#define GENERAL_BUS_SETTINGS_HDCP_BUS_BIT 2
+#define GENERAL_BUS_SETTINGS_HDCP_BUS_LOCK_BIT 3
+#define GENERAL_BUS_SETTINGS_CAPB_OWNER_BIT 4
+#define GENERAL_BUS_SETTINGS_CAPB_OWNER_LOCK_BIT 5
+
+#define GENERAL_BUS_SETTINGS_RESP_DPCD_BUS_BIT 0
+#define GENERAL_BUS_SETTINGS_RESP_HDCP_BUS_BIT 1
+#define GENERAL_BUS_SETTINGS_RESP_CAPB_OWNER_BIT 2
+
+/* HDCP TX ports working mode (HDCP 2.2 or 1.4) */
+enum {
+ HDCP_TX_2, /* lock only with HDCP2 */
+ HDCP_TX_1, /* lock only with HDCP1 */
+ HDCP_TX_BOTH, /* lock on HDCP2 or 1 depend on other side */
+};
+
+/* HDCP TX ports stream type (relevant if receiver is repeater) */
+enum {
+ HDCP_CONTENT_TYPE_0, /* May be transmitted by
+ The HDCP Repeater to all HDCP Devices. */
+ HDCP_CONTENT_TYPE_1, /* Must not be transmitted by the HDCP Repeater to
+ HDCP 1.x-compliant Devices and HDCP 2.0-compliant Repeaters */
+};
+
+/* different error types for HDCP_TX_STATUS_CHANGE */
+enum {
+ HDCP_TRAN_ERR_NO_ERROR,
+ HDCP_TRAN_ERR_HPD_IS_DOWN,
+ HDCP_TRAN_ERR_SRM_FAILURE,
+ HDCP_TRAN_ERR_SIGNATURE_VERIFICATION,
+ HDCP_TRAN_ERR_H_TAG_DIFF_H,
+ HDCP_TRAN_ERR_V_TAG_DIFF_V,
+ HDCP_TRAN_ERR_LOCALITY_CHECK,
+ HDCP_TRAN_ERR_DDC,
+ HDCP_TRAN_ERR_REAUTH_REQ,
+ HDCP_TRAN_ERR_TOPOLOGY,
+ HDCP_TRAN_ERR_HDCP_RSVD1,
+ HDCP_TRAN_ERR_HDMI_CAPABILITY,
+ HDCP_TRAN_ERR_RI,
+ HDCP_TRAN_ERR_WATCHDOG_EXPIRED,
+};
+
+static char const *g_last_error[16] = {
+ "No Error",
+ "HPD is down",
+ "SRM failure",
+ "Signature verification error",
+ "h tag != h",
+ "V tag diff v",
+ "Locality check",
+ "DDC error",
+ "REAUTH_REQ",
+ "Topology error",
+ "Verify receiver ID list failed",
+ "HDCP_RSVD1 was not 0,0,0",
+ "HDMI capability or mode",
+ "RI result was different than expected",
+ "WatchDog expired",
+ "Repeater integrity failed"
+};
+
+#define HDCP_MAX_RECEIVERS 32
+#define HDCP_RECEIVER_ID_SIZE_BYTES 5
+#define HPD_EVENT 1
+#define HDCP_STATUS_SIZE 0x5
+#define HDCP_PORT_STS_AUTH 0x1
+#define HDCP_PORT_STS_REPEATER 0x2
+#define HDCP_PORT_STS_TYPE_MASK 0xc
+#define HDCP_PORT_STS_TYPE_SHIFT 0x2
+#define HDCP_PORT_STS_AUTH_STREAM_ID_SHIFT 0x4
+#define HDCP_PORT_STS_AUTH_STREAM_ID_MASK 0x10
+#define HDCP_PORT_STS_LAST_ERR_SHIFT 0x5
+#define HDCP_PORT_STS_LAST_ERR_MASK (0x0F << 5)
+#define GET_HDCP_PORT_STS_LAST_ERR(__sts__) \
+ (((__sts__) & HDCP_PORT_STS_LAST_ERR_MASK) >> \
+ HDCP_PORT_STS_LAST_ERR_SHIFT)
+#define HDCP_PORT_STS_1_1_FEATURES 0x200
+
+#define HDCP_CONFIG_NONE ((u8) 0)
+#define HDCP_CONFIG_1_4 ((u8) 1) /* use HDCP 1.4 only */
+#define HDCP_CONFIG_2_2 ((u8) 2) /* use HDCP 2.2 only */
+
+/* Default timeout to use for wait4event in milliseconds */
+#define HDCP_EVENT_TO_DEF 800
+/* Timeout value to use for repeater receiver ID check, spec says 3s */
+#define HDCP_EVENT_TO_RPT 3500
+
+static int cdns_hdcp_check_link(struct cdns_mhdp_device *mhdp);
+
+static void print_port_status(u16 sts)
+{
+ char const *rx_type[4] = { "Unknown", "HDCP 1", "HDCP 2", "Unknown" };
+
+ DRM_DEBUG_KMS("INFO: HDCP Port Status: 0x%04x\n", sts);
+ DRM_DEBUG_KMS(" Authenticated: %d\n", sts & HDCP_PORT_STS_AUTH);
+ DRM_DEBUG_KMS(" Receiver is repeater: %d\n", sts & HDCP_PORT_STS_REPEATER);
+ DRM_DEBUG_KMS(" RX Type: %s\n",
+ rx_type[(sts & HDCP_PORT_STS_TYPE_MASK) >> HDCP_PORT_STS_TYPE_SHIFT]);
+ DRM_DEBUG_KMS(" AuthStreamId: %d\n", sts & HDCP_PORT_STS_AUTH_STREAM_ID_MASK);
+ DRM_DEBUG_KMS(" Last Error: %s\n",
+ g_last_error[(sts & HDCP_PORT_STS_LAST_ERR_MASK) >> HDCP_PORT_STS_LAST_ERR_SHIFT]);
+ DRM_DEBUG_KMS(" Enable 1.1 Features: %d\n", sts & HDCP_PORT_STS_1_1_FEATURES);
+}
+
+static void print_events(u8 events)
+{
+ if (events & HDMI_TX_HPD_EVENT)
+ DRM_INFO("INFO: HDMI_TX_HPD_EVENT\n");
+ if (events & HDCPTX_STATUS_EVENT)
+ DRM_INFO("INFO: HDCPTX_STATUS_EVENT\n");
+ if (events & HDCPTX_IS_KM_STORED_EVENT)
+ DRM_INFO("INFO: HDCPTX_IS_KM_STORED_EVENT\n");
+ if (events & HDCPTX_STORE_KM_EVENT)
+ DRM_INFO("INFO: HDCPTX_STORE_KM_EVENT\n");
+ if (events & HDCPTX_IS_RECEIVER_ID_VALID_EVENT)
+ DRM_INFO("INFO: HDCPTX_IS_RECEIVER_ID_VALID_EVENT\n");
+}
+
+static u8 wait4event(struct cdns_mhdp_device *mhdp, u8 *events,
+ u32 event_to_wait, u32 timeout_ms)
+{
+ u8 reg_events;
+ u8 returned_events;
+ u8 event_mask = event_to_wait | HDCPTX_STATUS_EVENT;
+ unsigned timeout;
+
+ timeout = timeout_ms;
+ do {
+ if (timeout == 0)
+ goto timeout_err;
+ timeout--;
+ udelay(1000);
+ reg_events = cdns_mhdp_get_event(mhdp);
+ *events |= reg_events;
+ } while (((event_mask & *events) == 0) && (event_to_wait > HDMI_TX_HPD_EVENT));
+
+ returned_events = *events & event_mask;
+ if (*events != returned_events) {
+ u32 unexpected_events = ~event_mask & *events;
+
+ DRM_INFO("INFO: %s() all 0x%08x expected 0x%08x unexpected 0x%08x",
+ __func__, *events, returned_events, unexpected_events);
+ DRM_INFO("INFO: %s() All events:\n", __func__);
+ print_events(*events);
+
+ DRM_INFO("INFO: %s() expected events:\n", __func__);
+ print_events(returned_events);
+
+ DRM_INFO("INFO: %s() unexpected events:\n", __func__);
+ print_events(unexpected_events);
+ } else
+ print_events(*events);
+
+ *events &= ~event_mask;
+
+ return returned_events;
+
+timeout_err:
+ DRM_INFO("INFO: %s() Timed out with events:\n", __func__);
+ print_events(event_to_wait);
+ return 0;
+}
+
+static u16 cdns_hdcp_get_status(struct cdns_mhdp_device *mhdp)
+{
+ u8 hdcp_status[HDCP_STATUS_SIZE];
+ u16 hdcp_port_status;
+
+ cdns_mhdp_hdcp_tx_status_req(mhdp, hdcp_status, HDCP_STATUS_SIZE);
+ hdcp_port_status = (hdcp_status[0] << 8) | hdcp_status[1];
+
+ return hdcp_port_status;
+}
+
+static inline u8 check_event(u8 events, u8 tested)
+{
+ if ((events & tested) == 0)
+ return 0;
+ return 1;
+}
+
+/* Prints status. Returns error code (0 = no error) */
+static u8 cdns_hdcp_handle_status(u16 status)
+{
+ print_port_status(status);
+ if (status & HDCP_PORT_STS_LAST_ERR_MASK)
+ DRM_ERROR("ERROR: HDCP error was set to %s\n",
+ g_last_error[((status & HDCP_PORT_STS_LAST_ERR_MASK)
+ >> HDCP_PORT_STS_LAST_ERR_SHIFT)]);
+ return GET_HDCP_PORT_STS_LAST_ERR(status);
+}
+
+static int cdns_hdcp_set_config(struct cdns_mhdp_device *mhdp, u8 hdcp_config)
+{
+ u8 bus_config, retEvents;
+ u16 hdcp_port_status;
+ int ret;
+
+ /* Clearing out existing events */
+ wait4event(mhdp, &mhdp->hdcp.events, HDMI_TX_HPD_EVENT, HDCP_EVENT_TO_DEF);
+ mhdp->hdcp.events = 0;
+
+ if (!strncmp("imx8mq-hdmi", mhdp->plat_data->plat_name, 11)) {
+ DRM_DEBUG_KMS("INFO: Switching HDCP Commands to SAPB.\n");
+ bus_config = (1 << GENERAL_BUS_SETTINGS_HDCP_BUS_BIT);
+ ret = cdns_mhdp_apb_conf(mhdp, bus_config);
+ if (ret) {
+ DRM_ERROR("Failed to set APB configuration.\n");
+ if (ret & (1 << GENERAL_BUS_SETTINGS_RESP_HDCP_BUS_BIT))/* 1 - locked */
+ DRM_ERROR("Failed to switch HDCP to SAPB Mailbox\n");
+ return -1;
+ }
+ DRM_DEBUG_KMS("INFO: HDCP switched to SAPB\n");
+ }
+
+ /* HDCP 2.2(and/or 1.4) | activate | km-key | 0 */
+ hdcp_config |= CDNS_HDCP_ACTIVATE | (HDCP_USE_KMKEY << 4) | (HDCP_CONTENT_TYPE_0 << 3);
+
+ DRM_DEBUG_KMS("INFO: Enabling HDCP...\n");
+ ret = cdns_mhdp_hdcp_tx_config(mhdp, hdcp_config);
+ if (ret < 0)
+ DRM_DEBUG_KMS("cdns_mhdp_hdcp_tx_config failed\n");
+
+ /* Wait until HDCP_TX_STATUS EVENT appears */
+ DRM_DEBUG_KMS("INFO: wait4event -> HDCPTX_STATUS_EVENT\n");
+ retEvents = wait4event(mhdp, &mhdp->hdcp.events, HDCPTX_STATUS_EVENT, HDCP_EVENT_TO_DEF);
+
+ /* Set TX STATUS REQUEST */
+ DRM_DEBUG_KMS("INFO: Getting port status\n");
+ hdcp_port_status = cdns_hdcp_get_status(mhdp);
+ if (cdns_hdcp_handle_status(hdcp_port_status) != 0)
+ return -1;
+
+ return 0;
+}
+
+static int cdns_hdcp_auth_check(struct cdns_mhdp_device *mhdp)
+{
+ u16 hdcp_port_status;
+ int ret;
+
+ DRM_DEBUG_KMS("INFO: wait4event -> HDCPTX_STATUS_EVENT\n");
+ mhdp->hdcp.events = wait4event(mhdp, &mhdp->hdcp.events, HDCPTX_STATUS_EVENT, HDCP_EVENT_TO_DEF+HDCP_EVENT_TO_DEF);
+ if (mhdp->hdcp.events == 0)
+ return -1;
+
+ DRM_DEBUG_KMS("HDCP: HDCPTX_STATUS_EVENT\n");
+ hdcp_port_status = cdns_hdcp_get_status(mhdp);
+ ret = cdns_hdcp_handle_status(hdcp_port_status);
+ if (ret != 0) {
+ if (ret == HDCP_TRAN_ERR_REAUTH_REQ) {
+ DRM_ERROR("HDCP_TRAN_ERR_REAUTH_REQ-->one more try!\n");
+ return 1;
+ } else
+ return -1;
+ }
+
+ if (hdcp_port_status & HDCP_PORT_STS_AUTH) {
+ DRM_INFO("Authentication completed successfully!\n");
+ /* Dump hdmi and phy register */
+ mhdp->hdcp.state = HDCP_STATE_AUTHENTICATED;
+ mhdp->hdcp.value = DRM_MODE_CONTENT_PROTECTION_ENABLED;
+ schedule_work(&mhdp->hdcp.prop_work);
+ return 0;
+ }
+
+ DRM_WARN("Authentication failed\n");
+ mhdp->hdcp.state = HDCP_STATE_AUTH_FAILED;
+ return -1;
+}
+
+inline void cdns_hdcp_swap_id(u8 *in, u8 *out)
+{
+ int i;
+
+ for (i = 0; i < HDCP_RECEIVER_ID_SIZE_BYTES; i++)
+ out[HDCP_RECEIVER_ID_SIZE_BYTES - (i + 1)] = in[i];
+}
+
+inline void cdns_hdcp_swap_list(u8 *list_in, u8 *list_out, int num_ids)
+{
+ int i;
+
+ for (i = 0; i < num_ids; i++)
+ cdns_hdcp_swap_id(&list_in[i * HDCP_RECEIVER_ID_SIZE_BYTES],
+ &list_out[i * HDCP_RECEIVER_ID_SIZE_BYTES]);
+}
+
+static int cdns_hdcp_check_receviers(struct cdns_mhdp_device *mhdp)
+{
+ u8 ret_events;
+ u8 hdcp_num_rec, i;
+ u8 hdcp_rec_id[HDCP_MAX_RECEIVERS][HDCP_RECEIVER_ID_SIZE_BYTES];
+ u8 hdcp_rec_id_temp[HDCP_MAX_RECEIVERS][HDCP_RECEIVER_ID_SIZE_BYTES];
+ u16 hdcp_port_status = 0;
+ int ret;
+
+ DRM_INFO("INFO: Waiting for Receiver ID valid event\n");
+ ret_events = 0;
+ do {
+ u8 events = 0;
+ u8 hdcp_last_error = 0;
+ events = check_event(ret_events,
+ HDCPTX_IS_RECEIVER_ID_VALID_EVENT);
+ DRM_DEBUG_KMS("INFO: Waiting HDCPTX_IS_RECEIVER_ID_VALID_EVENT\n");
+ ret_events = wait4event(mhdp, &mhdp->hdcp.events,
+ HDCPTX_IS_RECEIVER_ID_VALID_EVENT,
+ (mhdp->hdcp.sink_is_repeater ?
+ HDCP_EVENT_TO_RPT : HDCP_EVENT_TO_DEF));
+ if (ret_events == 0) {
+ /* time out occurred, return error */
+ DRM_ERROR("HDCP error did not get receiver IDs\n");
+ return -1;
+ }
+ if (check_event(ret_events, HDCPTX_STATUS_EVENT) != 0) {
+ /* There was a status update, could be due to HPD
+ going down or some other error, check if an error
+ was set, if so exit.
+ */
+ hdcp_port_status = cdns_hdcp_get_status(mhdp);
+ hdcp_last_error = GET_HDCP_PORT_STS_LAST_ERR(hdcp_port_status);
+ if (cdns_hdcp_handle_status(hdcp_port_status)) {
+ DRM_ERROR("HDCP error no: %u\n", hdcp_last_error);
+ return -1;
+ } else {
+ /* No error logged, keep going.
+ * If this somehow happened at same time, then need to
+ * put the HDCPTX_STATUS_EVENT back into the global
+ * events pool and checked later. */
+ mhdp->hdcp.events |= HDCPTX_STATUS_EVENT;
+
+ /* Special condition when connected to HDCP 1.4 repeater
+ * with no downstream devices attached, then will not
+ * get receiver ID list but instead will reach
+ * authenticated state. */
+ if ((mhdp->hdcp.hdcp_version == HDCP_TX_1) && (mhdp->hdcp.sink_is_repeater == 1) &&
+ ((hdcp_port_status & HDCP_PORT_STS_AUTH) == HDCP_PORT_STS_AUTH)) {
+ DRM_INFO("Connected to HDCP 1.4 repeater with no downstream devices!\n");
+ return 0;
+ }
+
+ msleep(20);
+ }
+ }
+ } while (check_event(ret_events,
+ HDCPTX_IS_RECEIVER_ID_VALID_EVENT) == 0);
+
+ DRM_INFO("INFO: Requesting Receivers ID's\n");
+
+ hdcp_num_rec = 0;
+ memset(&hdcp_rec_id, 0, sizeof(hdcp_rec_id));
+
+ ret = cdns_mhdp_hdcp_tx_is_receiver_id_valid(mhdp, (u8 *)hdcp_rec_id, &hdcp_num_rec);
+ if (ret) {
+ DRM_DEV_ERROR(mhdp->dev, "Failed to hdcp tx receiver ID.\n");
+ return -1;
+ }
+
+ if (hdcp_num_rec == 0) {
+ DRM_DEBUG_KMS("WARN: Failed to get receiver list\n");
+ /* Unknown problem, return error */
+ return -1;
+ }
+
+ DRM_INFO("INFO: Number of Receivers: %d\n", hdcp_num_rec);
+
+ for (i = 0; i < hdcp_num_rec; ++i) {
+ DRM_INFO("\tReceiver ID%2d: %.2X%.2X%.2X%.2X%.2X\n",
+ i,
+ hdcp_rec_id[i][0],
+ hdcp_rec_id[i][1],
+ hdcp_rec_id[i][2],
+ hdcp_rec_id[i][3],
+ hdcp_rec_id[i][4]
+ );
+ }
+
+ /* swap ids byte order */
+ cdns_hdcp_swap_list(&hdcp_rec_id[0][0],
+ &hdcp_rec_id_temp[0][0], hdcp_num_rec);
+
+ /* Check Receiver ID's against revocation list in SRM */
+ if (drm_hdcp_check_ksvs_revoked(mhdp->drm_dev, (u8 *)hdcp_rec_id_temp, hdcp_num_rec)) {
+ mhdp->hdcp.state = HDCP_STATE_AUTH_FAILED;
+ DRM_ERROR("INFO: Receiver check fails\n");
+ return -1;
+ }
+
+ ret = cdns_mhdp_hdcp_tx_respond_receiver_id_valid(mhdp, 1);
+ DRM_INFO("INFO: Responding with Receiver ID's OK!, ret=%d\n", ret);
+ return ret;
+}
+
+#ifdef STORE_PAIRING
+static int cdns_hdcp_get_stored_pairing(struct cdns_mhdp_device *mhdp)
+{
+ int ret = 0;
+ unsigned long timeout = jiffies + msecs_to_jiffies(IMX_FW_TIMEOUT_MS);
+ unsigned long sleep = 1000;
+ const struct firmware *fw;
+
+ DRM_DEBUG_KMS("%s()\n", __func__);
+
+ while (time_before(jiffies, timeout)) {
+ ret = request_firmware(&fw, cdns_hdcp_PAIRING_FIRMWARE, mhdp->dev);
+ if (ret == -ENOENT) {
+ msleep(sleep);
+ sleep *= 2;
+ continue;
+ } else if (ret) {
+ DRM_DEV_INFO(mhdp->dev, "HDCP pairing data not found\n");
+ goto out;
+ }
+
+ mhdp->hdcp.num_paired = fw->size /
+ sizeof(struct hdcp_trans_pairing_data);
+ if (mhdp->hdcp.num_paired > MAX_STORED_KM) {
+ /* todo: handle dropping */
+ mhdp->hdcp.num_paired = MAX_STORED_KM;
+ DRM_DEV_INFO(mhdp->dev,
+ "too many paired receivers - dropping older entries\n");
+ }
+ memcpy(&mhdp->hdcp.pairing[0], fw->data,
+ sizeof(struct hdcp_trans_pairing_data) * mhdp->hdcp.num_paired);
+ release_firmware(fw);
+ goto out;
+ }
+
+ DRM_DEV_ERROR(mhdp->dev, "Timed out trying to load firmware\n");
+ ret = -ETIMEDOUT;
+ out:
+ return ret;
+}
+#endif
+
+static int cdns_hdcp_find_km_store(struct cdns_mhdp_device *mhdp,
+ u8 receiver[HDCP_PAIRING_R_ID])
+{
+ int i;
+
+ DRM_DEBUG_KMS("%s()\n", __func__);
+ for (i = 0; i < mhdp->hdcp.num_paired; i++) {
+ if (memcmp(receiver, mhdp->hdcp.pairing[i].receiver_id,
+ HDCP_PAIRING_R_ID) == 0) {
+ DRM_INFO("HDCP: found receiver id: 0x%x%x%x%x%x\n",
+ receiver[0], receiver[1], receiver[2], receiver[3], receiver[4]);
+ return i;
+ }
+ }
+ DRM_INFO("HDCP: receiver id: 0x%x%x%x%x%x not stored\n",
+ receiver[0], receiver[1], receiver[2], receiver[3], receiver[4]);
+ return -1;
+}
+
+static int cdns_hdcp_store_km(struct cdns_mhdp_device *mhdp,
+ struct hdcp_trans_pairing_data *pairing,
+ int stored_km_index)
+{
+ int i, temp_index;
+ struct hdcp_trans_pairing_data temp_pairing;
+
+ DRM_DEBUG_KMS("%s()\n", __func__);
+
+ if (stored_km_index < 0) {
+ /* drop one entry if array is full */
+ if (mhdp->hdcp.num_paired == MAX_STORED_KM)
+ mhdp->hdcp.num_paired--;
+
+ temp_index = mhdp->hdcp.num_paired;
+ mhdp->hdcp.num_paired++;
+ if (!pairing) {
+ DRM_ERROR("NULL HDCP pairing data!\n");
+ return -1;
+ } else
+ /* save the new stored km */
+ temp_pairing = *pairing;
+ } else {
+ /* save the current stored km */
+ temp_index = stored_km_index;
+ temp_pairing = mhdp->hdcp.pairing[stored_km_index];
+ }
+
+ /* move entries one slot to the end */
+ for (i = temp_index; i > 0; i--)
+ mhdp->hdcp.pairing[i] = mhdp->hdcp.pairing[i - 1];
+
+ /* save the current/new entry at the beginning */
+ mhdp->hdcp.pairing[0] = temp_pairing;
+
+ return 0;
+}
+
+static inline int cdns_hdcp_auth_22(struct cdns_mhdp_device *mhdp)
+{
+ int km_idx = -1;
+ u8 retEvents;
+ u16 hdcp_port_status;
+ u8 resp[HDCP_STATUS_SIZE];
+ struct hdcp_trans_pairing_data pairing;
+ int ret;
+
+ DRM_DEBUG_KMS("HDCP: Start 2.2 Authentication\n");
+ mhdp->hdcp.sink_is_repeater = 0;
+
+ /* Wait until HDCP2_TX_IS_KM_STORED EVENT appears */
+ retEvents = 0;
+ DRM_DEBUG_KMS("INFO: Wait until HDCP2_TX_IS_KM_STORED EVENT appears\n");
+ while (check_event(retEvents, HDCPTX_IS_KM_STORED_EVENT) == 0) {
+ DRM_DEBUG_KMS("INFO: Waiting FOR _IS_KM_STORED EVENT\n");
+ retEvents = wait4event(mhdp, &mhdp->hdcp.events,
+ HDCPTX_IS_KM_STORED_EVENT, HDCP_EVENT_TO_DEF);
+ if (retEvents == 0)
+ /* time out occurred, return error */
+ return -1;
+ if (check_event(retEvents, HDCPTX_STATUS_EVENT) != 0) {
+ /* There was a status update, could be due to HPD
+ going down or some other error, check if an error
+ was set, if so exit.
+ */
+ hdcp_port_status = cdns_hdcp_get_status(mhdp);
+ if (cdns_hdcp_handle_status(hdcp_port_status) != 0)
+ return -1;
+ }
+ }
+
+ DRM_DEBUG_KMS("HDCP: HDCPTX_IS_KM_STORED_EVENT\n");
+
+ /* Set HDCP2 TX KM STORED REQUEST */
+ ret = cdns_mhdp_hdcp2_tx_is_km_stored_req(mhdp, resp, HDCP_STATUS_SIZE);
+ if (ret) {
+ DRM_DEV_ERROR(mhdp->dev, "Failed to hdcp2 tx km stored.\n");
+ return -1;
+ }
+
+ DRM_DEBUG_KMS("HDCP: CDN_API_HDCP2_TX_IS_KM_STORED_REQ_blocking\n");
+ DRM_DEBUG_KMS("HDCP: Receiver ID: 0x%x%x%x%x%x\n",
+ resp[0], resp[1], resp[2], resp[3], resp[4]);
+
+ km_idx = cdns_hdcp_find_km_store(mhdp, resp);
+
+ /* Check if KM is stored */
+ if (km_idx >= 0) {
+ DRM_DEBUG_KMS("INFO: KM is stored\n");
+ /* Set HDCP2 TX RESPOND KM with stored KM */
+ ret = cdns_mhdp_hdcp2_tx_respond_km(mhdp, (u8 *)&mhdp->hdcp.pairing[km_idx],
+ sizeof(struct hdcp_trans_pairing_data));
+
+ DRM_DEBUG_KMS("HDCP: CDN_API_HDCP2_TX_RESPOND_KM_blocking, ret=%d\n", ret);
+ } else { /* KM is not stored */
+ /* Set HDCP2 TX RESPOND KM with empty data */
+ ret = cdns_mhdp_hdcp2_tx_respond_km(mhdp, NULL, 0);
+ DRM_DEBUG_KMS("INFO: KM is not stored ret=%d\n", ret);
+ }
+
+ if (cdns_hdcp_check_receviers(mhdp))
+ return -1;
+
+ /* Check if KM is not stored */
+ if (km_idx < 0) {
+ int loop_cnt = 0;
+
+ /* Wait until HDCP2_TX_STORE_KM EVENT appears */
+ retEvents = 0;
+ DRM_DEBUG_KMS("INFO: wait4event -> HDCPTX_STORE_KM_EVENT\n");
+ while (check_event(retEvents, HDCPTX_STORE_KM_EVENT) == 0) {
+ retEvents = wait4event(mhdp, &mhdp->hdcp.events,
+ HDCPTX_STORE_KM_EVENT, HDCP_EVENT_TO_DEF);
+ if (check_event(retEvents, HDCPTX_STATUS_EVENT)
+ != 0) {
+ hdcp_port_status = cdns_hdcp_get_status(mhdp);
+ if (cdns_hdcp_handle_status(hdcp_port_status)
+ != 0)
+ return -1;
+ }
+ if (loop_cnt > 2) {
+ DRM_ERROR("Did not get event HDCPTX_STORE_KM_EVENT in time\n");
+ return -1;
+ } else
+ loop_cnt++;
+ }
+ DRM_DEBUG_KMS("HDCP: HDCPTX_STORE_KM_EVENT\n");
+
+ /* Set HDCP2_TX_STORE_KM REQUEST */
+ ret = cdns_mhdp_hdcp2_tx_store_km(mhdp, (u8 *)&pairing, sizeof(struct hdcp_trans_pairing_data));
+ DRM_DEBUG_KMS("HDCP: CDN_API_HDCP2_TX_STORE_KM_REQ_blocking ret=%d\n", ret);
+ cdns_hdcp_store_km(mhdp, &pairing, km_idx);
+ } else
+ cdns_hdcp_store_km(mhdp, NULL, km_idx);
+
+ /* Check if device was a repeater */
+ hdcp_port_status = cdns_hdcp_get_status(mhdp);
+
+ /* Exit if there was any errors logged at this point... */
+ if (GET_HDCP_PORT_STS_LAST_ERR(hdcp_port_status) > 0) {
+ cdns_hdcp_handle_status(hdcp_port_status);
+ return -1;
+ }
+
+ if (hdcp_port_status & HDCP_PORT_STS_REPEATER)
+ mhdp->hdcp.sink_is_repeater = 1;
+
+ /* If sink was a repeater, we will be getting additional IDs to validate...
+ * Note that this one may take some time since spec allows up to 3s... */
+ if (mhdp->hdcp.sink_is_repeater)
+ if (cdns_hdcp_check_receviers(mhdp))
+ return -1;
+
+ /* Slight delay to allow firmware to finish setting up authenticated state */
+ msleep(300);
+
+ DRM_INFO("Finished cdns_hdcp_auth_22\n");
+ return 0;
+}
+
+static inline int cdns_hdcp_auth_14(struct cdns_mhdp_device *mhdp)
+{
+ u16 hdcp_port_status;
+ int ret = 0;
+
+ DRM_DEBUG_KMS("HDCP: Starting 1.4 Authentication\n");
+ mhdp->hdcp.sink_is_repeater = 0;
+
+ ret = cdns_hdcp_check_receviers(mhdp);
+ if (ret)
+ return -1;
+
+ /* Check if device was a repeater */
+ hdcp_port_status = cdns_hdcp_get_status(mhdp);
+
+ /* Exit if there was any errors logged at this point... */
+ if (GET_HDCP_PORT_STS_LAST_ERR(hdcp_port_status) > 0) {
+ cdns_hdcp_handle_status(hdcp_port_status);
+ return -1;
+ }
+
+ if (hdcp_port_status & HDCP_PORT_STS_REPEATER) {
+ DRM_INFO("Connected to a repeater\n");
+ mhdp->hdcp.sink_is_repeater = 1;
+ } else
+ DRM_INFO("Connected to a normal sink\n");
+
+ /* If sink was a repeater, we will be getting additional IDs to validate...
+ * Note that this one may take some time since spec allows up to 3s... */
+ if (mhdp->hdcp.sink_is_repeater)
+ ret = cdns_hdcp_check_receviers(mhdp);
+
+ /* Slight delay to allow firmware to finish setting up authenticated state */
+ msleep(300);
+
+ return ret;
+}
+
+static int cdns_hdcp_auth(struct cdns_mhdp_device *mhdp, u8 hdcp_config)
+{
+ int ret = 0;
+
+ DRM_DEBUG_KMS("HDCP: Start Authentication\n");
+
+ if (mhdp->hdcp.reauth_in_progress == 0) {
+ ret = cdns_hdcp_set_config(mhdp, hdcp_config);
+ if (ret) {
+ DRM_ERROR("cdns_hdcp_set_config failed\n");
+ return -1;
+ }
+ }
+
+ mhdp->hdcp.reauth_in_progress = 0;
+ mhdp->hdcp.sink_is_repeater = 0;
+ mhdp->hdcp.hdcp_version = hdcp_config;
+
+ do {
+ if (mhdp->hdcp.cancel == 1) {
+ DRM_ERROR("mhdp->hdcp.cancel is TRUE\n");
+ return -ECANCELED;
+ }
+
+ if (hdcp_config == HDCP_TX_1)
+ ret = cdns_hdcp_auth_14(mhdp);
+ else
+ ret = cdns_hdcp_auth_22(mhdp);
+ if (ret) {
+ u16 hdcp_port_status;
+ DRM_ERROR("cdns_hdcp_auth_%s failed\n",
+ (hdcp_config == HDCP_TX_1) ? "14" : "22");
+ hdcp_port_status = cdns_hdcp_get_status(mhdp);
+ cdns_hdcp_handle_status(hdcp_port_status);
+ return -1;
+ }
+
+ ret = cdns_hdcp_auth_check(mhdp);
+ } while (ret == 1);
+
+ return ret;
+}
+
+static int _cdns_hdcp_disable(struct cdns_mhdp_device *mhdp)
+{
+ int ret = 0;
+ u8 hdcp_cfg = (HDCP_USE_KMKEY << 4);
+
+ DRM_DEBUG_KMS("[%s:%d] HDCP is being disabled...\n",
+ mhdp->connector.base.name, mhdp->connector.base.base.id);
+ DRM_DEBUG_KMS("INFO: Disabling HDCP...\n");
+
+ ret = cdns_mhdp_hdcp_tx_config(mhdp, hdcp_cfg);
+ if (ret < 0)
+ DRM_DEBUG_KMS("cdns_mhdp_hdcp_tx_config failed\n");
+
+ DRM_DEBUG_KMS("HDCP is disabled\n");
+
+ mhdp->hdcp.events = 0;
+
+ return ret;
+}
+
+static int _cdns_hdcp_enable(struct cdns_mhdp_device *mhdp)
+{
+ int i, ret = 0, tries = 9, tries14 = 50;
+ u8 hpd_sts;
+
+ hpd_sts = cdns_mhdp_read_hpd(mhdp);
+ if (hpd_sts == 0) {
+ dev_info(mhdp->dev, "%s HDP detected low, set state to DISABLING\n", __func__);
+ mhdp->hdcp.state = HDCP_STATE_DISABLING;
+ return -1;
+ }
+
+ DRM_DEBUG_KMS("[%s:%d] HDCP is being enabled...\n",
+ mhdp->connector.base.name, mhdp->connector.base.base.id);
+
+ mhdp->hdcp.events = 0;
+
+ /* Incase of authentication failures, HDCP spec expects reauth. */
+ /* TBD should this actually try 2.2 n times then 1.4? */
+ for (i = 0; i < tries; i++) {
+ if (mhdp->hdcp.config & HDCP_CONFIG_2_2) {
+ ret = cdns_hdcp_auth(mhdp, HDCP_TX_2);
+ if (ret == 0)
+ return 0;
+ else if (ret == -ECANCELED)
+ return ret;
+ _cdns_hdcp_disable(mhdp);
+ }
+ }
+
+ for (i = 0; i < tries14; i++) {
+ if (mhdp->hdcp.config & HDCP_CONFIG_1_4) {
+ ret = cdns_hdcp_auth(mhdp, HDCP_TX_1);
+ if (ret == 0)
+ return 0;
+ else if (ret == -ECANCELED)
+ return ret;
+ _cdns_hdcp_disable(mhdp);
+ }
+ DRM_DEBUG_KMS("HDCP Auth failure (%d)\n", ret);
+ }
+
+ DRM_ERROR("HDCP authentication failed (%d tries/%d)\n", tries, ret);
+ return ret;
+}
+
+static void cdns_hdcp_check_work(struct work_struct *work)
+{
+ struct cdns_mhdp_hdcp *hdcp = container_of(work,
+ struct cdns_mhdp_hdcp, check_work.work);
+ struct cdns_mhdp_device *mhdp = container_of(hdcp,
+ struct cdns_mhdp_device, hdcp);
+
+ /* todo: maybe we don't need to always schedule */
+ cdns_hdcp_check_link(mhdp);
+ schedule_delayed_work(&hdcp->check_work, 50);
+}
+
+static void cdns_hdcp_prop_work(struct work_struct *work)
+{
+ struct cdns_mhdp_hdcp *hdcp = container_of(work,
+ struct cdns_mhdp_hdcp, prop_work);
+ struct cdns_mhdp_device *mhdp = container_of(hdcp,
+ struct cdns_mhdp_device, hdcp);
+
+ struct drm_device *dev = mhdp->drm_dev;
+
+ drm_modeset_lock(&dev->mode_config.connection_mutex, NULL);
+ mutex_lock(&mhdp->hdcp.mutex);
+
+ /*
+ * This worker is only used to flip between ENABLED/DESIRED. Either of
+ * those to UNDESIRED is handled by core. If hdcp_value == UNDESIRED,
+ * we're running just after hdcp has been disabled, so just exit
+ */
+ if (mhdp->hdcp.value != DRM_MODE_CONTENT_PROTECTION_UNDESIRED) {
+ drm_hdcp_update_content_protection(&mhdp->connector.base,
+ mhdp->hdcp.value);
+ }
+
+ mutex_unlock(&mhdp->hdcp.mutex);
+ drm_modeset_unlock(&dev->mode_config.connection_mutex);
+}
+
+static void show_hdcp_supported(struct cdns_mhdp_device *mhdp)
+{
+ if ((mhdp->hdcp.config & (HDCP_CONFIG_1_4 | HDCP_CONFIG_2_2)) ==
+ (HDCP_CONFIG_1_4 | HDCP_CONFIG_2_2))
+ DRM_INFO("Both HDCP 1.4 and 2.2 are enabled\n");
+ else if (mhdp->hdcp.config & HDCP_CONFIG_1_4)
+ DRM_INFO("Only HDCP 1.4 is enabled\n");
+ else if (mhdp->hdcp.config & HDCP_CONFIG_2_2)
+ DRM_INFO("Only HDCP 2.2 is enabled\n");
+ else
+ DRM_INFO("HDCP is disabled\n");
+}
+
+static ssize_t HDCPTX_do_reauth_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count);
+static struct device_attribute HDCPTX_do_reauth = __ATTR_WO(HDCPTX_do_reauth);
+
+static ssize_t HDCPTX_do_reauth_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ int ret;
+ struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev);
+
+ ret = cdns_mhdp_hdcp_tx_reauth(mhdp, 1);
+ if (ret < 0) {
+ dev_err(dev, "%s cdns_mhdp_hdcp_tx_reauth failed\n", __func__);
+ return -1;
+ }
+
+ return count;
+}
+
+static ssize_t HDCPTX_Version_show(struct device *dev,
+ struct device_attribute *attr, char *buf);
+static ssize_t HDCPTX_Version_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count);
+static struct device_attribute HDCPTX_Version = __ATTR_RW(HDCPTX_Version);
+
+static ssize_t HDCPTX_Version_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev);
+ int value, ret;
+
+ ret = kstrtoint(buf, 10, &value);
+ if (ret != 0)
+ return -EINVAL;
+
+ if (value == 2)
+ mhdp->hdcp.config = 2;
+ else if (value == 1)
+ mhdp->hdcp.config = 1;
+ else if (value == 3)
+ mhdp->hdcp.config = 3;
+ else
+ mhdp->hdcp.config = 0;
+
+ return count;
+}
+
+ssize_t HDCPTX_Version_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%d\n", mhdp->hdcp.config);
+}
+
+static ssize_t HDCPTX_Status_show(struct device *dev,
+ struct device_attribute *attr, char *buf);
+static ssize_t HDCPTX_Status_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count);
+static struct device_attribute HDCPTX_Status = __ATTR_RW(HDCPTX_Status);
+
+ssize_t HDCPTX_Status_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev);
+
+ switch (mhdp->hdcp.state) {
+ case HDCP_STATE_NO_AKSV:
+ return sprintf(buf, "%d :HDCP_STATE_NO_AKSV\n", mhdp->hdcp.state);
+ case HDCP_STATE_INACTIVE:
+ return sprintf(buf, "%d :HDCP_STATE_INACTIVE\n", mhdp->hdcp.state);
+ case HDCP_STATE_ENABLING:
+ return sprintf(buf, "%d :HDCP_STATE_ENABLING\n", mhdp->hdcp.state);
+ case HDCP_STATE_AUTHENTICATING:
+ return sprintf(buf, "%d :HDCP_STATE_AUTHENTICATING\n", mhdp->hdcp.state);
+ case HDCP_STATE_AUTHENTICATED:
+ return sprintf(buf, "%d :HDCP_STATE_AUTHENTICATED\n", mhdp->hdcp.state);
+ case HDCP_STATE_DISABLING:
+ return sprintf(buf, "%d :HDCP_STATE_DISABLING\n", mhdp->hdcp.state);
+ case HDCP_STATE_AUTH_FAILED:
+ return sprintf(buf, "%d :HDCP_STATE_AUTH_FAILED\n", mhdp->hdcp.state);
+ default:
+ return sprintf(buf, "%d :HDCP_STATE don't exist\n", mhdp->hdcp.state);
+ }
+}
+
+ssize_t HDCPTX_Status_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev);
+ int value, ret;
+
+ if (count == 2) {
+ ret = kstrtoint(buf, 10, &value);
+ if (ret != 0)
+ return -EINVAL;
+
+ if ((value >= HDCP_STATE_NO_AKSV) && (value <= HDCP_STATE_AUTH_FAILED)) {
+ mhdp->hdcp.state = value;
+ return count;
+ }
+ dev_err(dev, "%s &hdp->state invalid\n", __func__);
+ return -1;
+ }
+
+ dev_info(dev, "%s &hdp->state desired %s count=%d\n ", __func__, buf, (int)count);
+
+ if (strncmp(buf, "HDCP_STATE_NO_AKSV", count - 1) == 0)
+ mhdp->hdcp.state = HDCP_STATE_NO_AKSV;
+ else if (strncmp(buf, "HDCP_STATE_INACTIVE", count - 1) == 0)
+ mhdp->hdcp.state = HDCP_STATE_INACTIVE;
+ else if (strncmp(buf, "HDCP_STATE_ENABLING", count - 1) == 0)
+ mhdp->hdcp.state = HDCP_STATE_ENABLING;
+ else if (strncmp(buf, "HDCP_STATE_AUTHENTICATING", count - 1) == 0)
+ mhdp->hdcp.state = HDCP_STATE_AUTHENTICATING;
+ else if (strncmp(buf, "HDCP_STATE_AUTHENTICATED", count - 1) == 0)
+ mhdp->hdcp.state = HDCP_STATE_AUTHENTICATED;
+ else if (strncmp(buf, "HDCP_STATE_DISABLING", count - 1) == 0)
+ mhdp->hdcp.state = HDCP_STATE_DISABLING;
+ else if (strncmp(buf, "HDCP_STATE_AUTH_FAILED", count - 1) == 0)
+ mhdp->hdcp.state = HDCP_STATE_AUTH_FAILED;
+ else
+ dev_err(dev, "%s &hdp->state invalid\n", __func__);
+ return -1;
+}
+
+void cnds_hdcp_create_device_files(struct cdns_mhdp_device *mhdp)
+{
+
+ if (device_create_file(mhdp->dev, &HDCPTX_do_reauth)) {
+ DRM_ERROR("Unable to create HDCPTX_do_reauth sysfs\n");
+ device_remove_file(mhdp->dev, &HDCPTX_do_reauth);
+ }
+
+ if (device_create_file(mhdp->dev, &HDCPTX_Version)) {
+ DRM_ERROR("Unable to create HDCPTX_Version sysfs\n");
+ device_remove_file(mhdp->dev, &HDCPTX_Version);
+ }
+
+ if (device_create_file(mhdp->dev, &HDCPTX_Status)) {
+ DRM_ERROR(KERN_ERR "Unable to create HDCPTX_Status sysfs\n");
+ device_remove_file(mhdp->dev, &HDCPTX_Status);
+ }
+}
+EXPORT_SYMBOL(cnds_hdcp_create_device_files);
+
+#ifdef DEBUG
+void cdns_hdcp_show_pairing(struct cdns_mhdp_device *mhdp, struct hdcp_trans_pairing_data *p)
+{
+ char s[80];
+ int i, k;
+
+ DRM_INFO("Reveiver ID: %.2X%.2X%.2X%.2X%.2X\n",
+ p->receiver_id[0],
+ p->receiver_id[1],
+ p->receiver_id[2],
+ p->receiver_id[3],
+ p->receiver_id[4]);
+ for (k = 0, i = 0; k < 16; k++)
+ i += snprintf(&s[i], sizeof(s), "%02x", p->m[k]);
+
+ DRM_INFO("\tm: %s\n", s);
+
+ for (k = 0, i = 0; k < 16; k++)
+ i += snprintf(&s[i], sizeof(s), "%02x", p->km[k]);
+
+ DRM_INFO("\tkm: %s\n", s);
+
+ for (k = 0, i = 0; k < 16; k++)
+ i += snprintf(&s[i], sizeof(s), "%02x", p->ekh[k]);
+
+ DRM_INFO("\tekh: %s\n", s);
+}
+#endif
+
+static int cdns_hdcp_dump_pairing(struct seq_file *s, void *data)
+{
+ struct cdns_mhdp_device *mhdp = data;
+#ifdef DEBUG
+ int i;
+ for (i = 0; i < mhdp->hdcp.num_paired; i++)
+ cdns_hdcp_show_pairing(mhdp, &mhdp->hdcp.pairing[i]);
+#endif
+ return seq_write(s, &mhdp->hdcp.pairing[0],
+ mhdp->hdcp.num_paired * sizeof(struct hdcp_trans_pairing_data));
+}
+
+static int cdns_hdcp_pairing_show(struct seq_file *s, void *data)
+{
+ return cdns_hdcp_dump_pairing(s, s->private);
+}
+
+static int cdns_hdcp_dump_pairing_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, cdns_hdcp_pairing_show, inode->i_private);
+}
+
+static const struct file_operations cdns_hdcp_dump_fops = {
+ .open = cdns_hdcp_dump_pairing_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static void cdns_hdcp_debugfs_init(struct cdns_mhdp_device *mhdp)
+{
+ struct dentry *d, *root;
+
+ root = debugfs_create_dir("imx-hdcp", NULL);
+ if (IS_ERR(root) || !root)
+ goto err;
+
+ d = debugfs_create_file("dump_pairing", 0444, root, mhdp,
+ &cdns_hdcp_dump_fops);
+ if (!d)
+ goto err;
+ return;
+
+err:
+ dev_err(mhdp->dev, "Unable to create debugfs entries\n");
+}
+
+int cdns_hdcp_init(struct cdns_mhdp_device *mhdp, struct device_node *of_node)
+{
+ const char *compat;
+ u32 temp;
+ int ret;
+
+ ret = of_property_read_string(of_node, "compatible", &compat);
+ if (ret) {
+ DRM_ERROR("Failed to compatible dts string\n");
+ return ret;
+ }
+
+ if (!(strstr(compat, "hdmi") || strstr(compat, "dp")))
+ return -EPERM;
+
+ ret = of_property_read_u32(of_node, "hdcp-config", &temp);
+ if (ret) {
+ /* using highest level by default */
+ mhdp->hdcp.config = HDCP_CONFIG_2_2;
+ DRM_INFO("Failed to get HDCP config - using HDCP 2.2 only\n");
+ } else {
+ mhdp->hdcp.config = temp;
+ show_hdcp_supported(mhdp);
+ }
+
+ cdns_hdcp_debugfs_init(mhdp);
+
+#ifdef USE_DEBUG_KEYS /* reserve for hdcp test key */
+ {
+ u8 hdcp_cfg;
+ hdcp_cfg = HDCP_TX_2 | (HDCP_USE_KMKEY << 4) | (HDCP_CONTENT_TYPE_0 << 3);
+ imx_hdmi_load_test_keys(mhdp, &hdcp_cfg);
+ }
+#endif
+
+ mhdp->hdcp.state = HDCP_STATE_INACTIVE;
+
+ mutex_init(&mhdp->hdcp.mutex);
+ INIT_DELAYED_WORK(&mhdp->hdcp.check_work, cdns_hdcp_check_work);
+ INIT_WORK(&mhdp->hdcp.prop_work, cdns_hdcp_prop_work);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(cdns_hdcp_init);
+
+int cdns_hdcp_enable(struct cdns_mhdp_device *mhdp)
+{
+ int ret = 0;
+
+ mhdp->hdcp.reauth_in_progress = 0;
+
+#ifdef STORE_PAIRING
+ cdns_hdcp_get_stored_pairing(mhdp);
+#endif
+ msleep(500);
+
+ mutex_lock(&mhdp->hdcp.mutex);
+
+ mhdp->hdcp.value = DRM_MODE_CONTENT_PROTECTION_DESIRED;
+ mhdp->hdcp.state = HDCP_STATE_ENABLING;
+ mhdp->hdcp.cancel = 0;
+
+ schedule_work(&mhdp->hdcp.prop_work);
+ schedule_delayed_work(&mhdp->hdcp.check_work, 50);
+
+ mutex_unlock(&mhdp->hdcp.mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(cdns_hdcp_enable);
+
+int cdns_hdcp_disable(struct cdns_mhdp_device *mhdp)
+{
+ int ret = 0;
+
+ cancel_delayed_work_sync(&mhdp->hdcp.check_work);
+
+ mutex_lock(&mhdp->hdcp.mutex);
+ if (mhdp->hdcp.value != DRM_MODE_CONTENT_PROTECTION_UNDESIRED) {
+ mhdp->hdcp.value = DRM_MODE_CONTENT_PROTECTION_UNDESIRED;
+ mhdp->hdcp.state = HDCP_STATE_DISABLING;
+ mhdp->hdcp.cancel = 1;
+ schedule_work(&mhdp->hdcp.prop_work);
+ }
+
+ mutex_unlock(&mhdp->hdcp.mutex);
+
+ /* Make sure HDCP_STATE_DISABLING state is handled */
+ cdns_hdcp_check_link(mhdp);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(cdns_hdcp_disable);
+
+void cdns_hdcp_atomic_check(struct drm_connector *connector,
+ struct drm_connector_state *old_state,
+ struct drm_connector_state *new_state)
+{
+ u64 old_cp = old_state->content_protection;
+ u64 new_cp = new_state->content_protection;
+ struct drm_crtc_state *crtc_state;
+
+ if (!new_state->crtc) {
+ /*
+ * If the connector is being disabled with CP enabled, mark it
+ * desired so it's re-enabled when the connector is brought back
+ */
+ if (old_cp == DRM_MODE_CONTENT_PROTECTION_ENABLED)
+ new_state->content_protection =
+ DRM_MODE_CONTENT_PROTECTION_DESIRED;
+ return;
+ }
+
+ /*
+ * Nothing to do if the state didn't change, or HDCP was activated since
+ * the last commit
+ */
+ if (old_cp == new_cp ||
+ (old_cp == DRM_MODE_CONTENT_PROTECTION_DESIRED &&
+ new_cp == DRM_MODE_CONTENT_PROTECTION_ENABLED))
+ return;
+
+ crtc_state = drm_atomic_get_new_crtc_state(new_state->state, new_state->crtc);
+ crtc_state->mode_changed = true;
+}
+EXPORT_SYMBOL_GPL(cdns_hdcp_atomic_check);
+
+static int cdns_hdcp_check_link(struct cdns_mhdp_device *mhdp)
+{
+ u16 hdcp_port_status = 0;
+ u8 hdcp_last_error = 0;
+ u8 hpd_sts;
+ int ret = 0;
+
+ mhdp->hdcp.reauth_in_progress = 0;
+ mutex_lock(&mhdp->lock);
+
+ if (mhdp->hdcp.state == HDCP_STATE_INACTIVE)
+ goto out;
+
+ if (mhdp->hdcp.state == HDCP_STATE_DISABLING) {
+ _cdns_hdcp_disable(mhdp);
+ mhdp->hdcp.state = HDCP_STATE_INACTIVE;
+ goto out;
+ }
+
+ if ((mhdp->hdcp.state == HDCP_STATE_AUTHENTICATED) ||
+ (mhdp->hdcp.state == HDCP_STATE_AUTHENTICATING) ||
+ (mhdp->hdcp.state == HDCP_STATE_REAUTHENTICATING) ||
+ (mhdp->hdcp.state == HDCP_STATE_ENABLING)) {
+
+ /* In active states, check the HPD signal. Because of the IRQ
+ * debounce delay, the state might not reflect the disconnection.
+ * The FW could already have detected the HDP down and reported error */
+ hpd_sts = cdns_mhdp_read_hpd(mhdp);
+ if (hpd_sts == 0) {
+ mhdp->hdcp.state = HDCP_STATE_DISABLING;
+ goto out;
+ }
+ }
+
+/* TODO items:
+ Need to make sure that any requests from the firmware are actually
+ processed so want to remove this first jump to 'out', i.e. process
+ reauthentication requests, cleanup errors and repeater receiver id
+ checks.
+*/
+ if (mhdp->hdcp.state == HDCP_STATE_AUTHENTICATED) {
+ /* get port status */
+ hdcp_port_status = cdns_hdcp_get_status(mhdp);
+ hdcp_last_error = GET_HDCP_PORT_STS_LAST_ERR(hdcp_port_status);
+ if (hdcp_last_error == HDCP_TRAN_ERR_REAUTH_REQ) {
+ DRM_INFO("Sink requesting re-authentication\n");
+ mhdp->hdcp.state = HDCP_STATE_REAUTHENTICATING;
+ } else if (hdcp_last_error) {
+ DRM_ERROR("HDCP error no: %u\n", hdcp_last_error);
+
+ if (mhdp->hdcp.value == DRM_MODE_CONTENT_PROTECTION_UNDESIRED)
+ goto out;
+ if (hdcp_port_status & HDCP_PORT_STS_AUTH) {
+ if (mhdp->hdcp.value !=
+ DRM_MODE_CONTENT_PROTECTION_UNDESIRED) {
+ mhdp->hdcp.value =
+ DRM_MODE_CONTENT_PROTECTION_ENABLED;
+ schedule_work(&mhdp->hdcp.prop_work);
+ goto out;
+ }
+ }
+
+ mhdp->hdcp.state = HDCP_STATE_AUTH_FAILED;
+
+ } else if (mhdp->hdcp.sink_is_repeater) {
+ u8 new_events;
+ /* Check events... and process if HDCPTX_IS_RECEIVER_ID_VALID_EVENT. */
+ new_events = cdns_mhdp_get_event(mhdp);
+ mhdp->hdcp.events |= new_events;
+ if (check_event(mhdp->hdcp.events, HDCPTX_IS_RECEIVER_ID_VALID_EVENT)) {
+ DRM_INFO("Sink repeater updating receiver ID list...\n");
+ if (cdns_hdcp_check_receviers(mhdp))
+ mhdp->hdcp.state = HDCP_STATE_AUTH_FAILED;
+ }
+ }
+ }
+
+ if (mhdp->hdcp.state == HDCP_STATE_REAUTHENTICATING) {
+ /* For now just deal with HDCP2.2 */
+ if (mhdp->hdcp.hdcp_version == HDCP_TX_2)
+ mhdp->hdcp.reauth_in_progress = 1;
+ else
+ mhdp->hdcp.state = HDCP_STATE_AUTH_FAILED;
+ }
+
+ if (mhdp->hdcp.state == HDCP_STATE_ENABLING) {
+ mhdp->hdcp.state = HDCP_STATE_AUTHENTICATING;
+ ret = _cdns_hdcp_enable(mhdp);
+ if (ret == -ECANCELED)
+ goto out;
+ else if (ret) {
+ DRM_ERROR("Failed to enable hdcp (%d)\n", ret);
+ mhdp->hdcp.value = DRM_MODE_CONTENT_PROTECTION_DESIRED;
+ schedule_work(&mhdp->hdcp.prop_work);
+ goto out;
+ }
+ }
+
+ if ((mhdp->hdcp.state == HDCP_STATE_AUTH_FAILED) ||
+ (mhdp->hdcp.state == HDCP_STATE_REAUTHENTICATING)) {
+
+ print_port_status(hdcp_port_status);
+ if (mhdp->hdcp.state == HDCP_STATE_AUTH_FAILED) {
+ DRM_DEBUG_KMS("[%s:%d] HDCP link failed, retrying authentication 0x%2x\n",
+ mhdp->connector.base.name, mhdp->connector.base.base.id, hdcp_port_status);
+ ret = _cdns_hdcp_disable(mhdp);
+ if (ret) {
+ DRM_ERROR("Failed to disable hdcp (%d)\n", ret);
+ mhdp->hdcp.value = DRM_MODE_CONTENT_PROTECTION_DESIRED;
+ schedule_work(&mhdp->hdcp.prop_work);
+ goto out;
+ }
+ } else
+ DRM_DEBUG_KMS("[%s:%d] HDCP attempt reauthentication 0x%2x\n",
+ mhdp->connector.base.name, mhdp->connector.base.base.id, hdcp_port_status);
+
+ ret = _cdns_hdcp_enable(mhdp);
+ if (ret == -ECANCELED)
+ goto out;
+ else if (ret) {
+ DRM_ERROR("Failed to enable hdcp (%d)\n", ret);
+ mhdp->hdcp.value = DRM_MODE_CONTENT_PROTECTION_DESIRED;
+ schedule_work(&mhdp->hdcp.prop_work);
+ goto out;
+ }
+ }
+
+out:
+ mutex_unlock(&mhdp->lock);
+
+ return ret;
+}
diff --git a/drivers/gpu/drm/bridge/cadence/cdns-hdcp-common.h b/drivers/gpu/drm/bridge/cadence/cdns-hdcp-common.h
new file mode 100644
index 000000000000..9de8759252d2
--- /dev/null
+++ b/drivers/gpu/drm/bridge/cadence/cdns-hdcp-common.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2021 NXP Semiconductor, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+
+#ifndef CDNS_HDCP_COMMON_H
+#define CDNS_HDCP_COMMON_H
+
+int cdns_hdcp_init(struct cdns_mhdp_device *mhdp, struct device_node *of_node);
+int cdns_hdcp_enable(struct cdns_mhdp_device *mhdp);
+int cdns_hdcp_disable(struct cdns_mhdp_device *mhdp);
+void cdns_hdcp_atomic_check(struct drm_connector *connector,
+ struct drm_connector_state *old_state,
+ struct drm_connector_state *new_state);
+void cnds_hdcp_create_device_files(struct cdns_mhdp_device *mhdp);
+
+#endif /* CDNS_HDCP_COMMON_H */
diff --git a/drivers/gpu/drm/bridge/cadence/cdns-hdmi-core.c b/drivers/gpu/drm/bridge/cadence/cdns-hdmi-core.c
new file mode 100644
index 000000000000..86620e17cf63
--- /dev/null
+++ b/drivers/gpu/drm/bridge/cadence/cdns-hdmi-core.c
@@ -0,0 +1,827 @@
+/*
+ * Cadence High-Definition Multimedia Interface (HDMI) driver
+ *
+ * Copyright 2019-2021 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+#include <drm/bridge/cdns-mhdp.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_encoder_slave.h>
+#include <drm/drm_hdcp.h>
+#include <drm/drm_of.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_print.h>
+#include <drm/drm_scdc_helper.h>
+#include <drm/drm_vblank.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/hdmi.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/mfd/syscon.h>
+#include <linux/mutex.h>
+#include <linux/of_device.h>
+
+#include "cdns-mhdp-hdcp.h"
+#include "cdns-hdcp-common.h"
+
+static void hdmi_sink_config(struct cdns_mhdp_device *mhdp)
+{
+ struct drm_scdc *scdc = &mhdp->connector.base.display_info.hdmi.scdc;
+ u8 buff = 0;
+
+ /* return if hdmi work in DVI mode */
+ if (mhdp->hdmi.hdmi_type == MODE_DVI)
+ return;
+
+ /* check sink support SCDC or not */
+ if (scdc->supported != true) {
+ DRM_INFO("Sink Not Support SCDC\n");
+ return;
+ }
+
+ if (mhdp->hdmi.char_rate > 340000) {
+ /*
+ * TMDS Character Rate above 340MHz should working in HDMI2.0
+ * Enable scrambling and TMDS_Bit_Clock_Ratio
+ */
+ buff = SCDC_TMDS_BIT_CLOCK_RATIO_BY_40 | SCDC_SCRAMBLING_ENABLE;
+ mhdp->hdmi.hdmi_type = MODE_HDMI_2_0;
+ } else if (scdc->scrambling.low_rates) {
+ /*
+ * Enable scrambling and HDMI2.0 when scrambling capability of sink
+ * be indicated in the HF-VSDB LTE_340Mcsc_scramble bit
+ */
+ buff = SCDC_SCRAMBLING_ENABLE;
+ mhdp->hdmi.hdmi_type = MODE_HDMI_2_0;
+ }
+
+ /* TMDS config */
+ cdns_hdmi_scdc_write(mhdp, 0x20, buff);
+}
+
+static void hdmi_lanes_config(struct cdns_mhdp_device *mhdp)
+{
+ /* Line swaping */
+ cdns_mhdp_reg_write(mhdp, LANES_CONFIG, 0x00400000 | mhdp->lane_mapping);
+}
+
+static int hdmi_avi_info_set(struct cdns_mhdp_device *mhdp,
+ struct drm_display_mode *mode)
+{
+ struct hdmi_avi_infoframe frame;
+ int format = mhdp->video_info.color_fmt;
+ struct drm_connector_state *conn_state = mhdp->connector.base.state;
+ struct drm_display_mode *adj_mode;
+ enum hdmi_quantization_range qr;
+ u8 buf[32];
+ int ret;
+
+ /* Initialise info frame from DRM mode */
+ drm_hdmi_avi_infoframe_from_display_mode(&frame, &mhdp->connector.base,
+ mode);
+
+ switch (format) {
+ case YCBCR_4_4_4:
+ frame.colorspace = HDMI_COLORSPACE_YUV444;
+ break;
+ case YCBCR_4_2_2:
+ frame.colorspace = HDMI_COLORSPACE_YUV422;
+ break;
+ case YCBCR_4_2_0:
+ frame.colorspace = HDMI_COLORSPACE_YUV420;
+ break;
+ default:
+ frame.colorspace = HDMI_COLORSPACE_RGB;
+ break;
+ }
+
+ drm_hdmi_avi_infoframe_colorspace(&frame, conn_state);
+
+ adj_mode = &mhdp->bridge.base.encoder->crtc->state->adjusted_mode;
+
+ qr = drm_default_rgb_quant_range(adj_mode);
+
+ drm_hdmi_avi_infoframe_quant_range(&frame, &mhdp->connector.base,
+ adj_mode, qr);
+
+ ret = hdmi_avi_infoframe_check(&frame);
+ if (WARN_ON(ret))
+ return false;
+
+ ret = hdmi_avi_infoframe_pack(&frame, buf + 1, sizeof(buf) - 1);
+ if (ret < 0) {
+ DRM_ERROR("failed to pack AVI infoframe: %d\n", ret);
+ return -1;
+ }
+
+ buf[0] = 0;
+ cdns_mhdp_infoframe_set(mhdp, 0, sizeof(buf), buf, HDMI_INFOFRAME_TYPE_AVI);
+ return 0;
+}
+
+static void hdmi_vendor_info_set(struct cdns_mhdp_device *mhdp,
+ struct drm_display_mode *mode)
+{
+ struct hdmi_vendor_infoframe frame;
+ u8 buf[32];
+ int ret;
+
+ /* Initialise vendor frame from DRM mode */
+ ret = drm_hdmi_vendor_infoframe_from_display_mode(&frame, &mhdp->connector.base, mode);
+ if (ret < 0) {
+ DRM_INFO("No vendor infoframe\n");
+ return;
+ }
+
+ ret = hdmi_vendor_infoframe_pack(&frame, buf + 1, sizeof(buf) - 1);
+ if (ret < 0) {
+ DRM_WARN("Unable to pack vendor infoframe: %d\n", ret);
+ return;
+ }
+
+ buf[0] = 0;
+ cdns_mhdp_infoframe_set(mhdp, 3, sizeof(buf), buf, HDMI_INFOFRAME_TYPE_VENDOR);
+}
+
+static void hdmi_drm_info_set(struct cdns_mhdp_device *mhdp)
+{
+ struct drm_connector_state *conn_state;
+ struct hdmi_drm_infoframe frame;
+ u8 buf[32];
+ int ret;
+
+ conn_state = mhdp->connector.base.state;
+
+ if (!conn_state->hdr_output_metadata)
+ return;
+
+ ret = drm_hdmi_infoframe_set_hdr_metadata(&frame, conn_state);
+ if (ret < 0) {
+ DRM_DEBUG_KMS("couldn't set HDR metadata in infoframe\n");
+ return;
+ }
+
+ ret = hdmi_drm_infoframe_pack(&frame, buf + 1, sizeof(buf) - 1);
+ if (ret < 0) {
+ DRM_DEBUG_KMS("couldn't pack HDR infoframe\n");
+ return;
+ }
+
+ buf[0] = 0;
+ cdns_mhdp_infoframe_set(mhdp, 3, sizeof(buf),
+ buf, HDMI_INFOFRAME_TYPE_DRM);
+}
+
+void cdns_hdmi_mode_set(struct cdns_mhdp_device *mhdp)
+{
+ struct drm_display_mode *mode = &mhdp->mode;
+ int ret;
+
+ /* video mode valid check */
+ if (mode->clock == 0 || mode->hdisplay == 0 || mode->vdisplay == 0)
+ return;
+
+ hdmi_lanes_config(mhdp);
+
+ cdns_mhdp_plat_call(mhdp, pclk_rate);
+
+ /* delay for HDMI FW stable after pixel clock relock */
+ msleep(20);
+
+ cdns_mhdp_plat_call(mhdp, phy_set);
+
+ hdmi_sink_config(mhdp);
+
+ ret = cdns_hdmi_ctrl_init(mhdp, mhdp->hdmi.hdmi_type, mhdp->hdmi.char_rate);
+ if (ret < 0) {
+ DRM_ERROR("%s, ret = %d\n", __func__, ret);
+ return;
+ }
+
+ /* Config GCP */
+ if (mhdp->video_info.color_depth == 8)
+ cdns_hdmi_disable_gcp(mhdp);
+ else
+ cdns_hdmi_enable_gcp(mhdp);
+
+ ret = hdmi_avi_info_set(mhdp, mode);
+ if (ret < 0) {
+ DRM_ERROR("%s ret = %d\n", __func__, ret);
+ return;
+ }
+
+ /* vendor info frame is enable only when HDMI1.4 4K mode */
+ hdmi_vendor_info_set(mhdp, mode);
+
+ hdmi_drm_info_set(mhdp);
+
+ ret = cdns_hdmi_mode_config(mhdp, mode, &mhdp->video_info);
+ if (ret < 0) {
+ DRM_ERROR("CDN_API_HDMITX_SetVic_blocking ret = %d\n", ret);
+ return;
+ }
+}
+
+static void handle_plugged_change(struct cdns_mhdp_device *mhdp, bool plugged)
+{
+ if (mhdp->plugged_cb && mhdp->codec_dev)
+ mhdp->plugged_cb(mhdp->codec_dev, plugged);
+}
+
+int cdns_hdmi_set_plugged_cb(struct cdns_mhdp_device *mhdp,
+ hdmi_codec_plugged_cb fn,
+ struct device *codec_dev)
+{
+ bool plugged;
+
+ mutex_lock(&mhdp->lock);
+ mhdp->plugged_cb = fn;
+ mhdp->codec_dev = codec_dev;
+ plugged = mhdp->last_connector_result == connector_status_connected;
+ handle_plugged_change(mhdp, plugged);
+ mutex_unlock(&mhdp->lock);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(cdns_hdmi_set_plugged_cb);
+
+static enum drm_connector_status
+cdns_hdmi_connector_detect(struct drm_connector *connector, bool force)
+{
+ struct cdns_mhdp_device *mhdp =
+ container_of(connector, struct cdns_mhdp_device, connector.base);
+ enum drm_connector_status result;
+
+ u8 hpd = 0xf;
+
+ hpd = cdns_mhdp_read_hpd(mhdp);
+
+ if (hpd == 1)
+ /* Cable Connected */
+ result = connector_status_connected;
+ else if (hpd == 0)
+ /* Cable Disconnedted */
+ result = connector_status_disconnected;
+ else {
+ /* Cable status unknown */
+ DRM_INFO("Unknow cable status, hdp=%u\n", hpd);
+ result = connector_status_unknown;
+ }
+
+ mutex_lock(&mhdp->lock);
+ if (result != mhdp->last_connector_result) {
+ handle_plugged_change(mhdp,
+ result == connector_status_connected);
+ mhdp->last_connector_result = result;
+ }
+ mutex_unlock(&mhdp->lock);
+
+ return result;
+}
+
+static int cdns_hdmi_connector_get_modes(struct drm_connector *connector)
+{
+ struct cdns_mhdp_device *mhdp =
+ container_of(connector, struct cdns_mhdp_device, connector.base);
+ int num_modes = 0;
+ struct edid *edid;
+
+ edid = drm_do_get_edid(&mhdp->connector.base,
+ cdns_hdmi_get_edid_block, mhdp);
+ if (edid) {
+ dev_info(mhdp->dev, "%x,%x,%x,%x,%x,%x,%x,%x\n",
+ edid->header[0], edid->header[1],
+ edid->header[2], edid->header[3],
+ edid->header[4], edid->header[5],
+ edid->header[6], edid->header[7]);
+ drm_connector_update_edid_property(connector, edid);
+ num_modes = drm_add_edid_modes(connector, edid);
+ mhdp->hdmi.hdmi_type = drm_detect_hdmi_monitor(edid) ?
+ MODE_HDMI_1_4 : MODE_DVI;
+ kfree(edid);
+ }
+
+ if (num_modes == 0)
+ DRM_ERROR("Invalid edid\n");
+ return num_modes;
+}
+
+static bool blob_equal(const struct drm_property_blob *a,
+ const struct drm_property_blob *b)
+{
+ if (a && b)
+ return a->length == b->length &&
+ !memcmp(a->data, b->data, a->length);
+
+ return !a == !b;
+}
+
+static void cdns_hdmi_bridge_disable(struct drm_bridge *bridge)
+{
+ struct cdns_mhdp_device *mhdp = bridge->driver_private;
+
+ cdns_hdcp_disable(mhdp);
+}
+
+static void cdns_hdmi_bridge_enable(struct drm_bridge *bridge)
+{
+ struct cdns_mhdp_device *mhdp = bridge->driver_private;
+ struct drm_connector_state *conn_state = mhdp->connector.base.state;
+
+ if (conn_state->content_protection == DRM_MODE_CONTENT_PROTECTION_DESIRED)
+ cdns_hdcp_enable(mhdp);
+}
+
+static int cdns_hdmi_connector_atomic_check(struct drm_connector *connector,
+ struct drm_atomic_state *state)
+{
+ struct drm_connector_state *new_con_state =
+ drm_atomic_get_new_connector_state(state, connector);
+ struct drm_connector_state *old_con_state =
+ drm_atomic_get_old_connector_state(state, connector);
+ struct drm_crtc *crtc = new_con_state->crtc;
+ struct drm_crtc_state *new_crtc_state;
+ struct cdns_mhdp_device *mhdp =
+ container_of(connector, struct cdns_mhdp_device, connector.base);
+
+ cdns_hdcp_atomic_check(connector, old_con_state, new_con_state);
+ if (!new_con_state->crtc)
+ return 0;
+
+ new_crtc_state = drm_atomic_get_crtc_state(state, crtc);
+ if (IS_ERR(new_crtc_state))
+ return PTR_ERR(new_crtc_state);
+
+ if (!blob_equal(new_con_state->hdr_output_metadata,
+ old_con_state->hdr_output_metadata) ||
+ new_con_state->colorspace != old_con_state->colorspace) {
+
+ new_crtc_state->mode_changed =
+ !new_con_state->hdr_output_metadata ||
+ !old_con_state->hdr_output_metadata ||
+ new_con_state->colorspace != old_con_state->colorspace;
+ }
+ /* save new connector state */
+ memcpy(&mhdp->connector.new_state, new_con_state, sizeof(struct drm_connector_state));
+
+ /*
+ * These properties are handled by fastset, and might not end up in a
+ * modeset.
+ */
+ if (new_con_state->picture_aspect_ratio !=
+ old_con_state->picture_aspect_ratio ||
+ new_con_state->content_type != old_con_state->content_type ||
+ new_con_state->scaling_mode != old_con_state->scaling_mode)
+ new_crtc_state->mode_changed = true;
+ return 0;
+}
+
+static const struct drm_connector_funcs cdns_hdmi_connector_funcs = {
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .detect = cdns_hdmi_connector_detect,
+ .destroy = drm_connector_cleanup,
+ .reset = drm_atomic_helper_connector_reset,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static const struct drm_connector_helper_funcs cdns_hdmi_connector_helper_funcs = {
+ .get_modes = cdns_hdmi_connector_get_modes,
+ .atomic_check = cdns_hdmi_connector_atomic_check,
+};
+
+static int cdns_hdmi_bridge_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
+{
+ struct cdns_mhdp_device *mhdp = bridge->driver_private;
+ struct drm_mode_config *config = &bridge->dev->mode_config;
+ struct drm_encoder *encoder = bridge->encoder;
+ struct drm_connector *connector = &mhdp->connector.base;
+ int ret;
+
+ connector->interlace_allowed = 1;
+ connector->polled = DRM_CONNECTOR_POLL_HPD;
+ if (!strncmp("imx8mq-hdmi", mhdp->plat_data->plat_name, 11))
+ connector->ycbcr_420_allowed = true;
+
+ drm_connector_helper_add(connector, &cdns_hdmi_connector_helper_funcs);
+
+ ret = drm_connector_init(bridge->dev, connector, &cdns_hdmi_connector_funcs,
+ DRM_MODE_CONNECTOR_HDMIA);
+ if (ret < 0) {
+ DRM_ERROR("Failed to initialize connector\n");
+ return ret;
+ }
+
+ if (!strncmp("imx8mq-hdmi", mhdp->plat_data->plat_name, 11)) {
+ drm_object_attach_property(&connector->base,
+ config->hdr_output_metadata_property,
+ 0);
+
+ if (!drm_mode_create_hdmi_colorspace_property(connector))
+ drm_object_attach_property(&connector->base,
+ connector->colorspace_property,
+ 0);
+ }
+
+ drm_connector_attach_encoder(connector, encoder);
+
+ drm_connector_attach_content_protection_property(connector, true);
+ return 0;
+}
+
+static enum drm_mode_status
+cdns_hdmi_bridge_mode_valid(struct drm_bridge *bridge,
+ const struct drm_display_info *info,
+ const struct drm_display_mode *mode)
+{
+ struct cdns_mhdp_device *mhdp = bridge->driver_private;
+ enum drm_mode_status mode_status = MODE_OK;
+ u32 vic;
+ int ret;
+
+ /* We don't support double-clocked and Interlaced modes */
+ if (mode->flags & DRM_MODE_FLAG_DBLCLK ||
+ mode->flags & DRM_MODE_FLAG_INTERLACE)
+ return MODE_BAD;
+
+ /* MAX support pixel clock rate 594MHz */
+ if (mode->clock > 594000)
+ return MODE_CLOCK_HIGH;
+
+ /* 5120 x 2160 is the maximum supported resolution */
+ if (mode->hdisplay > 5120 || mode->vdisplay > 2160)
+ return MODE_BAD_HVALUE;
+
+ /* imx8mq-hdmi does not support non CEA modes */
+ if (!strncmp("imx8mq-hdmi", mhdp->plat_data->plat_name, 11)) {
+ vic = drm_match_cea_mode(mode);
+ if (vic == 0)
+ return MODE_BAD;
+ }
+
+ mhdp->valid_mode = mode;
+ ret = cdns_mhdp_plat_call(mhdp, phy_video_valid);
+ if (ret == false)
+ return MODE_CLOCK_RANGE;
+
+ return mode_status;
+}
+
+static void cdns_hdmi_bridge_mode_set(struct drm_bridge *bridge,
+ const struct drm_display_mode *orig_mode,
+ const struct drm_display_mode *mode)
+{
+ struct cdns_mhdp_device *mhdp = bridge->driver_private;
+ struct video_info *video = &mhdp->video_info;
+
+ video->v_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NVSYNC);
+ video->h_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NHSYNC);
+
+ DRM_INFO("Mode: %dx%dp%d\n", mode->hdisplay, mode->vdisplay, mode->clock);
+ memcpy(&mhdp->mode, mode, sizeof(struct drm_display_mode));
+
+ mutex_lock(&mhdp->lock);
+ cdns_hdmi_mode_set(mhdp);
+ mutex_unlock(&mhdp->lock);
+}
+
+bool cdns_hdmi_bridge_mode_fixup(struct drm_bridge *bridge,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ struct cdns_mhdp_device *mhdp = bridge->driver_private;
+ struct drm_connector_state *new_state = &mhdp->connector.new_state;
+ struct drm_display_info *di = &mhdp->connector.base.display_info;
+ struct video_info *video = &mhdp->video_info;
+ int vic = drm_match_cea_mode(mode);
+
+ video->color_depth = 8;
+ video->color_fmt = PXL_RGB;
+
+ /* for all other platforms, other than imx8mq */
+ if (strncmp("imx8mq-hdmi", mhdp->plat_data->plat_name, 11)) {
+ if (di->bpc == 10 || di->bpc == 6)
+ video->color_depth = di->bpc;
+
+ return true;
+ }
+
+ /* H20 Section 7.2.2, Colorimetry BT2020 for pixel encoding 10bpc or more */
+ if (new_state->colorspace == DRM_MODE_COLORIMETRY_BT2020_RGB) {
+ if (drm_mode_is_420_only(di, mode))
+ return false;
+
+ /* BT2020_RGB for RGB 10bit or more */
+ /* 10b RGB is not supported for following VICs */
+ if (vic == 97 || vic == 96 || vic == 95 || vic == 93 || vic == 94)
+ return false;
+
+ video->color_depth = 10;
+ } else if (new_state->colorspace == DRM_MODE_COLORIMETRY_BT2020_CYCC ||
+ new_state->colorspace == DRM_MODE_COLORIMETRY_BT2020_YCC) {
+ /* BT2020_YCC/CYCC for YUV 10bit or more */
+ if (drm_mode_is_420_only(di, mode) ||
+ drm_mode_is_420_also(di, mode))
+ video->color_fmt = YCBCR_4_2_0;
+ else
+ video->color_fmt = YCBCR_4_2_2;
+
+ if (di->hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_36)
+ video->color_depth = 12;
+ else if (di->hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_30)
+ video->color_depth = 10;
+ } else if (new_state->colorspace == DRM_MODE_COLORIMETRY_SMPTE_170M_YCC ||
+ new_state->colorspace == DRM_MODE_COLORIMETRY_BT709_YCC ||
+ new_state->colorspace == DRM_MODE_COLORIMETRY_XVYCC_601 ||
+ new_state->colorspace == DRM_MODE_COLORIMETRY_XVYCC_709 ||
+ new_state->colorspace == DRM_MODE_COLORIMETRY_SYCC_601) {
+ /* Colorimetry for HD and SD YUV */
+ if (drm_mode_is_420_only(di, mode) || drm_mode_is_420_also(di, mode))
+ video->color_fmt = YCBCR_4_2_0;
+ else
+ video->color_fmt = YCBCR_4_4_4;
+ } else if (new_state->colorspace == DRM_MODE_COLORIMETRY_DEFAULT) {
+ /* set default color fmt for YUV420 only mode */
+ if (drm_mode_is_420_only(di, mode))
+ video->color_fmt = YCBCR_4_2_0;
+ }
+
+ return true;
+}
+
+static const struct drm_bridge_funcs cdns_hdmi_bridge_funcs = {
+ .attach = cdns_hdmi_bridge_attach,
+ .enable = cdns_hdmi_bridge_enable,
+ .disable = cdns_hdmi_bridge_disable,
+ .mode_set = cdns_hdmi_bridge_mode_set,
+ .mode_valid = cdns_hdmi_bridge_mode_valid,
+ .mode_fixup = cdns_hdmi_bridge_mode_fixup,
+};
+
+static void hotplug_work_func(struct work_struct *work)
+{
+ struct cdns_mhdp_device *mhdp = container_of(work,
+ struct cdns_mhdp_device, hotplug_work.work);
+ struct drm_connector *connector = &mhdp->connector.base;
+
+ drm_helper_hpd_irq_event(connector->dev);
+
+ if (connector->status == connector_status_connected) {
+ DRM_INFO("HDMI Cable Plug In\n");
+
+ /* Recovery HDCP state */
+ if (connector->state->content_protection != DRM_MODE_CONTENT_PROTECTION_UNDESIRED)
+ mhdp->hdcp.state = HDCP_STATE_ENABLING;
+
+ mhdp->force_mode_set = true;
+ enable_irq(mhdp->irq[IRQ_OUT]);
+ } else if (connector->status == connector_status_disconnected) {
+ /* Cable Disconnedted */
+ DRM_INFO("HDMI Cable Plug Out\n");
+
+ /* Disable HDCP when cable plugout,
+ * set content_protection to DESIRED, recovery HDCP state after cable plugin
+ */
+ if (connector->state->content_protection != DRM_MODE_CONTENT_PROTECTION_UNDESIRED) {
+ connector->state->content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED;
+ mhdp->hdcp.state = HDCP_STATE_DISABLING;
+ }
+
+ /* force mode set for cable replugin to recovery HDMI2.0 video modes */
+ mhdp->force_mode_set = true;
+ enable_irq(mhdp->irq[IRQ_IN]);
+ }
+}
+
+static irqreturn_t cdns_hdmi_irq_thread(int irq, void *data)
+{
+ struct cdns_mhdp_device *mhdp = data;
+
+ disable_irq_nosync(irq);
+
+ mod_delayed_work(system_wq, &mhdp->hotplug_work,
+ msecs_to_jiffies(HOTPLUG_DEBOUNCE_MS));
+
+ return IRQ_HANDLED;
+}
+
+static void cdns_hdmi_parse_dt(struct cdns_mhdp_device *mhdp)
+{
+ struct device_node *of_node = mhdp->dev->of_node;
+ int ret;
+
+ ret = of_property_read_u32(of_node, "lane-mapping", &mhdp->lane_mapping);
+ if (ret) {
+ mhdp->lane_mapping = 0xc6;
+ dev_warn(mhdp->dev, "Failed to get lane_mapping - using default 0xc6\n");
+ }
+ dev_info(mhdp->dev, "lane-mapping 0x%02x\n", mhdp->lane_mapping);
+}
+
+#ifdef CONFIG_DRM_CDNS_HDMI_CEC
+static void cdns_mhdp_cec_init(struct cdns_mhdp_device *mhdp)
+{
+ struct cdns_mhdp_cec *cec = &mhdp->hdmi.cec;
+
+ cec->dev = mhdp->dev;
+ cec->iolock = &mhdp->iolock;
+ cec->regs_base = mhdp->regs_base;
+ cec->regs_sec = mhdp->regs_sec;
+ cec->bus_type = mhdp->bus_type;
+}
+#endif
+
+static int __cdns_hdmi_probe(struct platform_device *pdev,
+ struct cdns_mhdp_device *mhdp)
+{
+ struct device *dev = &pdev->dev;
+ struct platform_device_info pdevinfo;
+ struct resource *iores = NULL;
+ int ret;
+
+ mutex_init(&mhdp->lock);
+ mutex_init(&mhdp->api_lock);
+ mutex_init(&mhdp->iolock);
+
+ INIT_DELAYED_WORK(&mhdp->hotplug_work, hotplug_work_func);
+
+ iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ mhdp->regs_base = devm_ioremap(dev, iores->start, resource_size(iores));
+ if (IS_ERR(mhdp->regs_base)) {
+ dev_err(dev, "No regs_base memory\n");
+ return -ENOMEM;
+ }
+
+ /* sec register base */
+ iores = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ mhdp->regs_sec = devm_ioremap(dev, iores->start, resource_size(iores));
+ if (IS_ERR(mhdp->regs_sec)) {
+ dev_err(dev, "No regs_sec memory\n");
+ return -ENOMEM;
+ }
+
+ mhdp->irq[IRQ_IN] = platform_get_irq_byname(pdev, "plug_in");
+ if (mhdp->irq[IRQ_IN] < 0) {
+ dev_info(dev, "No plug_in irq number\n");
+ return -EPROBE_DEFER;
+ }
+
+ mhdp->irq[IRQ_OUT] = platform_get_irq_byname(pdev, "plug_out");
+ if (mhdp->irq[IRQ_OUT] < 0) {
+ dev_info(dev, "No plug_out irq number\n");
+ return -EPROBE_DEFER;
+ }
+
+ cdns_mhdp_plat_call(mhdp, power_on);
+
+ /* Initialize FW */
+ cdns_mhdp_plat_call(mhdp, firmware_init);
+
+ /* HDMI FW alive check */
+ ret = cdns_mhdp_check_alive(mhdp);
+ if (ret == false) {
+ dev_err(dev, "NO HDMI FW running\n");
+ return -ENXIO;
+ }
+
+ /* Enable Hotplug Detect thread */
+ irq_set_status_flags(mhdp->irq[IRQ_IN], IRQ_NOAUTOEN);
+ ret = devm_request_threaded_irq(dev, mhdp->irq[IRQ_IN],
+ NULL, cdns_hdmi_irq_thread,
+ IRQF_ONESHOT, dev_name(dev),
+ mhdp);
+ if (ret < 0) {
+ dev_err(dev, "can't claim irq %d\n",
+ mhdp->irq[IRQ_IN]);
+ return -EINVAL;
+ }
+
+ irq_set_status_flags(mhdp->irq[IRQ_OUT], IRQ_NOAUTOEN);
+ ret = devm_request_threaded_irq(dev, mhdp->irq[IRQ_OUT],
+ NULL, cdns_hdmi_irq_thread,
+ IRQF_ONESHOT, dev_name(dev),
+ mhdp);
+ if (ret < 0) {
+ dev_err(dev, "can't claim irq %d\n",
+ mhdp->irq[IRQ_OUT]);
+ return -EINVAL;
+ }
+
+ cdns_hdmi_parse_dt(mhdp);
+
+ ret = cdns_hdcp_init(mhdp, pdev->dev.of_node);
+ if (ret < 0)
+ DRM_WARN("Failed to initialize HDCP\n");
+
+ cnds_hdcp_create_device_files(mhdp);
+
+ if (cdns_mhdp_read_hpd(mhdp))
+ enable_irq(mhdp->irq[IRQ_OUT]);
+ else
+ enable_irq(mhdp->irq[IRQ_IN]);
+
+ mhdp->bridge.base.driver_private = mhdp;
+ mhdp->bridge.base.funcs = &cdns_hdmi_bridge_funcs;
+#ifdef CONFIG_OF
+ mhdp->bridge.base.of_node = dev->of_node;
+#endif
+ mhdp->last_connector_result = connector_status_disconnected;
+
+ memset(&pdevinfo, 0, sizeof(pdevinfo));
+ pdevinfo.parent = dev;
+ pdevinfo.id = PLATFORM_DEVID_AUTO;
+
+ dev_set_drvdata(dev, mhdp);
+
+ /* register audio driver */
+ cdns_mhdp_register_audio_driver(dev);
+
+ /* register cec driver */
+#ifdef CONFIG_DRM_CDNS_HDMI_CEC
+ cdns_mhdp_cec_init(mhdp);
+ cdns_mhdp_register_cec_driver(&mhdp->hdmi.cec);
+#endif
+
+ return 0;
+}
+
+static void __cdns_hdmi_remove(struct cdns_mhdp_device *mhdp)
+{
+ /* unregister cec driver */
+#ifdef CONFIG_DRM_CDNS_HDMI_CEC
+ cdns_mhdp_unregister_cec_driver(&mhdp->hdmi.cec);
+#endif
+ cdns_mhdp_unregister_audio_driver(mhdp->dev);
+}
+
+/* -----------------------------------------------------------------------------
+ * Probe/remove API, used from platforms based on the DRM bridge API.
+ */
+int cdns_hdmi_probe(struct platform_device *pdev,
+ struct cdns_mhdp_device *mhdp)
+{
+ int ret;
+
+ ret = __cdns_hdmi_probe(pdev, mhdp);
+ if (ret < 0)
+ return ret;
+
+ drm_bridge_add(&mhdp->bridge.base);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(cdns_hdmi_probe);
+
+void cdns_hdmi_remove(struct platform_device *pdev)
+{
+ struct cdns_mhdp_device *mhdp = platform_get_drvdata(pdev);
+
+ drm_bridge_remove(&mhdp->bridge.base);
+
+ __cdns_hdmi_remove(mhdp);
+}
+EXPORT_SYMBOL_GPL(cdns_hdmi_remove);
+
+/* -----------------------------------------------------------------------------
+ * Bind/unbind API, used from platforms based on the component framework.
+ */
+int cdns_hdmi_bind(struct platform_device *pdev, struct drm_encoder *encoder,
+ struct cdns_mhdp_device *mhdp)
+{
+ int ret;
+
+ ret = __cdns_hdmi_probe(pdev, mhdp);
+ if (ret)
+ return ret;
+
+ ret = drm_bridge_attach(encoder, &mhdp->bridge.base, NULL, 0);
+ if (ret) {
+ cdns_hdmi_remove(pdev);
+ DRM_ERROR("Failed to initialize bridge with drm\n");
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(cdns_hdmi_bind);
+
+void cdns_hdmi_unbind(struct device *dev)
+{
+ struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev);
+
+ __cdns_hdmi_remove(mhdp);
+}
+EXPORT_SYMBOL_GPL(cdns_hdmi_unbind);
+
+MODULE_AUTHOR("Sandor Yu <sandor.yu@nxp.com>");
+MODULE_DESCRIPTION("Cadence HDMI transmitter driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:cdn-hdmi");
diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp-audio.c b/drivers/gpu/drm/bridge/cadence/cdns-mhdp-audio.c
new file mode 100644
index 000000000000..85f526175439
--- /dev/null
+++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp-audio.c
@@ -0,0 +1,423 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author: Chris Zhong <zyw@rock-chips.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#include <linux/clk.h>
+#include <linux/reset.h>
+#include <drm/bridge/cdns-mhdp.h>
+#include <sound/hdmi-codec.h>
+#include <drm/drm_of.h>
+#include <drm/drm_vblank.h>
+#include <drm/drm_print.h>
+
+#define CDNS_DP_SPDIF_CLK 200000000
+
+static u32 TMDS_rate_table[7] = {
+ 25200, 27000, 54000, 74250, 148500, 297000, 594000,
+};
+
+static u32 N_table_32k[7] = {
+/* 25200/27000/54000/74250/148500/297000/594000 */
+ 4096, 4096, 4096, 4096, 4096, 3072, 3072,
+};
+
+static u32 N_table_44k[7] = {
+ 6272, 6272, 6272, 6272, 6272, 4704, 9408,
+};
+
+static u32 N_table_48k[7] = {
+ 6144, 6144, 6144, 6144, 6144, 5120, 6144,
+};
+
+static int select_N_index(u32 pclk)
+{
+ int num = sizeof(TMDS_rate_table)/sizeof(int);
+ int i = 0;
+
+ for (i = 0; i < num ; i++)
+ if (pclk == TMDS_rate_table[i])
+ break;
+
+ if (i == num) {
+ DRM_WARN("pclkc %d is not supported!\n", pclk);
+ return num-1;
+ }
+
+ return i;
+}
+
+static void hdmi_audio_avi_set(struct cdns_mhdp_device *mhdp,
+ u32 channels)
+{
+ struct hdmi_audio_infoframe frame;
+ u8 buf[32];
+ int ret;
+
+ hdmi_audio_infoframe_init(&frame);
+
+ frame.channels = channels;
+ frame.coding_type = HDMI_AUDIO_CODING_TYPE_STREAM;
+
+ if (channels == 2)
+ frame.channel_allocation = 0;
+ else if (channels == 4)
+ frame.channel_allocation = 0x3;
+ else if (channels == 6)
+ frame.channel_allocation = 0xB;
+ else if (channels == 8)
+ frame.channel_allocation = 0x13;
+
+ ret = hdmi_audio_infoframe_pack(&frame, buf + 1, sizeof(buf) - 1);
+ if (ret < 0) {
+ DRM_ERROR("failed to pack audio infoframe: %d\n", ret);
+ return;
+ }
+
+ buf[0] = 0;
+
+ cdns_mhdp_infoframe_set(mhdp, 1, sizeof(buf), buf, HDMI_INFOFRAME_TYPE_AUDIO);
+}
+
+int cdns_mhdp_audio_stop(struct cdns_mhdp_device *mhdp,
+ struct audio_info *audio)
+{
+ int ret;
+
+ if (audio->connector_type == DRM_MODE_CONNECTOR_DisplayPort) {
+ ret = cdns_mhdp_reg_write(mhdp, AUDIO_PACK_CONTROL, 0);
+ if (ret) {
+ DRM_DEV_ERROR(mhdp->dev, "audio stop failed: %d\n", ret);
+ return ret;
+ }
+ }
+
+ cdns_mhdp_bus_write(0, mhdp, SPDIF_CTRL_ADDR);
+
+ /* clearn the audio config and reset */
+ cdns_mhdp_bus_write(0, mhdp, AUDIO_SRC_CNTL);
+ cdns_mhdp_bus_write(0, mhdp, AUDIO_SRC_CNFG);
+ cdns_mhdp_bus_write(AUDIO_SW_RST, mhdp, AUDIO_SRC_CNTL);
+ cdns_mhdp_bus_write(0, mhdp, AUDIO_SRC_CNTL);
+
+ /* reset smpl2pckt component */
+ cdns_mhdp_bus_write(0, mhdp, SMPL2PKT_CNTL);
+ cdns_mhdp_bus_write(AUDIO_SW_RST, mhdp, SMPL2PKT_CNTL);
+ cdns_mhdp_bus_write(0, mhdp, SMPL2PKT_CNTL);
+
+ /* reset FIFO */
+ cdns_mhdp_bus_write(AUDIO_SW_RST, mhdp, FIFO_CNTL);
+ cdns_mhdp_bus_write(0, mhdp, FIFO_CNTL);
+
+ if (audio->format == AFMT_SPDIF_INT)
+ clk_disable_unprepare(mhdp->spdif_clk);
+
+ return 0;
+}
+EXPORT_SYMBOL(cdns_mhdp_audio_stop);
+
+int cdns_mhdp_audio_mute(struct cdns_mhdp_device *mhdp, bool enable)
+{
+ struct audio_info *audio = &mhdp->audio_info;
+ int ret = true;
+
+ if (audio->connector_type == DRM_MODE_CONNECTOR_DisplayPort) {
+ ret = cdns_mhdp_reg_write_bit(mhdp, DP_VB_ID, 4, 1, enable);
+ if (ret)
+ DRM_DEV_ERROR(mhdp->dev, "audio mute failed: %d\n", ret);
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL(cdns_mhdp_audio_mute);
+
+static void cdns_mhdp_audio_config_i2s(struct cdns_mhdp_device *mhdp,
+ struct audio_info *audio)
+{
+ int sub_pckt_num = 1, i2s_port_en_val = 0xf, i;
+ int idx = select_N_index(mhdp->mode.clock);
+ int numofchannels = audio->channels;
+ u32 val, ncts;
+ u32 disable_port3 = 0;
+ u32 audio_type = 0x2; /* L-PCM */
+ u32 transmission_type = 0; /* not required for L-PCM */
+
+ if (numofchannels == 2) {
+ if (mhdp->dp.num_lanes == 1)
+ sub_pckt_num = 2;
+ else
+ sub_pckt_num = 4;
+
+ i2s_port_en_val = 1;
+ } else if (numofchannels == 4) {
+ i2s_port_en_val = 3;
+ } else if (numofchannels == 6) {
+ numofchannels = 8;
+ disable_port3 = 1;
+ } else if ((numofchannels == 8) && (audio->non_pcm)) {
+ audio_type = 0x9; /* HBR packet type */
+ transmission_type = 0x9; /* HBR packet type */
+ }
+
+ cdns_mhdp_bus_write(0x0, mhdp, SPDIF_CTRL_ADDR);
+
+ val = SYNC_WR_TO_CH_ZERO;
+ val |= disable_port3 << 4;
+ cdns_mhdp_bus_write(val, mhdp, FIFO_CNTL);
+
+ val = MAX_NUM_CH(numofchannels);
+ val |= NUM_OF_I2S_PORTS(numofchannels);
+ val |= audio_type << 7;
+ val |= CFG_SUB_PCKT_NUM(sub_pckt_num);
+ cdns_mhdp_bus_write(val, mhdp, SMPL2PKT_CNFG);
+
+ if (audio->sample_width == 16)
+ val = 0;
+ else if (audio->sample_width == 24)
+ val = 1 << 9;
+ else
+ val = 2 << 9;
+
+ val |= AUDIO_CH_NUM(numofchannels);
+ val |= I2S_DEC_PORT_EN(i2s_port_en_val);
+ val |= TRANS_SMPL_WIDTH_32;
+ val |= transmission_type << 13;
+ cdns_mhdp_bus_write(val, mhdp, AUDIO_SRC_CNFG);
+
+ for (i = 0; i < (numofchannels + 1) / 2; i++) {
+ if (audio->sample_width == 16)
+ val = (0x02 << 8) | (0x02 << 20);
+ else if (audio->sample_width == 24)
+ val = (0x0b << 8) | (0x0b << 20);
+
+ val |= ((2 * i) << 4) | ((2 * i + 1) << 16);
+ cdns_mhdp_bus_write(val, mhdp, STTS_BIT_CH(i));
+ }
+
+ switch (audio->sample_rate) {
+ case 32000:
+ val = SAMPLING_FREQ(3) |
+ ORIGINAL_SAMP_FREQ(0xc);
+ ncts = N_table_32k[idx];
+ break;
+ case 44100:
+ val = SAMPLING_FREQ(0) |
+ ORIGINAL_SAMP_FREQ(0xf);
+ ncts = N_table_44k[idx];
+ break;
+ case 48000:
+ val = SAMPLING_FREQ(2) |
+ ORIGINAL_SAMP_FREQ(0xd);
+ ncts = N_table_48k[idx];
+ break;
+ case 88200:
+ val = SAMPLING_FREQ(8) |
+ ORIGINAL_SAMP_FREQ(0x7);
+ ncts = N_table_44k[idx] * 2;
+ break;
+ case 96000:
+ val = SAMPLING_FREQ(0xa) |
+ ORIGINAL_SAMP_FREQ(5);
+ ncts = N_table_48k[idx] * 2;
+ break;
+ case 176400:
+ val = SAMPLING_FREQ(0xc) |
+ ORIGINAL_SAMP_FREQ(3);
+ ncts = N_table_44k[idx] * 4;
+ break;
+ case 192000:
+ default:
+ val = SAMPLING_FREQ(0xe) |
+ ORIGINAL_SAMP_FREQ(1);
+ ncts = N_table_48k[idx] * 4;
+ break;
+ }
+ val |= 4;
+ cdns_mhdp_bus_write(val, mhdp, COM_CH_STTS_BITS);
+
+ if (audio->connector_type == DRM_MODE_CONNECTOR_HDMIA)
+ cdns_mhdp_reg_write(mhdp, CM_I2S_CTRL, ncts | 0x4000000);
+
+ cdns_mhdp_bus_write(SMPL2PKT_EN, mhdp, SMPL2PKT_CNTL);
+ cdns_mhdp_bus_write(I2S_DEC_START, mhdp, AUDIO_SRC_CNTL);
+}
+
+static void cdns_mhdp_audio_config_spdif(struct cdns_mhdp_device *mhdp)
+{
+ u32 val;
+
+ cdns_mhdp_bus_write(SYNC_WR_TO_CH_ZERO, mhdp, FIFO_CNTL);
+
+ val = MAX_NUM_CH(2) | AUDIO_TYPE_LPCM | CFG_SUB_PCKT_NUM(4);
+ cdns_mhdp_bus_write(val, mhdp, SMPL2PKT_CNFG);
+ cdns_mhdp_bus_write(SMPL2PKT_EN, mhdp, SMPL2PKT_CNTL);
+
+ val = SPDIF_ENABLE | SPDIF_AVG_SEL | SPDIF_JITTER_BYPASS;
+ cdns_mhdp_bus_write(val, mhdp, SPDIF_CTRL_ADDR);
+
+ clk_prepare_enable(mhdp->spdif_clk);
+ clk_set_rate(mhdp->spdif_clk, CDNS_DP_SPDIF_CLK);
+}
+
+int cdns_mhdp_audio_config(struct cdns_mhdp_device *mhdp,
+ struct audio_info *audio)
+{
+ int ret;
+
+ /* reset the spdif clk before config */
+ if (audio->format == AFMT_SPDIF_INT) {
+ reset_control_assert(mhdp->spdif_rst);
+ reset_control_deassert(mhdp->spdif_rst);
+ }
+
+ if (audio->connector_type == DRM_MODE_CONNECTOR_DisplayPort) {
+ ret = cdns_mhdp_reg_write(mhdp, CM_LANE_CTRL, LANE_REF_CYC);
+ if (ret)
+ goto err_audio_config;
+
+ ret = cdns_mhdp_reg_write(mhdp, CM_CTRL, 0);
+ if (ret)
+ goto err_audio_config;
+ } else {
+ /* HDMI Mode */
+ ret = cdns_mhdp_reg_write(mhdp, CM_CTRL, 8);
+ if (ret)
+ goto err_audio_config;
+ }
+
+ if (audio->format == AFMT_I2S)
+ cdns_mhdp_audio_config_i2s(mhdp, audio);
+ else if (audio->format == AFMT_SPDIF_INT)
+ cdns_mhdp_audio_config_spdif(mhdp);
+
+ if (audio->connector_type == DRM_MODE_CONNECTOR_DisplayPort)
+ ret = cdns_mhdp_reg_write(mhdp, AUDIO_PACK_CONTROL, AUDIO_PACK_EN);
+
+ if (audio->connector_type == DRM_MODE_CONNECTOR_HDMIA)
+ hdmi_audio_avi_set(mhdp, audio->channels);
+
+err_audio_config:
+ if (ret)
+ DRM_DEV_ERROR(mhdp->dev, "audio config failed: %d\n", ret);
+ return ret;
+}
+EXPORT_SYMBOL(cdns_mhdp_audio_config);
+
+static int audio_hw_params(struct device *dev, void *data,
+ struct hdmi_codec_daifmt *daifmt,
+ struct hdmi_codec_params *params)
+{
+ struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev);
+ struct audio_info audio = {
+ .sample_width = params->sample_width,
+ .sample_rate = params->sample_rate,
+ .channels = params->channels,
+ .connector_type = mhdp->connector.base.connector_type,
+ };
+ int ret;
+
+ switch (daifmt->fmt) {
+ case HDMI_I2S:
+ audio.format = AFMT_I2S;
+ break;
+ case HDMI_SPDIF:
+ audio.format = AFMT_SPDIF_EXT;
+ break;
+ default:
+ DRM_DEV_ERROR(dev, "Invalid format %d\n", daifmt->fmt);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ audio.non_pcm = params->iec.status[0] & IEC958_AES0_NONAUDIO;
+
+ ret = cdns_mhdp_audio_config(mhdp, &audio);
+ if (!ret)
+ mhdp->audio_info = audio;
+
+out:
+ return ret;
+}
+
+static void audio_shutdown(struct device *dev, void *data)
+{
+ struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev);
+ int ret;
+
+ ret = cdns_mhdp_audio_stop(mhdp, &mhdp->audio_info);
+ if (!ret)
+ mhdp->audio_info.format = AFMT_UNUSED;
+}
+
+static int audio_mute_stream(struct device *dev, void *data,
+ bool enable, int direction)
+{
+ struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev);
+ int ret;
+
+ ret = cdns_mhdp_audio_mute(mhdp, enable);
+
+ return ret;
+}
+
+static int audio_get_eld(struct device *dev, void *data,
+ u8 *buf, size_t len)
+{
+ struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev);
+
+ memcpy(buf, mhdp->connector.base.eld,
+ min(sizeof(mhdp->connector.base.eld), len));
+
+ return 0;
+}
+
+static int audio_hook_plugged_cb(struct device *dev, void *data,
+ hdmi_codec_plugged_cb fn,
+ struct device *codec_dev)
+{
+ struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev);
+
+ return cdns_hdmi_set_plugged_cb(mhdp, fn, codec_dev);
+}
+
+static const struct hdmi_codec_ops audio_codec_ops = {
+ .hw_params = audio_hw_params,
+ .audio_shutdown = audio_shutdown,
+ .mute_stream = audio_mute_stream,
+ .get_eld = audio_get_eld,
+ .hook_plugged_cb = audio_hook_plugged_cb,
+ .no_capture_mute = 1,
+};
+
+int cdns_mhdp_register_audio_driver(struct device *dev)
+{
+ struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev);
+ struct hdmi_codec_pdata codec_data = {
+ .i2s = 1,
+ .spdif = 1,
+ .ops = &audio_codec_ops,
+ .max_i2s_channels = 8,
+ };
+
+ mhdp->audio_pdev = platform_device_register_data(
+ dev, HDMI_CODEC_DRV_NAME, 1,
+ &codec_data, sizeof(codec_data));
+
+ return PTR_ERR_OR_ZERO(mhdp->audio_pdev);
+}
+
+void cdns_mhdp_unregister_audio_driver(struct device *dev)
+{
+ struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev);
+
+ platform_device_unregister(mhdp->audio_pdev);
+}
diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp-cec.c b/drivers/gpu/drm/bridge/cadence/cdns-mhdp-cec.c
new file mode 100644
index 000000000000..18ae36cd3668
--- /dev/null
+++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp-cec.c
@@ -0,0 +1,365 @@
+/*
+ * Copyright 2019-2020 NXP
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#include <linux/module.h>
+#include <linux/workqueue.h>
+#include <linux/kthread.h>
+#include <linux/freezer.h>
+#include <drm/bridge/cdns-mhdp.h>
+
+#define CEC_NAME "cdns-mhdp-cec"
+
+#define REG_ADDR_OFF 4
+
+/* regsiter define */
+#define TX_MSG_HEADER 0x33800
+#define TX_MSG_LENGTH 0x33840
+#define TX_MSG_CMD 0x33844
+#define RX_MSG_CMD 0x33850
+#define RX_CLEAR_BUF 0x33854
+#define LOGICAL_ADDRESS_LA0 0x33858
+
+#define CLK_DIV_MSB 0x3386c
+#define CLK_DIV_LSB 0x33870
+#define RX_MSG_DATA1 0x33900
+#define RX_MSG_LENGTH 0x33940
+#define RX_MSG_STATUS 0x33944
+#define NUM_OF_MSG_RX_BUF 0x33948
+#define TX_MSG_STATUS 0x3394c
+#define DB_L_TIMER 0x33980
+
+/**
+ * CEC Transceiver operation.
+ */
+enum {
+ CEC_TX_STOP,
+ CEC_TX_TRANSMIT,
+ CEC_TX_ABORT,
+ CEC_TX_ABORT_AND_TRANSMIT
+};
+
+/**
+ * CEC Transceiver status.
+ */
+enum {
+ CEC_STS_IDLE,
+ CEC_STS_BUSY,
+ CEC_STS_SUCCESS,
+ CEC_STS_ERROR
+};
+
+/**
+ * CEC Receiver operation.
+ */
+enum {
+ CEC_RX_STOP,
+ CEC_RX_READ,
+ CEC_RX_DISABLE,
+ CEC_RX_ABORT_AND_CLR_FIFO
+};
+/**
+ * Maximum number of Messages in the RX Buffers.
+ */
+#define CEC_MAX_RX_MSGS 2
+
+static u32 mhdp_cec_read(struct cdns_mhdp_cec *cec, u32 offset)
+{
+ u32 val;
+
+ mutex_lock(cec->iolock);
+
+ if (cec->bus_type == BUS_TYPE_LOW4K_HDMI_RX) {
+ /* Remap address to low 4K HDMI RX */
+ writel(offset >> 12, cec->regs_sec + 4);
+ val = readl((offset & 0xfff) + cec->regs_base);
+ } else if (cec->bus_type == BUS_TYPE_LOW4K_APB) {
+ /* Remap address to low 4K memory */
+ writel(offset >> 12, cec->regs_sec + 8);
+ val = readl((offset & 0xfff) + cec->regs_base);
+ } else
+ val = readl(cec->regs_base + offset);
+
+ mutex_unlock(cec->iolock);
+
+ return val;
+}
+
+static void mhdp_cec_write(struct cdns_mhdp_cec *cec, u32 offset, u32 val)
+{
+ mutex_lock(cec->iolock);
+
+ if (cec->bus_type == BUS_TYPE_LOW4K_HDMI_RX) {
+ /* Remap address to low 4K SAPB bus */
+ writel(offset >> 12, cec->regs_sec + 4);
+ writel(val, (offset & 0xfff) + cec->regs_base);
+ } else if (cec->bus_type == BUS_TYPE_LOW4K_APB) {
+ /* Remap address to low 4K memory */
+ writel(offset >> 12, cec->regs_sec + 8);
+ writel(val, (offset & 0xfff) + cec->regs_base);
+ } else if (cec->bus_type == BUS_TYPE_NORMAL_SAPB)
+ writel(val, cec->regs_sec + offset);
+ else
+ writel(val, cec->regs_base + offset);
+
+ mutex_unlock(cec->iolock);
+}
+
+static u32 mhdp_get_fw_clk(struct cdns_mhdp_cec *cec)
+{
+ return mhdp_cec_read(cec, SW_CLK_H);
+}
+
+static void mhdp_cec_clear_rx_buffer(struct cdns_mhdp_cec *cec)
+{
+ mhdp_cec_write(cec, RX_CLEAR_BUF, 1);
+ mhdp_cec_write(cec, RX_CLEAR_BUF, 0);
+}
+
+static void mhdp_cec_set_divider(struct cdns_mhdp_cec *cec)
+{
+ u32 clk_div;
+
+ /* Set clock divider */
+ clk_div = mhdp_get_fw_clk(cec) * 10;
+
+ mhdp_cec_write(cec, CLK_DIV_MSB,
+ (clk_div >> 8) & 0xFF);
+ mhdp_cec_write(cec, CLK_DIV_LSB, clk_div & 0xFF);
+}
+
+static u32 mhdp_cec_read_message(struct cdns_mhdp_cec *cec)
+{
+ struct cec_msg *msg = &cec->msg;
+ int len;
+ int i;
+
+ mhdp_cec_write(cec, RX_MSG_CMD, CEC_RX_READ);
+
+ len = mhdp_cec_read(cec, RX_MSG_LENGTH);
+ msg->len = len + 1;
+ dev_dbg(cec->dev, "RX MSG len =%d\n", len);
+
+ /* Read RX MSG bytes */
+ for (i = 0; i < msg->len; ++i) {
+ msg->msg[i] = (u8) mhdp_cec_read(cec, RX_MSG_DATA1 + (i * REG_ADDR_OFF));
+ dev_dbg(cec->dev, "RX MSG[%d]=0x%x\n", i, msg->msg[i]);
+ }
+
+ mhdp_cec_write(cec, RX_MSG_CMD, CEC_RX_STOP);
+
+ return true;
+}
+
+static u32 mhdp_cec_write_message(struct cdns_mhdp_cec *cec, struct cec_msg *msg)
+{
+ u8 i;
+
+ mhdp_cec_write(cec, TX_MSG_CMD, CEC_TX_STOP);
+
+ if (msg->len > CEC_MAX_MSG_SIZE) {
+ dev_err(cec->dev, "Invalid MSG size!\n");
+ return -EINVAL;
+ }
+
+ for (i = 0; i < msg->len; ++i)
+ printk("msg[%d]=0x%x\n",i, msg->msg[i]);
+
+ /* Write Message to register */
+ for (i = 0; i < msg->len; ++i) {
+ mhdp_cec_write(cec, TX_MSG_HEADER + (i * REG_ADDR_OFF),
+ msg->msg[i]);
+ }
+ /* Write Message Length (payload + opcode) */
+ mhdp_cec_write(cec, TX_MSG_LENGTH, msg->len - 1);
+
+ mhdp_cec_write(cec, TX_MSG_CMD, CEC_TX_TRANSMIT);
+
+ return true;
+}
+
+static int mhdp_cec_set_logical_addr(struct cdns_mhdp_cec *cec, u32 la)
+{
+ u8 la_reg;
+ u8 i;
+
+ if (la == CEC_LOG_ADDR_INVALID) {
+ /* invalid all LA address */
+ for (i = 0; i < CEC_MAX_LOG_ADDRS; i++)
+ mhdp_cec_write(cec, LOGICAL_ADDRESS_LA0 + (i * REG_ADDR_OFF), 0);
+ return 0;
+ }
+
+ /* In fact cdns mhdp cec could support max 5 La address */
+ for (i = 0; i < CEC_MAX_LOG_ADDRS; i++) {
+ la_reg = mhdp_cec_read(cec, LOGICAL_ADDRESS_LA0 + (i * REG_ADDR_OFF));
+ /* Check LA already used */
+ if (la_reg & 0x10)
+ continue;
+
+ if ((la_reg & 0xF) == la) {
+ dev_warn(cec->dev, "Warning. LA already in use.\n");
+ return 0;
+ }
+
+ la = (la & 0xF) | (1 << 4);
+
+ mhdp_cec_write(cec, LOGICAL_ADDRESS_LA0 + (i * REG_ADDR_OFF), la);
+ return 0;
+ }
+
+ dev_warn(cec->dev, "All LA in use\n");
+
+ return -ENXIO;
+}
+
+static int mhdp_cec_poll_worker(void *_cec)
+{
+ struct cdns_mhdp_cec *cec = (struct cdns_mhdp_cec *)_cec;
+ int num_rx_msgs, i;
+ int sts;
+
+ set_freezable();
+
+ for (;;) {
+ if (kthread_freezable_should_stop(NULL))
+ break;
+
+ /* Check TX State */
+ sts = mhdp_cec_read(cec, TX_MSG_STATUS);
+ switch (sts) {
+ case CEC_STS_SUCCESS:
+ cec_transmit_done(cec->adap, CEC_TX_STATUS_OK, 0, 0, 0,
+ 0);
+ mhdp_cec_write(cec, TX_MSG_CMD, CEC_TX_STOP);
+ break;
+ case CEC_STS_ERROR:
+ mhdp_cec_write(cec, TX_MSG_CMD, CEC_TX_STOP);
+ cec_transmit_done(cec->adap,
+ CEC_TX_STATUS_MAX_RETRIES |
+ CEC_TX_STATUS_NACK, 0, 1, 0, 0);
+ break;
+ case CEC_STS_BUSY:
+ default:
+ break;
+ }
+
+ /* Check RX State */
+ sts = mhdp_cec_read(cec, RX_MSG_STATUS);
+ num_rx_msgs = mhdp_cec_read(cec, NUM_OF_MSG_RX_BUF);
+ switch (sts) {
+ case CEC_STS_SUCCESS:
+ if (num_rx_msgs == 0xf)
+ num_rx_msgs = CEC_MAX_RX_MSGS;
+
+ if (num_rx_msgs > CEC_MAX_RX_MSGS) {
+ dev_err(cec->dev, "Error rx msg num %d\n",
+ num_rx_msgs);
+ mhdp_cec_clear_rx_buffer(cec);
+ break;
+ }
+
+ /* Rx FIFO Depth 2 RX MSG */
+ for (i = 0; i < num_rx_msgs; i++) {
+ mhdp_cec_read_message(cec);
+ cec->msg.rx_status = CEC_RX_STATUS_OK;
+ cec_received_msg(cec->adap, &cec->msg);
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (!kthread_should_stop())
+ schedule_timeout_idle(20);
+ }
+
+ return 0;
+}
+
+static int mhdp_cec_adap_enable(struct cec_adapter *adap, bool enable)
+{
+ struct cdns_mhdp_cec *cec = cec_get_drvdata(adap);
+
+ if (enable) {
+ mhdp_cec_write(cec, DB_L_TIMER, 0x10);
+ mhdp_cec_set_divider(cec);
+ } else
+ mhdp_cec_set_divider(cec);
+
+ return 0;
+}
+
+static int mhdp_cec_adap_log_addr(struct cec_adapter *adap, u8 addr)
+{
+ struct cdns_mhdp_cec *cec = cec_get_drvdata(adap);
+
+ return mhdp_cec_set_logical_addr(cec, addr);
+}
+
+static int mhdp_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
+ u32 signal_free_time, struct cec_msg *msg)
+{
+ struct cdns_mhdp_cec *cec = cec_get_drvdata(adap);
+
+ mhdp_cec_write_message(cec, msg);
+
+ return 0;
+}
+
+static const struct cec_adap_ops cdns_mhdp_cec_adap_ops = {
+ .adap_enable = mhdp_cec_adap_enable,
+ .adap_log_addr = mhdp_cec_adap_log_addr,
+ .adap_transmit = mhdp_cec_adap_transmit,
+};
+
+int cdns_mhdp_register_cec_driver(struct cdns_mhdp_cec *cec)
+{
+ int ret;
+
+ cec->adap = cec_allocate_adapter(&cdns_mhdp_cec_adap_ops, cec,
+ CEC_NAME,
+ CEC_CAP_PHYS_ADDR | CEC_CAP_LOG_ADDRS |
+ CEC_CAP_TRANSMIT | CEC_CAP_PASSTHROUGH
+ | CEC_CAP_RC, CEC_MAX_LOG_ADDRS);
+ ret = PTR_ERR_OR_ZERO(cec->adap);
+ if (ret)
+ return ret;
+ ret = cec_register_adapter(cec->adap, cec->dev);
+ if (ret) {
+ cec_delete_adapter(cec->adap);
+ return ret;
+ }
+
+ cec->cec_worker = kthread_create(mhdp_cec_poll_worker, cec, "cdns-mhdp-cec");
+ if (IS_ERR(cec->cec_worker))
+ dev_err(cec->dev, "failed create hdp cec thread\n");
+
+ wake_up_process(cec->cec_worker);
+
+ dev_dbg(cec->dev, "CEC successfuly probed\n");
+ return 0;
+}
+
+int cdns_mhdp_unregister_cec_driver(struct cdns_mhdp_cec *cec)
+{
+ if (cec->cec_worker) {
+ kthread_stop(cec->cec_worker);
+ cec->cec_worker = NULL;
+ }
+ cec_unregister_adapter(cec->adap);
+ return 0;
+}
+
+MODULE_AUTHOR("Sandor.Yu@NXP.com");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("NXP CDNS MHDP HDMI CEC driver");
diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp-common.c b/drivers/gpu/drm/bridge/cadence/cdns-mhdp-common.c
new file mode 100644
index 000000000000..35a37f6b6d6f
--- /dev/null
+++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp-common.c
@@ -0,0 +1,853 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Author: Chris Zhong <zyw@rock-chips.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/reset.h>
+
+#include <asm/unaligned.h>
+
+#include <drm/bridge/cdns-mhdp.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_print.h>
+#include <linux/regmap.h>
+
+#include "cdns-mhdp.h"
+
+#define CDNS_DP_SPDIF_CLK 200000000
+#define FW_ALIVE_TIMEOUT_US 1000000
+u32 cdns_mhdp_bus_read(struct cdns_mhdp_device *mhdp, u32 offset)
+{
+ u32 val;
+
+ mutex_lock(&mhdp->iolock);
+
+ if (mhdp->bus_type == BUS_TYPE_LOW4K_SAPB) {
+ /* Remap address to low 4K SAPB bus */
+ writel(offset >> 12, mhdp->regs_sec + 0xc);
+ val = readl((offset & 0xfff) + mhdp->regs_base);
+ } else if (mhdp->bus_type == BUS_TYPE_LOW4K_APB) {
+ /* Remap address to low 4K memory */
+ writel(offset >> 12, mhdp->regs_sec + 8);
+ val = readl((offset & 0xfff) + mhdp->regs_base);
+ } else if (mhdp->bus_type == BUS_TYPE_NORMAL_SAPB)
+ val = readl(mhdp->regs_sec + offset);
+ else
+ val = readl(mhdp->regs_base + offset);
+
+ mutex_unlock(&mhdp->iolock);
+
+ return val;
+}
+EXPORT_SYMBOL(cdns_mhdp_bus_read);
+
+void cdns_mhdp_bus_write(u32 val, struct cdns_mhdp_device *mhdp, u32 offset)
+{
+ mutex_lock(&mhdp->iolock);
+
+ if (mhdp->bus_type == BUS_TYPE_LOW4K_SAPB) {
+ /* Remap address to low 4K SAPB bus */
+ writel(offset >> 12, mhdp->regs_sec + 0xc);
+ writel(val, (offset & 0xfff) + mhdp->regs_base);
+ } else if (mhdp->bus_type == BUS_TYPE_LOW4K_APB) {
+ /* Remap address to low 4K memory */
+ writel(offset >> 12, mhdp->regs_sec + 8);
+ writel(val, (offset & 0xfff) + mhdp->regs_base);
+ } else if (mhdp->bus_type == BUS_TYPE_NORMAL_SAPB)
+ writel(val, mhdp->regs_sec + offset);
+ else
+ writel(val, mhdp->regs_base + offset);
+
+ mutex_unlock(&mhdp->iolock);
+}
+EXPORT_SYMBOL(cdns_mhdp_bus_write);
+
+void cdns_mhdp_set_fw_clk(struct cdns_mhdp_device *mhdp, unsigned long clk)
+{
+ cdns_mhdp_bus_write(clk / 1000000, mhdp, SW_CLK_H);
+}
+EXPORT_SYMBOL(cdns_mhdp_set_fw_clk);
+
+void cdns_mhdp_clock_reset(struct cdns_mhdp_device *mhdp)
+{
+ u32 val;
+
+ val = DPTX_FRMR_DATA_CLK_RSTN_EN |
+ DPTX_FRMR_DATA_CLK_EN |
+ DPTX_PHY_DATA_RSTN_EN |
+ DPTX_PHY_DATA_CLK_EN |
+ DPTX_PHY_CHAR_RSTN_EN |
+ DPTX_PHY_CHAR_CLK_EN |
+ SOURCE_AUX_SYS_CLK_RSTN_EN |
+ SOURCE_AUX_SYS_CLK_EN |
+ DPTX_SYS_CLK_RSTN_EN |
+ DPTX_SYS_CLK_EN |
+ CFG_DPTX_VIF_CLK_RSTN_EN |
+ CFG_DPTX_VIF_CLK_EN;
+ cdns_mhdp_bus_write(val, mhdp, SOURCE_DPTX_CAR);
+
+ val = SOURCE_PHY_RSTN_EN | SOURCE_PHY_CLK_EN;
+ cdns_mhdp_bus_write(val, mhdp, SOURCE_PHY_CAR);
+
+ val = SOURCE_PKT_SYS_RSTN_EN |
+ SOURCE_PKT_SYS_CLK_EN |
+ SOURCE_PKT_DATA_RSTN_EN |
+ SOURCE_PKT_DATA_CLK_EN;
+ cdns_mhdp_bus_write(val, mhdp, SOURCE_PKT_CAR);
+
+ val = SPDIF_CDR_CLK_RSTN_EN |
+ SPDIF_CDR_CLK_EN |
+ SOURCE_AIF_SYS_RSTN_EN |
+ SOURCE_AIF_SYS_CLK_EN |
+ SOURCE_AIF_CLK_RSTN_EN |
+ SOURCE_AIF_CLK_EN;
+ cdns_mhdp_bus_write(val, mhdp, SOURCE_AIF_CAR);
+
+ val = SOURCE_CIPHER_SYSTEM_CLK_RSTN_EN |
+ SOURCE_CIPHER_SYS_CLK_EN |
+ SOURCE_CIPHER_CHAR_CLK_RSTN_EN |
+ SOURCE_CIPHER_CHAR_CLK_EN;
+ cdns_mhdp_bus_write(val, mhdp, SOURCE_CIPHER_CAR);
+
+ val = SOURCE_CRYPTO_SYS_CLK_RSTN_EN |
+ SOURCE_CRYPTO_SYS_CLK_EN;
+ cdns_mhdp_bus_write(val, mhdp, SOURCE_CRYPTO_CAR);
+
+ /* enable Mailbox and PIF interrupt */
+ cdns_mhdp_bus_write(0, mhdp, APB_INT_MASK);
+}
+EXPORT_SYMBOL(cdns_mhdp_clock_reset);
+
+bool cdns_mhdp_check_alive(struct cdns_mhdp_device *mhdp)
+{
+ u32 alive, newalive;
+ u8 retries_left = 50;
+
+ alive = cdns_mhdp_bus_read(mhdp, KEEP_ALIVE);
+
+ while (retries_left--) {
+ msleep(1);
+
+ newalive = cdns_mhdp_bus_read(mhdp, KEEP_ALIVE);
+ if (alive == newalive)
+ continue;
+ return true;
+ }
+ return false;
+}
+EXPORT_SYMBOL(cdns_mhdp_check_alive);
+
+int mhdp_mailbox_read(struct cdns_mhdp_device *mhdp)
+{
+ int val, ret;
+
+ ret = mhdp_readx_poll_timeout(cdns_mhdp_bus_read, mhdp, MAILBOX_EMPTY_ADDR,
+ val, !val, MAILBOX_RETRY_US,
+ MAILBOX_TIMEOUT_US);
+ if (ret < 0)
+ return ret;
+
+ return cdns_mhdp_bus_read(mhdp, MAILBOX0_RD_DATA) & 0xff;
+}
+
+static int mhdp_mailbox_write(struct cdns_mhdp_device *mhdp, u8 val)
+{
+ int ret, full;
+
+ ret = mhdp_readx_poll_timeout(cdns_mhdp_bus_read, mhdp, MAILBOX_FULL_ADDR,
+ full, !full, MAILBOX_RETRY_US,
+ MAILBOX_TIMEOUT_US);
+ if (ret < 0)
+ return ret;
+
+ cdns_mhdp_bus_write(val, mhdp, MAILBOX0_WR_DATA);
+
+ return 0;
+}
+
+int cdns_mhdp_mailbox_validate_receive(struct cdns_mhdp_device *mhdp,
+ u8 module_id, u8 opcode,
+ u16 req_size)
+{
+ u32 mbox_size, i;
+ u8 header[4];
+ int ret;
+
+ /* read the header of the message */
+ for (i = 0; i < 4; i++) {
+ ret = mhdp_mailbox_read(mhdp);
+ if (ret < 0)
+ return ret;
+
+ header[i] = ret;
+ }
+
+ mbox_size = get_unaligned_be16(header + 2);
+
+ if (opcode != header[0] || module_id != header[1] ||
+ req_size != mbox_size) {
+ DRM_DEV_INFO(mhdp->dev,
+ "Hmmm spurious mailbox data maybe, cleaning out...%d:%d:%d vs %d:%d:%d\n",
+ module_id, opcode, req_size, header[1],
+ header[0], mbox_size);
+ /*
+ * If the message in mailbox is not what we want, we need to
+ * clear the mailbox by reading its contents.
+ */
+ for (i = 0; i < mbox_size; i++)
+ if (mhdp_mailbox_read(mhdp) < 0)
+ break;
+
+ return -EINVAL;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(cdns_mhdp_mailbox_validate_receive);
+
+int cdns_mhdp_mailbox_read_receive(struct cdns_mhdp_device *mhdp,
+ u8 *buff, u16 buff_size)
+{
+ u32 i;
+ int ret;
+
+ for (i = 0; i < buff_size; i++) {
+ ret = mhdp_mailbox_read(mhdp);
+ if (ret < 0)
+ return ret;
+
+ buff[i] = ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(cdns_mhdp_mailbox_read_receive);
+
+int cdns_mhdp_mailbox_send(struct cdns_mhdp_device *mhdp, u8 module_id,
+ u8 opcode, u16 size, u8 *message)
+{
+ u8 header[4];
+ int ret, i;
+
+ header[0] = opcode;
+ header[1] = module_id;
+ put_unaligned_be16(size, header + 2);
+
+ for (i = 0; i < 4; i++) {
+ ret = mhdp_mailbox_write(mhdp, header[i]);
+ if (ret)
+ return ret;
+ }
+
+ for (i = 0; i < size; i++) {
+ ret = mhdp_mailbox_write(mhdp, message[i]);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(cdns_mhdp_mailbox_send);
+
+int cdns_mhdp_reg_read(struct cdns_mhdp_device *mhdp, u32 addr)
+{
+ u8 msg[4], resp[8];
+ u32 val;
+ int ret;
+
+ mutex_lock(&mhdp->api_lock);
+
+ if (addr == 0) {
+ ret = -EINVAL;
+ goto err_reg_read;
+ }
+
+ put_unaligned_be32(addr, msg);
+
+ ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_GENERAL,
+ GENERAL_READ_REGISTER,
+ sizeof(msg), msg);
+ if (ret)
+ goto err_reg_read;
+
+ ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_GENERAL,
+ GENERAL_READ_REGISTER,
+ sizeof(resp));
+ if (ret)
+ goto err_reg_read;
+
+ ret = cdns_mhdp_mailbox_read_receive(mhdp, resp, sizeof(resp));
+ if (ret)
+ goto err_reg_read;
+
+ /* Returned address value should be the same as requested */
+ if (memcmp(msg, resp, sizeof(msg))) {
+ ret = -EINVAL;
+ goto err_reg_read;
+ }
+
+ val = get_unaligned_be32(resp + 4);
+
+ mutex_unlock(&mhdp->api_lock);
+ return val;
+err_reg_read:
+ mutex_unlock(&mhdp->api_lock);
+ DRM_DEV_ERROR(mhdp->dev, "Failed to read register.\n");
+
+mutex_unlock(&mhdp->api_lock);
+ return ret;
+}
+EXPORT_SYMBOL(cdns_mhdp_reg_read);
+
+int cdns_mhdp_reg_write(struct cdns_mhdp_device *mhdp, u32 addr, u32 val)
+{
+ int ret;
+ u8 msg[8];
+
+ mutex_lock(&mhdp->api_lock);
+
+ put_unaligned_be32(addr, msg);
+ put_unaligned_be32(val, msg + 4);
+
+ ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_GENERAL,
+ GENERAL_WRITE_REGISTER,
+ sizeof(msg), msg);
+ mutex_unlock(&mhdp->api_lock);
+return ret;
+}
+EXPORT_SYMBOL(cdns_mhdp_reg_write);
+
+int cdns_mhdp_reg_write_bit(struct cdns_mhdp_device *mhdp, u16 addr,
+ u8 start_bit, u8 bits_no, u32 val)
+{
+ int ret;
+ u8 field[8];
+
+ mutex_lock(&mhdp->api_lock);
+
+ put_unaligned_be16(addr, field);
+ field[2] = start_bit;
+ field[3] = bits_no;
+ put_unaligned_be32(val, field + 4);
+
+ ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
+ DPTX_WRITE_FIELD, sizeof(field), field);
+ mutex_unlock(&mhdp->api_lock);
+ return ret;
+}
+EXPORT_SYMBOL(cdns_mhdp_reg_write_bit);
+
+int cdns_mhdp_load_firmware(struct cdns_mhdp_device *mhdp, const u32 *i_mem,
+ u32 i_size, const u32 *d_mem, u32 d_size)
+{
+ u32 reg;
+ int i, ret;
+
+ /* reset ucpu before load firmware*/
+ cdns_mhdp_bus_write(APB_IRAM_PATH | APB_DRAM_PATH | APB_XT_RESET,
+ mhdp, APB_CTRL);
+
+ for (i = 0; i < i_size; i += 4)
+ cdns_mhdp_bus_write(*i_mem++, mhdp, ADDR_IMEM + i);
+
+ for (i = 0; i < d_size; i += 4)
+ cdns_mhdp_bus_write(*d_mem++, mhdp, ADDR_DMEM + i);
+
+ /* un-reset ucpu */
+ cdns_mhdp_bus_write(0, mhdp, APB_CTRL);
+
+ /* check the keep alive register to make sure fw working */
+ ret = mhdp_readx_poll_timeout(cdns_mhdp_bus_read, mhdp, KEEP_ALIVE,
+ reg, reg, 2000, FW_ALIVE_TIMEOUT_US);
+ if (ret < 0) {
+ DRM_DEV_ERROR(mhdp->dev, "failed to loaded the FW reg = %x\n",
+ reg);
+ return -EINVAL;
+ }
+
+ reg = cdns_mhdp_bus_read(mhdp, VER_L) & 0xff;
+ mhdp->fw_version = reg;
+ reg = cdns_mhdp_bus_read(mhdp, VER_H) & 0xff;
+ mhdp->fw_version |= reg << 8;
+ reg = cdns_mhdp_bus_read(mhdp, VER_LIB_L_ADDR) & 0xff;
+ mhdp->fw_version |= reg << 16;
+ reg = cdns_mhdp_bus_read(mhdp, VER_LIB_H_ADDR) & 0xff;
+ mhdp->fw_version |= reg << 24;
+
+ DRM_DEV_DEBUG(mhdp->dev, "firmware version: %x\n", mhdp->fw_version);
+
+ return 0;
+}
+EXPORT_SYMBOL(cdns_mhdp_load_firmware);
+
+int cdns_mhdp_set_firmware_active(struct cdns_mhdp_device *mhdp, bool enable)
+{
+ u8 msg[5];
+ int ret, i;
+
+ mutex_lock(&mhdp->api_lock);
+
+ msg[0] = GENERAL_MAIN_CONTROL;
+ msg[1] = MB_MODULE_ID_GENERAL;
+ msg[2] = 0;
+ msg[3] = 1;
+ msg[4] = enable ? FW_ACTIVE : FW_STANDBY;
+
+ for (i = 0; i < sizeof(msg); i++) {
+ ret = mhdp_mailbox_write(mhdp, msg[i]);
+ if (ret)
+ goto err_set_firmware_active;
+ }
+
+ /* read the firmware state */
+ for (i = 0; i < sizeof(msg); i++) {
+ ret = mhdp_mailbox_read(mhdp);
+ if (ret < 0)
+ goto err_set_firmware_active;
+
+ msg[i] = ret;
+ }
+
+ ret = 0;
+
+err_set_firmware_active:
+ if (ret < 0)
+ DRM_DEV_ERROR(mhdp->dev, "set firmware active failed\n");
+ mutex_unlock(&mhdp->api_lock);
+ return ret;
+}
+EXPORT_SYMBOL(cdns_mhdp_set_firmware_active);
+
+int cdns_mhdp_apb_conf(struct cdns_mhdp_device *mhdp, u8 sel)
+{
+ u8 status;
+ int ret;
+
+ mutex_lock(&mhdp->api_lock);
+
+ ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_GENERAL, GENERAL_BUS_SETTINGS,
+ sizeof(sel), &sel);
+ if (ret)
+ goto err_apb_conf;
+
+ ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_GENERAL,
+ GENERAL_BUS_SETTINGS, sizeof(status));
+ if (ret)
+ goto err_apb_conf;
+
+ ret = cdns_mhdp_mailbox_read_receive(mhdp, &status, sizeof(status));
+ if (ret)
+ goto err_apb_conf;
+
+ mutex_unlock(&mhdp->api_lock);
+
+ return status;
+
+err_apb_conf:
+ DRM_ERROR("apb conf failed: %d\n", ret);
+mutex_unlock(&mhdp->api_lock);
+ return ret;
+}
+EXPORT_SYMBOL(cdns_mhdp_apb_conf);
+
+int cdns_mhdp_set_host_cap(struct cdns_mhdp_device *mhdp)
+{
+ u8 msg[8];
+ int ret, lane;
+
+ mutex_lock(&mhdp->api_lock);
+
+ msg[0] = drm_dp_link_rate_to_bw_code(mhdp->dp.rate);
+ msg[1] = mhdp->dp.num_lanes | SCRAMBLER_EN;
+ if (mhdp->dp.link_training_type == DP_TX_FULL_LINK_TRAINING) {
+ msg[2] = (VOLTAGE_LEVEL_3 & 0x3) | (mhdp->dp.force_vswing & 0x1) << 4;
+ msg[3] = (PRE_EMPHASIS_LEVEL_2 & 0x3) | (mhdp->dp.force_preemphasis & 0x1) << 4;
+ } else {
+ msg[2] = 0;
+ msg[3] = 0;
+ for (lane = 0; lane < mhdp->dp.num_lanes; lane++) {
+ msg[2] |= (mhdp->dp.vswing[lane] & 0x3) << (2 * lane);
+ msg[3] |= (mhdp->dp.preemphasis[lane] & 0x3) << (2 * lane);
+ }
+ }
+ msg[4] = PTS1 | PTS2 | PTS3 | PTS4;
+ msg[5] = mhdp->dp.link_training_type;
+ msg[6] = mhdp->lane_mapping;
+ msg[7] = ENHANCED;
+
+ ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
+ DPTX_SET_HOST_CAPABILITIES,
+ sizeof(msg), msg);
+ if (ret)
+ goto err_set_host_cap;
+
+/* TODO Sandor */
+// ret = cdns_mhdp_reg_write(mhdp, DP_AUX_SWAP_INVERSION_CONTROL,
+// AUX_HOST_INVERT);
+
+err_set_host_cap:
+ if (ret)
+ DRM_DEV_ERROR(mhdp->dev, "set host cap failed: %d\n", ret);
+ mutex_unlock(&mhdp->api_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL(cdns_mhdp_set_host_cap);
+
+int cdns_mhdp_event_config(struct cdns_mhdp_device *mhdp)
+{
+ u8 msg[5];
+ int ret;
+
+ mutex_lock(&mhdp->api_lock);
+
+ memset(msg, 0, sizeof(msg));
+
+ msg[0] = MHDP_EVENT_ENABLE_HPD | MHDP_EVENT_ENABLE_TRAINING;
+
+ ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
+ DPTX_ENABLE_EVENT, sizeof(msg), msg);
+ if (ret)
+ DRM_DEV_ERROR(mhdp->dev, "set event config failed: %d\n", ret);
+
+ mutex_unlock(&mhdp->api_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL(cdns_mhdp_event_config);
+
+u32 cdns_mhdp_get_event(struct cdns_mhdp_device *mhdp)
+{
+ return cdns_mhdp_bus_read(mhdp, SW_EVENTS0);
+}
+EXPORT_SYMBOL(cdns_mhdp_get_event);
+
+int cdns_mhdp_read_hpd(struct cdns_mhdp_device *mhdp)
+{
+ u8 status;
+ int ret;
+
+ mutex_lock(&mhdp->api_lock);
+
+ ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_GENERAL, GENERAL_GET_HPD_STATE,
+ 0, NULL);
+ if (ret)
+ goto err_get_hpd;
+
+ ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_GENERAL,
+ GENERAL_GET_HPD_STATE, sizeof(status));
+ if (ret)
+ goto err_get_hpd;
+
+ ret = cdns_mhdp_mailbox_read_receive(mhdp, &status, sizeof(status));
+ if (ret)
+ goto err_get_hpd;
+
+ mutex_unlock(&mhdp->api_lock);
+
+ return status;
+
+err_get_hpd:
+ DRM_ERROR("read hpd failed: %d\n", ret);
+ mutex_unlock(&mhdp->api_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL(cdns_mhdp_read_hpd);
+
+int cdns_mhdp_get_edid_block(void *data, u8 *edid,
+ unsigned int block, size_t length)
+{
+ struct cdns_mhdp_device *mhdp = data;
+ u8 msg[2], reg[2], i;
+ int ret;
+
+ mutex_lock(&mhdp->api_lock);
+
+ for (i = 0; i < 4; i++) {
+ msg[0] = block / 2;
+ msg[1] = block % 2;
+
+ ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
+ DPTX_GET_EDID, sizeof(msg), msg);
+ if (ret)
+ continue;
+
+ ret = cdns_mhdp_mailbox_validate_receive(mhdp,
+ MB_MODULE_ID_DP_TX,
+ DPTX_GET_EDID,
+ sizeof(reg) + length);
+ if (ret)
+ continue;
+
+ ret = cdns_mhdp_mailbox_read_receive(mhdp, reg, sizeof(reg));
+ if (ret)
+ continue;
+
+ ret = cdns_mhdp_mailbox_read_receive(mhdp, edid, length);
+ if (ret)
+ continue;
+
+ if (reg[0] == length && reg[1] == block / 2)
+ break;
+ }
+
+ if (ret)
+ DRM_DEV_ERROR(mhdp->dev, "get block[%d] edid failed: %d\n",
+ block, ret);
+
+ mutex_unlock(&mhdp->api_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL(cdns_mhdp_get_edid_block);
+
+int cdns_mhdp_set_video_status(struct cdns_mhdp_device *mhdp, int active)
+{
+ u8 msg;
+ int ret;
+
+ mutex_lock(&mhdp->api_lock);
+
+ msg = !!active;
+
+ ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
+ DPTX_SET_VIDEO, sizeof(msg), &msg);
+ if (ret)
+ DRM_DEV_ERROR(mhdp->dev, "set video status failed: %d\n", ret);
+
+ mutex_unlock(&mhdp->api_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL(cdns_mhdp_set_video_status);
+
+static int mhdp_get_msa_misc(struct video_info *video,
+ struct drm_display_mode *mode)
+{
+ u32 msa_misc;
+ u8 val[2] = {0};
+
+ switch (video->color_fmt) {
+ case PXL_RGB:
+ case Y_ONLY:
+ val[0] = 0;
+ break;
+ /* set YUV default color space conversion to BT601 */
+ case YCBCR_4_4_4:
+ val[0] = 6 + BT_601 * 8;
+ break;
+ case YCBCR_4_2_2:
+ val[0] = 5 + BT_601 * 8;
+ break;
+ case YCBCR_4_2_0:
+ val[0] = 5;
+ break;
+ }
+
+ switch (video->color_depth) {
+ case 6:
+ val[1] = 0;
+ break;
+ case 8:
+ val[1] = 1;
+ break;
+ case 10:
+ val[1] = 2;
+ break;
+ case 12:
+ val[1] = 3;
+ break;
+ case 16:
+ val[1] = 4;
+ break;
+ }
+
+ msa_misc = 2 * val[0] + 32 * val[1] +
+ ((video->color_fmt == Y_ONLY) ? (1 << 14) : 0);
+
+ return msa_misc;
+}
+
+int cdns_mhdp_config_video(struct cdns_mhdp_device *mhdp)
+{
+ struct video_info *video = &mhdp->video_info;
+ struct drm_display_mode *mode = &mhdp->mode;
+ u64 symbol;
+ u32 val, link_rate, rem;
+ u8 bit_per_pix, tu_size_reg = TU_SIZE;
+ int ret;
+
+ bit_per_pix = (video->color_fmt == YCBCR_4_2_2) ?
+ (video->color_depth * 2) : (video->color_depth * 3);
+
+ link_rate = mhdp->dp.rate / 1000;
+
+ ret = cdns_mhdp_reg_write(mhdp, BND_HSYNC2VSYNC, VIF_BYPASS_INTERLACE);
+ if (ret)
+ goto err_config_video;
+
+ ret = cdns_mhdp_reg_write(mhdp, HSYNC2VSYNC_POL_CTRL, 0);
+ if (ret)
+ goto err_config_video;
+
+ /*
+ * get a best tu_size and valid symbol:
+ * 1. chose Lclk freq(162Mhz, 270Mhz, 540Mhz), set TU to 32
+ * 2. calculate VS(valid symbol) = TU * Pclk * Bpp / (Lclk * Lanes)
+ * 3. if VS > *.85 or VS < *.1 or VS < 2 or TU < VS + 4, then set
+ * TU += 2 and repeat 2nd step.
+ */
+ do {
+ tu_size_reg += 2;
+ symbol = (u64) tu_size_reg * mode->clock * bit_per_pix;
+ do_div(symbol, mhdp->dp.num_lanes * link_rate * 8);
+ rem = do_div(symbol, 1000);
+ if (tu_size_reg > 64) {
+ ret = -EINVAL;
+ DRM_DEV_ERROR(mhdp->dev,
+ "tu error, clk:%d, lanes:%d, rate:%d\n",
+ mode->clock, mhdp->dp.num_lanes,
+ link_rate);
+ goto err_config_video;
+ }
+ } while ((symbol <= 1) || (tu_size_reg - symbol < 4) ||
+ (rem > 850) || (rem < 100));
+
+ val = symbol + (tu_size_reg << 8);
+ val |= TU_CNT_RST_EN;
+ ret = cdns_mhdp_reg_write(mhdp, DP_FRAMER_TU, val);
+ if (ret)
+ goto err_config_video;
+
+ /* set the FIFO Buffer size */
+ val = div_u64(mode->clock * (symbol + 1), 1000) + link_rate;
+ val /= (mhdp->dp.num_lanes * link_rate);
+ val = div_u64(8 * (symbol + 1), bit_per_pix) - val;
+ val += 2;
+ ret = cdns_mhdp_reg_write(mhdp, DP_VC_TABLE(15), val);
+
+ switch (video->color_depth) {
+ case 6:
+ val = BCS_6;
+ break;
+ case 8:
+ val = BCS_8;
+ break;
+ case 10:
+ val = BCS_10;
+ break;
+ case 12:
+ val = BCS_12;
+ break;
+ case 16:
+ val = BCS_16;
+ break;
+ }
+
+ val += video->color_fmt << 8;
+ ret = cdns_mhdp_reg_write(mhdp, DP_FRAMER_PXL_REPR, val);
+ if (ret)
+ goto err_config_video;
+
+ val = video->h_sync_polarity ? DP_FRAMER_SP_HSP : 0;
+ val |= video->v_sync_polarity ? DP_FRAMER_SP_VSP : 0;
+ ret = cdns_mhdp_reg_write(mhdp, DP_FRAMER_SP, val);
+ if (ret)
+ goto err_config_video;
+
+ val = (mode->hsync_start - mode->hdisplay) << 16;
+ val |= mode->htotal - mode->hsync_end;
+ ret = cdns_mhdp_reg_write(mhdp, DP_FRONT_BACK_PORCH, val);
+ if (ret)
+ goto err_config_video;
+
+ val = mode->hdisplay * bit_per_pix / 8;
+ ret = cdns_mhdp_reg_write(mhdp, DP_BYTE_COUNT, val);
+ if (ret)
+ goto err_config_video;
+
+ val = mode->htotal | ((mode->htotal - mode->hsync_start) << 16);
+ ret = cdns_mhdp_reg_write(mhdp, MSA_HORIZONTAL_0, val);
+ if (ret)
+ goto err_config_video;
+
+ val = mode->hsync_end - mode->hsync_start;
+ val |= (mode->hdisplay << 16) | (video->h_sync_polarity << 15);
+ ret = cdns_mhdp_reg_write(mhdp, MSA_HORIZONTAL_1, val);
+ if (ret)
+ goto err_config_video;
+
+ val = mode->vtotal;
+ val |= (mode->vtotal - mode->vsync_start) << 16;
+ ret = cdns_mhdp_reg_write(mhdp, MSA_VERTICAL_0, val);
+ if (ret)
+ goto err_config_video;
+
+ val = mode->vsync_end - mode->vsync_start;
+ val |= (mode->vdisplay << 16) | (video->v_sync_polarity << 15);
+ ret = cdns_mhdp_reg_write(mhdp, MSA_VERTICAL_1, val);
+ if (ret)
+ goto err_config_video;
+
+ val = mhdp_get_msa_misc(video, mode);
+ ret = cdns_mhdp_reg_write(mhdp, MSA_MISC, val);
+ if (ret)
+ goto err_config_video;
+
+ ret = cdns_mhdp_reg_write(mhdp, STREAM_CONFIG, 1);
+ if (ret)
+ goto err_config_video;
+
+ val = mode->hsync_end - mode->hsync_start;
+ val |= mode->hdisplay << 16;
+ ret = cdns_mhdp_reg_write(mhdp, DP_HORIZONTAL, val);
+ if (ret)
+ goto err_config_video;
+
+ val = mode->vdisplay;
+ val |= (mode->vtotal - mode->vsync_start) << 16;
+ ret = cdns_mhdp_reg_write(mhdp, DP_VERTICAL_0, val);
+ if (ret)
+ goto err_config_video;
+
+ val = mode->vtotal;
+ ret = cdns_mhdp_reg_write(mhdp, DP_VERTICAL_1, val);
+ if (ret)
+ goto err_config_video;
+
+ ret = cdns_mhdp_reg_write_bit(mhdp, DP_VB_ID, 2, 1, 0);
+
+err_config_video:
+ if (ret)
+ DRM_DEV_ERROR(mhdp->dev, "config video failed: %d\n", ret);
+ return ret;
+}
+EXPORT_SYMBOL(cdns_mhdp_config_video);
+
+int cdns_phy_reg_write(struct cdns_mhdp_device *mhdp, u32 addr, u32 val)
+{
+ return cdns_mhdp_reg_write(mhdp, ADDR_PHY_AFE + (addr << 2), val);
+}
+EXPORT_SYMBOL(cdns_phy_reg_write);
+
+u32 cdns_phy_reg_read(struct cdns_mhdp_device *mhdp, u32 addr)
+{
+ return cdns_mhdp_reg_read(mhdp, ADDR_PHY_AFE + (addr << 2));
+}
+EXPORT_SYMBOL(cdns_phy_reg_read);
diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp-dp.c b/drivers/gpu/drm/bridge/cadence/cdns-mhdp-dp.c
new file mode 100644
index 000000000000..299e533d6b52
--- /dev/null
+++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp-dp.c
@@ -0,0 +1,278 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <asm/unaligned.h>
+#include <drm/bridge/cdns-mhdp.h>
+#include <drm/drm_print.h>
+#include <linux/io.h>
+
+#define LINK_TRAINING_TIMEOUT_MS 500
+#define LINK_TRAINING_RETRY_MS 20
+
+int cdns_mhdp_dpcd_read(struct cdns_mhdp_device *mhdp,
+ u32 addr, u8 *data, u16 len)
+{
+ u8 msg[5], reg[5];
+ int ret;
+
+ put_unaligned_be16(len, msg);
+ put_unaligned_be24(addr, msg + 2);
+
+ ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
+ DPTX_READ_DPCD, sizeof(msg), msg);
+ if (ret)
+ goto err_dpcd_read;
+
+ ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_DP_TX,
+ DPTX_READ_DPCD,
+ sizeof(reg) + len);
+ if (ret)
+ goto err_dpcd_read;
+
+ ret = cdns_mhdp_mailbox_read_receive(mhdp, reg, sizeof(reg));
+ if (ret)
+ goto err_dpcd_read;
+
+ ret = cdns_mhdp_mailbox_read_receive(mhdp, data, len);
+
+err_dpcd_read:
+ return ret;
+}
+EXPORT_SYMBOL(cdns_mhdp_dpcd_read);
+
+int cdns_mhdp_i2c_read(struct cdns_mhdp_device *mhdp, u8 addr, u8 *data,
+ u16 len, u8 mot, u16 *respLength)
+{
+ u8 msg[5], reg[3];
+ int ret;
+
+ put_unaligned_be16(len, msg);
+ msg[2] = addr;
+ msg[3] = mot;
+
+ ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
+ DPTX_I2C_READ, sizeof(msg), msg);
+ if (ret)
+ goto err_i2c_read;
+
+ ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_DP_TX,
+ DPTX_I2C_READ,
+ sizeof(reg) + len);
+ if (ret)
+ goto err_i2c_read;
+
+ ret = cdns_mhdp_mailbox_read_receive(mhdp, reg, sizeof(reg));
+ if (ret)
+ goto err_i2c_read;
+
+ ret = cdns_mhdp_mailbox_read_receive(mhdp, data, len);
+ *respLength = (reg[0] << 8u) + reg[1];
+
+err_i2c_read:
+ if (ret)
+ DRM_DEV_ERROR(mhdp->dev, "i2c read failed: %d\n", ret);
+ return ret;
+}
+EXPORT_SYMBOL(cdns_mhdp_i2c_read);
+
+int cdns_mhdp_dpcd_write(struct cdns_mhdp_device *mhdp, u32 addr, u8 value)
+{
+ u8 msg[6], reg[5];
+ int ret;
+
+ put_unaligned_be16(1, msg);
+ put_unaligned_be24(addr, msg + 2);
+ msg[5] = value;
+
+ ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
+ DPTX_WRITE_DPCD, sizeof(msg), msg);
+ if (ret)
+ goto err_dpcd_write;
+
+ ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_DP_TX,
+ DPTX_WRITE_DPCD, sizeof(reg));
+ if (ret)
+ goto err_dpcd_write;
+
+ ret = cdns_mhdp_mailbox_read_receive(mhdp, reg, sizeof(reg));
+ if (ret)
+ goto err_dpcd_write;
+
+ if (addr != get_unaligned_be24(reg + 2))
+ ret = -EINVAL;
+
+err_dpcd_write:
+ if (ret)
+ DRM_DEV_ERROR(mhdp->dev, "dpcd write failed: %d\n", ret);
+ return ret;
+}
+EXPORT_SYMBOL(cdns_mhdp_dpcd_write);
+
+int cdns_mhdp_i2c_write(struct cdns_mhdp_device *mhdp, u8 addr, u8 *value,
+ u8 mot, u16 len, u16 *respLength)
+{
+ u8 msg[4+DP_AUX_MAX_PAYLOAD_BYTES], reg[3];
+ int ret;
+
+ put_unaligned_be16(len, msg);
+ msg[2] = addr;
+ msg[3] = mot;
+ memcpy(&msg[4], value, len);
+
+
+ ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
+ DPTX_I2C_WRITE, sizeof(msg), msg);
+ if (ret)
+ goto err_i2c_write;
+
+ ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_DP_TX,
+ DPTX_I2C_WRITE, sizeof(reg));
+ if (ret)
+ goto err_i2c_write;
+
+ ret = cdns_mhdp_mailbox_read_receive(mhdp, reg, sizeof(reg));
+ if (ret)
+ goto err_i2c_write;
+
+ if (addr != reg[2])
+ ret = -EINVAL;
+
+ *respLength = (reg[0]<<8u) + reg[1];
+
+err_i2c_write:
+ if (ret)
+ DRM_DEV_ERROR(mhdp->dev, "i2c write failed: %d\n", ret);
+ return ret;
+}
+EXPORT_SYMBOL(cdns_mhdp_i2c_write);
+
+
+int cdns_mhdp_get_last_i2c_status(struct cdns_mhdp_device *mhdp, u8 *resp)
+{
+ u8 status[1];
+ int ret;
+
+ ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
+ DPTX_GET_LAST_I2C_STATUS, 0, NULL);
+ if (ret)
+ goto err_get_i2c_status;
+
+ ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_DP_TX,
+ DPTX_GET_LAST_I2C_STATUS,
+ sizeof(status));
+ if (ret)
+ goto err_get_i2c_status;
+
+ ret = cdns_mhdp_mailbox_read_receive(mhdp, status, sizeof(status));
+ if (ret)
+ goto err_get_i2c_status;
+
+ *resp = status[0];
+
+err_get_i2c_status:
+ if (ret)
+ DRM_DEV_ERROR(mhdp->dev, "get i2c status failed: %d\n",
+ ret);
+ return ret;
+}
+EXPORT_SYMBOL(cdns_mhdp_get_last_i2c_status);
+
+static int cdns_mhdp_training_start(struct cdns_mhdp_device *mhdp)
+{
+ unsigned long timeout;
+ u8 msg, event[2];
+ int ret;
+
+ msg = LINK_TRAINING_RUN;
+
+ /* start training */
+ ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
+ DPTX_TRAINING_CONTROL, sizeof(msg), &msg);
+ if (ret)
+ goto err_training_start;
+
+ timeout = jiffies + msecs_to_jiffies(LINK_TRAINING_TIMEOUT_MS);
+ while (time_before(jiffies, timeout)) {
+ msleep(LINK_TRAINING_RETRY_MS);
+ ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
+ DPTX_READ_EVENT, 0, NULL);
+ if (ret)
+ goto err_training_start;
+
+ ret = cdns_mhdp_mailbox_validate_receive(mhdp,
+ MB_MODULE_ID_DP_TX,
+ DPTX_READ_EVENT,
+ sizeof(event));
+ if (ret)
+ goto err_training_start;
+
+ ret = cdns_mhdp_mailbox_read_receive(mhdp, event,
+ sizeof(event));
+ if (ret)
+ goto err_training_start;
+
+ if (event[1] & CLK_RECOVERY_FAILED)
+ DRM_DEV_ERROR(mhdp->dev, "clock recovery failed\n");
+ else if (event[1] & EQ_PHASE_FINISHED)
+ return 0;
+ }
+
+ ret = -ETIMEDOUT;
+
+err_training_start:
+ DRM_DEV_ERROR(mhdp->dev, "training failed: %d\n", ret);
+ return ret;
+}
+
+static int cdns_mhdp_get_training_status(struct cdns_mhdp_device *mhdp)
+{
+ u8 status[13];
+ int ret;
+
+ ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_DP_TX,
+ DPTX_READ_LINK_STAT, 0, NULL);
+ if (ret)
+ goto err_get_training_status;
+
+ ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_DP_TX,
+ DPTX_READ_LINK_STAT,
+ sizeof(status));
+ if (ret)
+ goto err_get_training_status;
+
+ ret = cdns_mhdp_mailbox_read_receive(mhdp, status, sizeof(status));
+ if (ret)
+ goto err_get_training_status;
+
+ mhdp->dp.rate = drm_dp_bw_code_to_link_rate(status[0]);
+ mhdp->dp.num_lanes = status[1];
+
+err_get_training_status:
+ if (ret)
+ DRM_DEV_ERROR(mhdp->dev, "get training status failed: %d\n",
+ ret);
+ return ret;
+}
+
+int cdns_mhdp_train_link(struct cdns_mhdp_device *mhdp)
+{
+ int ret;
+
+ ret = cdns_mhdp_training_start(mhdp);
+ if (ret) {
+ DRM_DEV_ERROR(mhdp->dev, "Failed to start training %d\n",
+ ret);
+ return ret;
+ }
+
+ ret = cdns_mhdp_get_training_status(mhdp);
+ if (ret) {
+ DRM_DEV_ERROR(mhdp->dev, "Failed to get training stat %d\n",
+ ret);
+ return ret;
+ }
+
+ DRM_DEV_DEBUG_KMS(mhdp->dev, "rate:0x%x, lanes:%d\n", mhdp->dp.rate,
+ mhdp->dp.num_lanes);
+ return ret;
+}
+EXPORT_SYMBOL(cdns_mhdp_train_link);
diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp-hdcp.c b/drivers/gpu/drm/bridge/cadence/cdns-mhdp-hdcp.c
new file mode 100644
index 000000000000..d999a53ddf0a
--- /dev/null
+++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp-hdcp.c
@@ -0,0 +1,350 @@
+/*
+ * Cadence HDCP API driver
+ *
+ * Copyright 2021 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <asm/unaligned.h>
+#include <drm/bridge/cdns-mhdp.h>
+#include <drm/drm_print.h>
+
+#include "cdns-mhdp.h"
+
+static u32 mhdp_hdcp_bus_read(struct cdns_mhdp_device *mhdp, u32 offset)
+{
+ u32 val;
+
+ mutex_lock(&mhdp->iolock);
+
+ if (mhdp->bus_type == BUS_TYPE_LOW4K_APB) {
+ /* Remap address to low 4K APB bus */
+ writel(offset >> 12, mhdp->regs_sec + 8);
+ val = readl((offset & 0xfff) + mhdp->regs_base);
+ } else if (mhdp->bus_type == BUS_TYPE_NORMAL_APB)
+ val = readl(mhdp->regs_sec + offset);
+ else
+ val = readl(mhdp->regs_base + offset);
+
+ mutex_unlock(&mhdp->iolock);
+
+ return val;
+}
+
+static void mhdp_hdcp_bus_write(u32 val, struct cdns_mhdp_device *mhdp, u32 offset)
+{
+ mutex_lock(&mhdp->iolock);
+
+ if (mhdp->bus_type == BUS_TYPE_LOW4K_APB) {
+ /* Remap address to low 4K APB bus */
+ writel(offset >> 12, mhdp->regs_sec + 8);
+ writel(val, (offset & 0xfff) + mhdp->regs_base);
+ } else if (mhdp->bus_type == BUS_TYPE_NORMAL_APB)
+ writel(val, mhdp->regs_sec + offset);
+
+ mutex_unlock(&mhdp->iolock);
+}
+
+static int mhdp_hdcp_mailbox_read(struct cdns_mhdp_device *mhdp)
+{
+ int val, ret;
+
+ ret = mhdp_readx_poll_timeout(mhdp_hdcp_bus_read, mhdp, MAILBOX_EMPTY_ADDR,
+ val, !val, MAILBOX_RETRY_US,
+ MAILBOX_TIMEOUT_US);
+ if (ret < 0)
+ return ret;
+
+ return mhdp_hdcp_bus_read(mhdp, MAILBOX0_RD_DATA) & 0xff;
+}
+
+static int mhdp_hdcp_mailbox_write(struct cdns_mhdp_device *mhdp, u8 val)
+{
+ int ret, full;
+
+ ret = mhdp_readx_poll_timeout(mhdp_hdcp_bus_read, mhdp, MAILBOX_FULL_ADDR,
+ full, !full, MAILBOX_RETRY_US,
+ MAILBOX_TIMEOUT_US);
+ if (ret < 0)
+ return ret;
+
+ mhdp_hdcp_bus_write(val, mhdp, MAILBOX0_WR_DATA);
+
+ return 0;
+}
+
+static int mhdp_hdcp_mailbox_validate_receive(struct cdns_mhdp_device *mhdp,
+ u8 module_id, u8 opcode, u16 req_size)
+{
+ u32 mbox_size, i;
+ u8 header[4];
+ int ret;
+
+ /* read the header of the message */
+ for (i = 0; i < 4; i++) {
+ ret = mhdp_hdcp_mailbox_read(mhdp);
+ if (ret < 0)
+ return ret;
+
+ header[i] = ret;
+ }
+
+ mbox_size = get_unaligned_be16(header + 2);
+
+ if (opcode != header[0] || module_id != header[1] ||
+ req_size != mbox_size) {
+ /*
+ * If the message in mailbox is not what we want, we need to
+ * clear the mailbox by reading its contents.
+ */
+ for (i = 0; i < mbox_size; i++)
+ if (mhdp_hdcp_mailbox_read(mhdp) < 0)
+ break;
+
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int mhdp_hdcp_mailbox_read_receive(struct cdns_mhdp_device *mhdp,
+ u8 *buff, u16 buff_size)
+{
+ u32 i;
+ int ret;
+
+ for (i = 0; i < buff_size; i++) {
+ ret = mhdp_hdcp_mailbox_read(mhdp);
+ if (ret < 0)
+ return ret;
+
+ buff[i] = ret;
+ }
+
+ return 0;
+}
+
+static int mhdp_hdcp_mailbox_send(struct cdns_mhdp_device *mhdp, u8 module_id,
+ u8 opcode, u16 size, u8 *message)
+{
+ u8 header[4];
+ int ret, i;
+
+ header[0] = opcode;
+ header[1] = module_id;
+ put_unaligned_be16(size, header + 2);
+
+ for (i = 0; i < 4; i++) {
+ ret = mhdp_hdcp_mailbox_write(mhdp, header[i]);
+ if (ret)
+ return ret;
+ }
+
+ for (i = 0; i < size; i++) {
+ ret = mhdp_hdcp_mailbox_write(mhdp, message[i]);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+/* HDCP API */
+int cdns_mhdp_hdcp_tx_config(struct cdns_mhdp_device *mhdp, u8 config)
+{
+ int ret;
+
+ mutex_lock(&mhdp->api_lock);
+ ret = mhdp_hdcp_mailbox_send(mhdp, MB_MODULE_ID_HDCP_TX,
+ HDCP_TX_CONFIGURATION, sizeof(config), &config);
+
+ mutex_unlock(&mhdp->api_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL(cdns_mhdp_hdcp_tx_config);
+
+int cdns_mhdp_hdcp2_tx_respond_km(struct cdns_mhdp_device *mhdp,
+ u8 *msg, u16 len)
+{
+ int ret;
+
+ mutex_lock(&mhdp->api_lock);
+
+ ret = mhdp_hdcp_mailbox_send(mhdp, MB_MODULE_ID_HDCP_TX,
+ HDCP2_TX_RESPOND_KM, len, msg);
+ mutex_unlock(&mhdp->api_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL(cdns_mhdp_hdcp2_tx_respond_km);
+
+int cdns_mhdp_hdcp_tx_status_req(struct cdns_mhdp_device *mhdp,
+ u8 *status, u16 len)
+{
+ int ret;
+
+ mutex_lock(&mhdp->api_lock);
+
+ ret = mhdp_hdcp_mailbox_send(mhdp, MB_MODULE_ID_HDCP_TX,
+ HDCP_TX_STATUS_CHANGE, 0, NULL);
+ if (ret)
+ goto err_tx_req;
+
+ ret = mhdp_hdcp_mailbox_validate_receive(mhdp, MB_MODULE_ID_HDCP_TX,
+ HDCP_TX_STATUS_CHANGE, len);
+ if (ret)
+ goto err_tx_req;
+
+ ret = mhdp_hdcp_mailbox_read_receive(mhdp, status, len);
+ if (ret)
+ goto err_tx_req;
+
+err_tx_req:
+ if (ret)
+ DRM_ERROR("hdcp tx status req failed: %d\n", ret);
+ mutex_unlock(&mhdp->api_lock);
+ return ret;
+}
+EXPORT_SYMBOL(cdns_mhdp_hdcp_tx_status_req);
+
+int cdns_mhdp_hdcp2_tx_is_km_stored_req(struct cdns_mhdp_device *mhdp, u8 *data, u16 len)
+{
+ int ret;
+
+ mutex_lock(&mhdp->api_lock);
+
+ ret = mhdp_hdcp_mailbox_send(mhdp, MB_MODULE_ID_HDCP_TX,
+ HDCP2_TX_IS_KM_STORED, 0, NULL);
+ if (ret)
+ goto err_is_km;
+
+ ret = mhdp_hdcp_mailbox_validate_receive(mhdp, MB_MODULE_ID_HDCP_TX,
+ HDCP2_TX_IS_KM_STORED, len);
+ if (ret)
+ goto err_is_km;
+
+ ret = mhdp_hdcp_mailbox_read_receive(mhdp, data, len);
+
+err_is_km:
+ if (ret)
+ DRM_ERROR("hdcp2 tx is km stored req failed: %d\n", ret);
+ mutex_unlock(&mhdp->api_lock);
+ return ret;
+}
+EXPORT_SYMBOL(cdns_mhdp_hdcp2_tx_is_km_stored_req);
+
+int cdns_mhdp_hdcp2_tx_store_km(struct cdns_mhdp_device *mhdp,
+ u8 *resp, u16 len)
+{
+ int ret;
+
+ mutex_lock(&mhdp->api_lock);
+
+ ret = mhdp_hdcp_mailbox_send(mhdp, MB_MODULE_ID_HDCP_TX,
+ HDCP2_TX_STORE_KM, 0, NULL);
+ if (ret)
+ goto err_store_km;
+
+ ret = mhdp_hdcp_mailbox_validate_receive(mhdp, MB_MODULE_ID_HDCP_TX,
+ HDCP2_TX_STORE_KM, len);
+ if (ret)
+ goto err_store_km;
+
+ ret = mhdp_hdcp_mailbox_read_receive(mhdp, resp, len);
+
+err_store_km:
+ mutex_unlock(&mhdp->api_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL(cdns_mhdp_hdcp2_tx_store_km);
+
+int cdns_mhdp_hdcp_tx_is_receiver_id_valid(struct cdns_mhdp_device *mhdp,
+ u8 *rx_id, u8 *num)
+{
+ u32 mbox_size, i;
+ u8 header[4];
+ u8 temp;
+ int ret;
+
+ mutex_lock(&mhdp->api_lock);
+
+ ret = mhdp_hdcp_mailbox_send(mhdp, MB_MODULE_ID_HDCP_TX,
+ HDCP_TX_IS_RECEIVER_ID_VALID, 0, NULL);
+ if (ret)
+ goto err_rx_id;
+
+ /* read the header of the message */
+ for (i = 0; i < 4; i++) {
+ ret = mhdp_hdcp_mailbox_read(mhdp);
+ if (ret < 0) {
+
+ mutex_unlock(&mhdp->api_lock);
+ return ret;
+ }
+
+ header[i] = ret;
+ }
+
+ mbox_size = get_unaligned_be16(header + 2);
+
+ if (header[0] != HDCP_TX_IS_RECEIVER_ID_VALID ||
+ header[1] != MB_MODULE_ID_HDCP_TX){
+
+ mutex_unlock(&mhdp->api_lock);
+ return -EINVAL;
+ }
+ /* First get num of receivers */
+ ret = mhdp_hdcp_mailbox_read_receive(mhdp, num, 1);
+ if (ret)
+ goto err_rx_id;
+
+ /* skip second data */
+ ret = mhdp_hdcp_mailbox_read_receive(mhdp, &temp, 1);
+ if (ret)
+ goto err_rx_id;
+
+ /* get receivers ID */
+ ret = mhdp_hdcp_mailbox_read_receive(mhdp, rx_id, mbox_size - 2);
+
+err_rx_id:
+ mutex_unlock(&mhdp->api_lock);
+ return ret;
+}
+EXPORT_SYMBOL(cdns_mhdp_hdcp_tx_is_receiver_id_valid);
+
+int cdns_mhdp_hdcp_tx_respond_receiver_id_valid(
+ struct cdns_mhdp_device *mhdp, u8 val)
+{
+ int ret;
+
+ mutex_lock(&mhdp->api_lock);
+
+ ret = mhdp_hdcp_mailbox_send(mhdp, MB_MODULE_ID_HDCP_TX,
+ HDCP_TX_RESPOND_RECEIVER_ID_VALID,
+ sizeof(val), &val);
+ mutex_unlock(&mhdp->api_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL(cdns_mhdp_hdcp_tx_respond_receiver_id_valid);
+
+int cdns_mhdp_hdcp_tx_reauth(struct cdns_mhdp_device *mhdp, u8 msg)
+{
+ int ret;
+
+ mutex_lock(&mhdp->api_lock);
+
+ ret = mhdp_hdcp_mailbox_send(mhdp, MB_MODULE_ID_HDCP_TX,
+ HDCP_TX_DO_AUTH_REQ, sizeof(msg), &msg);
+
+ mutex_unlock(&mhdp->api_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL(cdns_mhdp_hdcp_tx_reauth);
diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp-hdcp.h b/drivers/gpu/drm/bridge/cadence/cdns-mhdp-hdcp.h
new file mode 100644
index 000000000000..558fc07b3ce7
--- /dev/null
+++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp-hdcp.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2021 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+
+#ifndef CDNS_HDMI_HDCP_H
+#define CDNS_HDMI_HDCP_H
+
+int cdns_mhdp_hdcp2_tx_respond_km(struct cdns_mhdp_device *mhdp,
+ u8 *msg, u16 len);
+int cdns_mhdp_hdcp_tx_config(struct cdns_mhdp_device *mhdp, u8 config);
+int cdns_mhdp_hdcp_tx_status_req(struct cdns_mhdp_device *mhdp,
+ u8 *status, u16 len);
+int cdns_mhdp_hdcp2_tx_is_km_stored_req(struct cdns_mhdp_device *mhdp, u8 *data, u16 len);
+int cdns_mhdp_hdcp2_tx_store_km(struct cdns_mhdp_device *mhdp,
+ u8 *reg, u16 len);
+int cdns_mhdp_hdcp_tx_is_receiver_id_valid(struct cdns_mhdp_device *mhdp,
+ u8 *rx_id, u8 *num);
+int cdns_mhdp_hdcp_tx_respond_receiver_id_valid(struct cdns_mhdp_device *mhdp,
+ u8 val);
+int cdns_mhdp_hdcp_tx_test_keys(struct cdns_mhdp_device *mhdp, u8 type, u8 resp);
+int cdns_mhdp_hdcp_tx_reauth(struct cdns_mhdp_device *mhdp, u8 msg);
+
+#endif /* CDNS_HDMI_HDCP_H */
diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp-hdmi.c b/drivers/gpu/drm/bridge/cadence/cdns-mhdp-hdmi.c
new file mode 100644
index 000000000000..47c853781d84
--- /dev/null
+++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp-hdmi.c
@@ -0,0 +1,332 @@
+/*
+ * Copyright 2019-2021 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <drm/drm_vblank.h>
+#include <drm/drm_print.h>
+#include <linux/io.h>
+#include <drm/bridge/cdns-mhdp.h>
+#include <linux/regmap.h>
+
+void cdns_mhdp_infoframe_set(struct cdns_mhdp_device *mhdp,
+ u8 entry_id, u8 packet_len, u8 *packet, u8 packet_type)
+{
+ u32 *packet32, len32;
+ u32 val, i;
+
+ /* invalidate entry */
+ val = F_ACTIVE_IDLE_TYPE(1) | F_PKT_ALLOC_ADDRESS(entry_id);
+ cdns_mhdp_bus_write(val, mhdp, SOURCE_PIF_PKT_ALLOC_REG);
+ cdns_mhdp_bus_write(F_PKT_ALLOC_WR_EN(1), mhdp, SOURCE_PIF_PKT_ALLOC_WR_EN);
+
+ /* flush fifo 1 */
+ cdns_mhdp_bus_write(F_FIFO1_FLUSH(1), mhdp, SOURCE_PIF_FIFO1_FLUSH);
+
+ /* write packet into memory */
+ packet32 = (u32 *)packet;
+ len32 = packet_len / 4;
+ for (i = 0; i < len32; i++)
+ cdns_mhdp_bus_write(F_DATA_WR(packet32[i]), mhdp, SOURCE_PIF_DATA_WR);
+
+ /* write entry id */
+ cdns_mhdp_bus_write(F_WR_ADDR(entry_id), mhdp, SOURCE_PIF_WR_ADDR);
+
+ /* write request */
+ cdns_mhdp_bus_write(F_HOST_WR(1), mhdp, SOURCE_PIF_WR_REQ);
+
+ /* update entry */
+ val = F_ACTIVE_IDLE_TYPE(1) | F_TYPE_VALID(1) |
+ F_PACKET_TYPE(packet_type) | F_PKT_ALLOC_ADDRESS(entry_id);
+ cdns_mhdp_bus_write(val, mhdp, SOURCE_PIF_PKT_ALLOC_REG);
+
+ cdns_mhdp_bus_write(F_PKT_ALLOC_WR_EN(1), mhdp, SOURCE_PIF_PKT_ALLOC_WR_EN);
+}
+
+int cdns_hdmi_get_edid_block(void *data, u8 *edid,
+ u32 block, size_t length)
+{
+ struct cdns_mhdp_device *mhdp = data;
+ u8 msg[2], reg[5], i;
+ int ret;
+
+ for (i = 0; i < 4; i++) {
+ msg[0] = block / 2;
+ msg[1] = block % 2;
+
+ ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_HDMI_TX, HDMI_TX_EDID,
+ sizeof(msg), msg);
+ if (ret)
+ continue;
+
+ ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_HDMI_TX,
+ HDMI_TX_EDID, sizeof(reg) + length);
+ if (ret)
+ continue;
+
+ ret = cdns_mhdp_mailbox_read_receive(mhdp, reg, sizeof(reg));
+ if (ret)
+ continue;
+
+ ret = cdns_mhdp_mailbox_read_receive(mhdp, edid, length);
+ if (ret)
+ continue;
+
+ if ((reg[3] << 8 | reg[4]) == length)
+ break;
+ }
+
+ if (ret)
+ DRM_ERROR("get block[%d] edid failed: %d\n", block, ret);
+ return ret;
+}
+
+int cdns_hdmi_scdc_read(struct cdns_mhdp_device *mhdp, u8 addr, u8 *data)
+{
+ u8 msg[4], reg[6];
+ int ret;
+
+ msg[0] = 0x54;
+ msg[1] = addr;
+ msg[2] = 0;
+ msg[3] = 1;
+ ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_HDMI_TX, HDMI_TX_READ,
+ sizeof(msg), msg);
+ if (ret)
+ goto err_scdc_read;
+
+ ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_HDMI_TX,
+ HDMI_TX_READ, sizeof(reg));
+ if (ret)
+ goto err_scdc_read;
+
+ ret = cdns_mhdp_mailbox_read_receive(mhdp, reg, sizeof(reg));
+ if (ret)
+ goto err_scdc_read;
+
+ *data = reg[5];
+
+err_scdc_read:
+ if (ret)
+ DRM_ERROR("scdc read failed: %d\n", ret);
+ return ret;
+}
+
+int cdns_hdmi_scdc_write(struct cdns_mhdp_device *mhdp, u8 addr, u8 value)
+{
+ u8 msg[5], reg[5];
+ int ret;
+
+ msg[0] = 0x54;
+ msg[1] = addr;
+ msg[2] = 0;
+ msg[3] = 1;
+ msg[4] = value;
+ ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_HDMI_TX, HDMI_TX_WRITE,
+ sizeof(msg), msg);
+ if (ret)
+ goto err_scdc_write;
+
+ ret = cdns_mhdp_mailbox_validate_receive(mhdp, MB_MODULE_ID_HDMI_TX,
+ HDMI_TX_WRITE, sizeof(reg));
+ if (ret)
+ goto err_scdc_write;
+
+ ret = cdns_mhdp_mailbox_read_receive(mhdp, reg, sizeof(reg));
+ if (ret)
+ goto err_scdc_write;
+
+ if (reg[0] != 0)
+ ret = -EINVAL;
+
+err_scdc_write:
+ if (ret)
+ DRM_ERROR("scdc write failed: %d\n", ret);
+ return ret;
+}
+
+int cdns_hdmi_ctrl_init(struct cdns_mhdp_device *mhdp,
+ int protocol,
+ u32 char_rate)
+{
+ u32 reg0;
+ u32 reg1;
+ u32 val;
+ int ret;
+
+ /* Set PHY to HDMI data */
+ ret = cdns_mhdp_reg_write(mhdp, PHY_DATA_SEL, F_SOURCE_PHY_MHDP_SEL(1));
+ if (ret < 0)
+ return ret;
+
+ ret = cdns_mhdp_reg_write(mhdp, HDTX_HPD,
+ F_HPD_VALID_WIDTH(4) | F_HPD_GLITCH_WIDTH(0));
+ if (ret < 0)
+ return ret;
+
+ /* open CARS */
+ ret = cdns_mhdp_reg_write(mhdp, SOURCE_PHY_CAR, 0xF);
+ if (ret < 0)
+ return ret;
+ ret = cdns_mhdp_reg_write(mhdp, SOURCE_HDTX_CAR, 0xFF);
+ if (ret < 0)
+ return ret;
+ ret = cdns_mhdp_reg_write(mhdp, SOURCE_PKT_CAR, 0xF);
+ if (ret < 0)
+ return ret;
+ ret = cdns_mhdp_reg_write(mhdp, SOURCE_AIF_CAR, 0xF);
+ if (ret < 0)
+ return ret;
+ ret = cdns_mhdp_reg_write(mhdp, SOURCE_CIPHER_CAR, 0xF);
+ if (ret < 0)
+ return ret;
+ ret = cdns_mhdp_reg_write(mhdp, SOURCE_CRYPTO_CAR, 0xF);
+ if (ret < 0)
+ return ret;
+ ret = cdns_mhdp_reg_write(mhdp, SOURCE_CEC_CAR, 3);
+ if (ret < 0)
+ return ret;
+
+ reg0 = reg1 = 0x7c1f;
+ if (protocol == MODE_HDMI_2_0 && char_rate >= 340000) {
+ reg0 = 0;
+ reg1 = 0xFFFFF;
+ }
+ ret = cdns_mhdp_reg_write(mhdp, HDTX_CLOCK_REG_0, reg0);
+ if (ret < 0)
+ return ret;
+ ret = cdns_mhdp_reg_write(mhdp, HDTX_CLOCK_REG_1, reg1);
+ if (ret < 0)
+ return ret;
+
+ /* set hdmi mode and preemble mode data enable */
+ val = F_HDMI_MODE(protocol) | F_HDMI2_PREAMBLE_EN(1) | F_DATA_EN(1) |
+ F_HDMI2_CTRL_IL_MODE(1) | F_BCH_EN(1) | F_PIC_3D(0XF) | F_CLEAR_AVMUTE(1);
+ ret = cdns_mhdp_reg_write(mhdp, HDTX_CONTROLLER, val);
+
+ return ret;
+}
+
+int cdns_hdmi_mode_config(struct cdns_mhdp_device *mhdp,
+ struct drm_display_mode *mode,
+ struct video_info *video_info)
+{
+ int ret;
+ u32 val;
+ u32 vsync_lines = mode->vsync_end - mode->vsync_start;
+ u32 eof_lines = mode->vsync_start - mode->vdisplay;
+ u32 sof_lines = mode->vtotal - mode->vsync_end;
+ u32 hblank = mode->htotal - mode->hdisplay;
+ u32 hactive = mode->hdisplay;
+ u32 vblank = mode->vtotal - mode->vdisplay;
+ u32 vactive = mode->vdisplay;
+ u32 hfront = mode->hsync_start - mode->hdisplay;
+ u32 hback = mode->htotal - mode->hsync_end;
+ u32 vfront = eof_lines;
+ u32 hsync = hblank - hfront - hback;
+ u32 vsync = vsync_lines;
+ u32 vback = sof_lines;
+ u32 v_h_polarity = ((mode->flags & DRM_MODE_FLAG_NHSYNC) ? 0 : 1) +
+ ((mode->flags & DRM_MODE_FLAG_NVSYNC) ? 0 : 2);
+
+ ret = cdns_mhdp_reg_write(mhdp, SCHEDULER_H_SIZE, (hactive << 16) + hblank);
+ if (ret < 0)
+ return ret;
+
+ ret = cdns_mhdp_reg_write(mhdp, SCHEDULER_V_SIZE, (vactive << 16) + vblank);
+ if (ret < 0)
+ return ret;
+
+ ret = cdns_mhdp_reg_write(mhdp, HDTX_SIGNAL_FRONT_WIDTH, (vfront << 16) + hfront);
+ if (ret < 0)
+ return ret;
+
+ ret = cdns_mhdp_reg_write(mhdp, HDTX_SIGNAL_SYNC_WIDTH, (vsync << 16) + hsync);
+ if (ret < 0)
+ return ret;
+
+ ret = cdns_mhdp_reg_write(mhdp, HDTX_SIGNAL_BACK_WIDTH, (vback << 16) + hback);
+ if (ret < 0)
+ return ret;
+
+ ret = cdns_mhdp_reg_write(mhdp, HSYNC2VSYNC_POL_CTRL, v_h_polarity);
+ if (ret < 0)
+ return ret;
+
+ /* Reset Data Enable */
+ val = cdns_mhdp_reg_read(mhdp, HDTX_CONTROLLER);
+ val &= ~F_DATA_EN(1);
+ ret = cdns_mhdp_reg_write(mhdp, HDTX_CONTROLLER, val);
+ if (ret < 0)
+ return ret;
+
+ /* Set bpc */
+ val &= ~F_VIF_DATA_WIDTH(3);
+ switch (video_info->color_depth) {
+ case 10:
+ val |= F_VIF_DATA_WIDTH(1);
+ break;
+ case 12:
+ val |= F_VIF_DATA_WIDTH(2);
+ break;
+ case 16:
+ val |= F_VIF_DATA_WIDTH(3);
+ break;
+ case 8:
+ default:
+ val |= F_VIF_DATA_WIDTH(0);
+ break;
+ }
+
+ /* select color encoding */
+ val &= ~F_HDMI_ENCODING(3);
+ switch (video_info->color_fmt) {
+ case YCBCR_4_4_4:
+ val |= F_HDMI_ENCODING(2);
+ break;
+ case YCBCR_4_2_2:
+ val |= F_HDMI_ENCODING(1);
+ break;
+ case YCBCR_4_2_0:
+ val |= F_HDMI_ENCODING(3);
+ break;
+ case PXL_RGB:
+ default:
+ val |= F_HDMI_ENCODING(0);
+ break;
+ }
+
+ ret = cdns_mhdp_reg_write(mhdp, HDTX_CONTROLLER, val);
+ if (ret < 0)
+ return ret;
+
+ /* set data enable */
+ val |= F_DATA_EN(1);
+ ret = cdns_mhdp_reg_write(mhdp, HDTX_CONTROLLER, val);
+
+ return ret;
+}
+
+int cdns_hdmi_disable_gcp(struct cdns_mhdp_device *mhdp)
+{
+ u32 val;
+
+ val = cdns_mhdp_reg_read(mhdp, HDTX_CONTROLLER);
+ val &= ~F_GCP_EN(1);
+
+ return cdns_mhdp_reg_write(mhdp, HDTX_CONTROLLER, val);
+}
+
+int cdns_hdmi_enable_gcp(struct cdns_mhdp_device *mhdp)
+{
+ u32 val;
+
+ val = cdns_mhdp_reg_read(mhdp, HDTX_CONTROLLER);
+ val |= F_GCP_EN(1);
+
+ return cdns_mhdp_reg_write(mhdp, HDTX_CONTROLLER, val);
+}
diff --git a/drivers/gpu/drm/bridge/cadence/cdns-mhdp.h b/drivers/gpu/drm/bridge/cadence/cdns-mhdp.h
new file mode 100644
index 000000000000..8ad99eb8f86e
--- /dev/null
+++ b/drivers/gpu/drm/bridge/cadence/cdns-mhdp.h
@@ -0,0 +1,232 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Cadence MHDP DP MST bridge driver.
+ *
+ * Copyright: 2018 Cadence Design Systems, Inc.
+ *
+ * Author: Quentin Schulz <quentin.schulz@free-electrons.com>
+ */
+
+
+#ifndef CDNS_MHDP_H
+#define CDNS_MHDP_H
+
+#include <drm/drm_dp_mst_helper.h>
+
+#define CDNS_APB_CFG 0x00000
+#define CDNS_APB_CTRL (CDNS_APB_CFG + 0x00)
+#define CDNS_MAILBOX_FULL (CDNS_APB_CFG + 0x08)
+#define CDNS_MAILBOX_EMPTY (CDNS_APB_CFG + 0x0c)
+#define CDNS_MAILBOX_TX_DATA (CDNS_APB_CFG + 0x10)
+#define CDNS_MAILBOX_RX_DATA (CDNS_APB_CFG + 0x14)
+#define CDNS_KEEP_ALIVE (CDNS_APB_CFG + 0x18)
+#define CDNS_KEEP_ALIVE_MASK GENMASK(7, 0)
+
+#define CDNS_MB_INT_MASK (CDNS_APB_CFG + 0x34)
+
+#define CDNS_SW_CLK_L (CDNS_APB_CFG + 0x3c)
+#define CDNS_SW_CLK_H (CDNS_APB_CFG + 0x40)
+#define CDNS_SW_EVENT0 (CDNS_APB_CFG + 0x44)
+#define CDNS_DPTX_HPD BIT(0)
+
+#define CDNS_SW_EVENT1 (CDNS_APB_CFG + 0x48)
+#define CDNS_SW_EVENT2 (CDNS_APB_CFG + 0x4c)
+#define CDNS_SW_EVENT3 (CDNS_APB_CFG + 0x50)
+
+#define CDNS_APB_INT_MASK (CDNS_APB_CFG + 0x6C)
+#define CDNS_APB_INT_MASK_MAILBOX_INT BIT(0)
+#define CDNS_APB_INT_MASK_SW_EVENT_INT BIT(1)
+
+#define CDNS_DPTX_CAR (CDNS_APB_CFG + 0x904)
+#define CDNS_VIF_CLK_EN BIT(0)
+#define CDNS_VIF_CLK_RSTN BIT(1)
+
+#define CDNS_SOURCE_VIDEO_IF(s) (0x00b00 + (s * 0x20))
+#define CDNS_BND_HSYNC2VSYNC(s) (CDNS_SOURCE_VIDEO_IF(s) + \
+ 0x00)
+#define CDNS_IP_DTCT_WIN GENMASK(11, 0)
+#define CDNS_IP_DET_INTERLACE_FORMAT BIT(12)
+#define CDNS_IP_BYPASS_V_INTERFACE BIT(13)
+
+#define CDNS_HSYNC2VSYNC_POL_CTRL(s) (CDNS_SOURCE_VIDEO_IF(s) + \
+ 0x10)
+#define CDNS_H2V_HSYNC_POL_ACTIVE_LOW BIT(1)
+#define CDNS_H2V_VSYNC_POL_ACTIVE_LOW BIT(2)
+
+#define CDNS_DPTX_PHY_CONFIG 0x02000
+#define CDNS_PHY_TRAINING_EN BIT(0)
+#define CDNS_PHY_TRAINING_TYPE(x) (((x) & GENMASK(3, 0)) << 1)
+#define CDNS_PHY_SCRAMBLER_BYPASS BIT(5)
+#define CDNS_PHY_ENCODER_BYPASS BIT(6)
+#define CDNS_PHY_SKEW_BYPASS BIT(7)
+#define CDNS_PHY_TRAINING_AUTO BIT(8)
+#define CDNS_PHY_LANE0_SKEW(x) (((x) & GENMASK(2, 0)) << 9)
+#define CDNS_PHY_LANE1_SKEW(x) (((x) & GENMASK(2, 0)) << 12)
+#define CDNS_PHY_LANE2_SKEW(x) (((x) & GENMASK(2, 0)) << 15)
+#define CDNS_PHY_LANE3_SKEW(x) (((x) & GENMASK(2, 0)) << 18)
+#define CDNS_PHY_COMMON_CONFIG (CDNS_PHY_LANE1_SKEW(1) | \
+ CDNS_PHY_LANE2_SKEW(2) | \
+ CDNS_PHY_LANE3_SKEW(3))
+#define CDNS_PHY_10BIT_EN BIT(21)
+
+#define CDNS_DPTX_FRAMER 0x02200
+#define CDNS_DP_FRAMER_GLOBAL_CONFIG (CDNS_DPTX_FRAMER + 0x00)
+#define CDNS_DP_NUM_LANES(x) (x - 1)
+#define CDNS_DP_MST_EN BIT(2)
+#define CDNS_DP_FRAMER_EN BIT(3)
+#define CDNS_DP_RATE_GOVERNOR_EN BIT(4)
+#define CDNS_DP_NO_VIDEO_MODE BIT(5)
+#define CDNS_DP_DISABLE_PHY_RST BIT(6)
+#define CDNS_DP_WR_FAILING_EDGE_VSYNC BIT(7)
+
+#define CDNS_DP_SW_RESET (CDNS_DPTX_FRAMER + 0x04)
+#define CDNS_DP_FRAMER_TU (CDNS_DPTX_FRAMER + 0x08)
+#define CDNS_DP_FRAMER_TU_SIZE(x) (((x) & GENMASK(6, 0)) << 8)
+#define CDNS_DP_FRAMER_TU_VS(x) ((x) & GENMASK(5, 0))
+#define CDNS_DP_FRAMER_TU_CNT_RST_EN BIT(15)
+
+#define CDNS_DPTX_STREAM(s) (0x03000 + s * 0x80)
+#define CDNS_DP_MSA_HORIZONTAL_0(s) (CDNS_DPTX_STREAM(s) + 0x00)
+#define CDNS_DP_MSAH0_H_TOTAL(x) (x)
+#define CDNS_DP_MSAH0_HSYNC_START(x) ((x) << 16)
+
+#define CDNS_DP_MSA_HORIZONTAL_1(s) (CDNS_DPTX_STREAM(s) + 0x04)
+#define CDNS_DP_MSAH1_HSYNC_WIDTH(x) (x)
+#define CDNS_DP_MSAH1_HSYNC_POL_LOW BIT(15)
+#define CDNS_DP_MSAH1_HDISP_WIDTH(x) ((x) << 16)
+
+#define CDNS_DP_MSA_VERTICAL_0(s) (CDNS_DPTX_STREAM(s) + 0x08)
+#define CDNS_DP_MSAV0_V_TOTAL(x) (x)
+#define CDNS_DP_MSAV0_VSYNC_START(x) ((x) << 16)
+
+#define CDNS_DP_MSA_VERTICAL_1(s) (CDNS_DPTX_STREAM(s) + 0x0c)
+#define CDNS_DP_MSAV1_VSYNC_WIDTH(x) (x)
+#define CDNS_DP_MSAV1_VSYNC_POL_LOW BIT(15)
+#define CDNS_DP_MSAV1_VDISP_WIDTH(x) ((x) << 16)
+
+#define CDNS_DP_MSA_MISC(s) (CDNS_DPTX_STREAM(s) + 0x10)
+#define CDNS_DP_STREAM_CONFIGs(s) (CDNS_DPTX_STREAM(s) + 0x14)
+#define CDNS_DP_STREAM_CONFIG_2(s) (CDNS_DPTX_STREAM(s) + 0x2c)
+#define CDNS_DP_SC2_TU_VS_DIFF(x) ((x) << 8)
+
+#define CDNS_DP_HORIZONTAL(s) (CDNS_DPTX_STREAM(s) + 0x30)
+#define CDNS_DP_H_HSYNC_WIDTH(x) (x)
+#define CDNS_DP_H_H_TOTAL(x) ((x) << 16)
+
+#define CDNS_DP_VERTICAL_0(s) (CDNS_DPTX_STREAM(s) + 0x34)
+#define CDNS_DP_V0_VHEIGHT(x) (x)
+#define CDNS_DP_V0_VSTART(x) ((x) << 16)
+
+#define CDNS_DP_VERTICAL_1(s) (CDNS_DPTX_STREAM(s) + 0x38)
+#define CDNS_DP_V1_VTOTAL(x) (x)
+#define CDNS_DP_V1_VTOTAL_EVEN BIT(16)
+
+#define CDNS_DP_FRAMER_PXL_REPR(s) (CDNS_DPTX_STREAM(s) + 0x4c)
+#define CDNS_DP_FRAMER_6_BPC BIT(0)
+#define CDNS_DP_FRAMER_8_BPC BIT(1)
+#define CDNS_DP_FRAMER_10_BPC BIT(2)
+#define CDNS_DP_FRAMER_12_BPC BIT(3)
+#define CDNS_DP_FRAMER_16_BPC BIT(4)
+#define CDNS_DP_FRAMER_PXL_FORMAT 0x8
+#define CDNS_DP_FRAMER_RGB BIT(0)
+#define CDNS_DP_FRAMER_YCBCR444 BIT(1)
+#define CDNS_DP_FRAMER_YCBCR422 BIT(2)
+#define CDNS_DP_FRAMER_YCBCR420 BIT(3)
+#define CDNS_DP_FRAMER_Y_ONLY BIT(4)
+
+#define CDNS_DP_FRAMER_SP(s) (CDNS_DPTX_STREAM(s) + 0x10)
+#define CDNS_DP_FRAMER_VSYNC_POL_LOW BIT(0)
+#define CDNS_DP_FRAMER_HSYNC_POL_LOW BIT(1)
+#define CDNS_DP_FRAMER_INTERLACE BIT(2)
+
+#define CDNS_DP_LINE_THRESH(s) (CDNS_DPTX_STREAM(s) + 0x64)
+#define CDNS_DP_ACTIVE_LINE_THRESH(x) (x)
+
+#define CDNS_DP_VB_ID(s) (CDNS_DPTX_STREAM(s) + 0x68)
+#define CDNS_DP_VB_ID_INTERLACED BIT(2)
+#define CDNS_DP_VB_ID_COMPRESSED BIT(6)
+
+#define CDNS_DP_FRONT_BACK_PORCH(s) (CDNS_DPTX_STREAM(s) + 0x78)
+#define CDNS_DP_BACK_PORCH(x) (x)
+#define CDNS_DP_FRONT_PORCH(x) ((x) << 16)
+
+#define CDNS_DP_BYTE_COUNT(s) (CDNS_DPTX_STREAM(s) + 0x7c)
+#define CDNS_DP_BYTE_COUNT_BYTES_IN_CHUNK_SHIFT 16
+
+#define CDNS_DP_MST_STREAM_CONFIG(s) (CDNS_DPTX_STREAM(s) + 0x14)
+#define CDNS_DP_MST_STRM_CFG_STREAM_EN BIT(0)
+#define CDNS_DP_MST_STRM_CFG_NO_VIDEO BIT(1)
+
+#define CDNS_DP_MST_SLOT_ALLOCATE(s) (CDNS_DPTX_STREAM(s) + 0x44)
+#define CDNS_DP_S_ALLOC_START_SLOT(x) (x)
+#define CDNS_DP_S_ALLOC_END_SLOT(x) ((x) << 8)
+
+#define CDNS_DP_RATE_GOVERNING(s) (CDNS_DPTX_STREAM(s) + 0x48)
+#define CDNS_DP_RG_TARG_AV_SLOTS_Y(x) (x)
+#define CDNS_DP_RG_TARG_AV_SLOTS_X(x) (x << 4)
+#define CDNS_DP_RG_ENABLE BIT(10)
+
+#define CDNS_DP_MTPH_CONTROL 0x2264
+#define CDNS_DP_MTPH_ECF_EN BIT(0)
+#define CDNS_DP_MTPH_ACT_EN BIT(1)
+#define CDNS_DP_MTPH_LVP_EN BIT(2)
+
+#define CDNS_DP_MTPH_STATUS 0x226C
+#define CDNS_DP_MTPH_ACT_STATUS BIT(0)
+
+#define CDNS_DPTX_GLOBAL 0x02300
+#define CDNS_DP_LANE_EN (CDNS_DPTX_GLOBAL + 0x00)
+#define CDNS_DP_LANE_EN_LANES(x) GENMASK(x - 1, 0)
+#define CDNS_DP_ENHNCD (CDNS_DPTX_GLOBAL + 0x04)
+
+
+#define to_mhdp_connector(x) container_of(x, struct cdns_mhdp_connector, base)
+#define to_mhdp_bridge(x) container_of(x, struct cdns_mhdp_bridge, base)
+#define mgr_to_mhdp(x) container_of(x, struct cdns_mhdp_device, mst_mgr)
+
+#define CDNS_MHDP_MAX_STREAMS 4
+
+#define MAILBOX_RETRY_US 1000
+#define MAILBOX_TIMEOUT_US 5000000
+
+#define mhdp_readx_poll_timeout(op, addr, offset, val, cond, sleep_us, timeout_us) \
+({ \
+ u64 __timeout_us = (timeout_us); \
+ unsigned long __sleep_us = (sleep_us); \
+ ktime_t __timeout = ktime_add_us(ktime_get(), __timeout_us); \
+ might_sleep_if((__sleep_us) != 0); \
+ for (;;) { \
+ (val) = op(addr, offset); \
+ if (cond) \
+ break; \
+ if (__timeout_us && \
+ ktime_compare(ktime_get(), __timeout) > 0) { \
+ (val) = op(addr, offset); \
+ break; \
+ } \
+ if (__sleep_us) \
+ usleep_range((__sleep_us >> 2) + 1, __sleep_us); \
+ } \
+ (cond) ? 0 : -ETIMEDOUT; \
+})
+
+enum pixel_format {
+ PIXEL_FORMAT_RGB = 1,
+ PIXEL_FORMAT_YCBCR_444 = 2,
+ PIXEL_FORMAT_YCBCR_422 = 4,
+ PIXEL_FORMAT_YCBCR_420 = 8,
+ PIXEL_FORMAT_Y_ONLY = 16,
+};
+
+
+int cdns_mhdp_mst_init(struct cdns_mhdp_device *mhdp);
+void cdns_mhdp_mst_deinit(struct cdns_mhdp_device *mhdp);
+bool cdns_mhdp_mst_probe(struct cdns_mhdp_device *mhdp);
+enum pixel_format cdns_mhdp_get_pxlfmt(u32 color_formats);
+u32 cdns_mhdp_get_bpp(u32 bpc, u32 color_formats);
+void cdns_mhdp_configure_video(struct drm_bridge *bridge);
+void cdns_mhdp_mst_enable(struct drm_bridge *bridge);
+void cdns_mhdp_mst_disable(struct drm_bridge *bridge);
+void cdns_mhdp_enable(struct drm_bridge *bridge);
+
+#endif
diff --git a/drivers/gpu/drm/bridge/fsl-imx-ldb.c b/drivers/gpu/drm/bridge/fsl-imx-ldb.c
new file mode 100644
index 000000000000..55ccb0128f18
--- /dev/null
+++ b/drivers/gpu/drm/bridge/fsl-imx-ldb.c
@@ -0,0 +1,281 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2012 Sascha Hauer, Pengutronix
+ * Copyright 2020 NXP
+ */
+
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <drm/bridge/fsl_imx_ldb.h>
+#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
+
+#define LDB_CH0_MODE_EN_TO_DI0 (1 << 0)
+#define LDB_CH0_MODE_EN_TO_DI1 (3 << 0)
+#define LDB_CH0_MODE_EN_MASK (3 << 0)
+#define LDB_CH1_MODE_EN_TO_DI0 (1 << 2)
+#define LDB_CH1_MODE_EN_TO_DI1 (3 << 2)
+#define LDB_CH1_MODE_EN_MASK (3 << 2)
+#define LDB_SPLIT_MODE_EN (1 << 4)
+#define LDB_DATA_WIDTH_CH0_24 (1 << 5)
+#define LDB_BIT_MAP_CH0_JEIDA (1 << 6)
+#define LDB_DATA_WIDTH_CH1_24 (1 << 7)
+#define LDB_BIT_MAP_CH1_JEIDA (1 << 8)
+#define LDB_DI0_VS_POL_ACT_LOW (1 << 9)
+#define LDB_DI1_VS_POL_ACT_LOW (1 << 10)
+
+struct ldb_bit_mapping {
+ u32 bus_format;
+ u32 datawidth;
+ const char * const mapping;
+};
+
+static const struct ldb_bit_mapping ldb_bit_mappings[] = {
+ { MEDIA_BUS_FMT_RGB666_1X7X3_SPWG, 18, "spwg" },
+ { MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, 24, "spwg" },
+ { MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA, 24, "jeida" },
+};
+
+static u32 of_get_bus_format(struct device *dev, struct device_node *np)
+{
+ const char *bm;
+ u32 datawidth = 0;
+ int ret, i;
+
+ ret = of_property_read_string(np, "fsl,data-mapping", &bm);
+ if (ret < 0)
+ return ret;
+
+ of_property_read_u32(np, "fsl,data-width", &datawidth);
+
+ for (i = 0; i < ARRAY_SIZE(ldb_bit_mappings); i++) {
+ if (!strcasecmp(bm, ldb_bit_mappings[i].mapping) &&
+ datawidth == ldb_bit_mappings[i].datawidth)
+ return ldb_bit_mappings[i].bus_format;
+ }
+
+ dev_err(dev, "invalid data mapping: %d-bit \"%s\"\n", datawidth, bm);
+
+ return -ENOENT;
+}
+
+static inline struct ldb_channel *bridge_to_ldb_ch(struct drm_bridge *b)
+{
+ return container_of(b, struct ldb_channel, bridge);
+}
+
+static void ldb_ch_set_bus_format(struct ldb_channel *ldb_ch, u32 bus_format)
+{
+ struct ldb *ldb = ldb_ch->ldb;
+
+ switch (bus_format) {
+ case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
+ break;
+ case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
+ if (ldb_ch->chno == 0 || ldb->dual)
+ ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH0_24;
+ if (ldb_ch->chno == 1 || ldb->dual)
+ ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH1_24;
+ break;
+ case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
+ if (ldb_ch->chno == 0 || ldb->dual)
+ ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH0_24 |
+ LDB_BIT_MAP_CH0_JEIDA;
+ if (ldb_ch->chno == 1 || ldb->dual)
+ ldb->ldb_ctrl |= LDB_DATA_WIDTH_CH1_24 |
+ LDB_BIT_MAP_CH1_JEIDA;
+ break;
+ }
+}
+
+static void ldb_bridge_mode_set(struct drm_bridge *bridge,
+ const struct drm_display_mode *mode,
+ const struct drm_display_mode *adjusted_mode)
+{
+ struct ldb_channel *ldb_ch = bridge_to_ldb_ch(bridge);
+ struct ldb *ldb = ldb_ch->ldb;
+
+ /* FIXME - assumes straight connections DI0 --> CH0, DI1 --> CH1 */
+ if (ldb_ch == ldb->channel[0] || ldb->dual) {
+ if (adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC)
+ ldb->ldb_ctrl |= LDB_DI0_VS_POL_ACT_LOW;
+ else if (adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC)
+ ldb->ldb_ctrl &= ~LDB_DI0_VS_POL_ACT_LOW;
+ }
+ if (ldb_ch == ldb->channel[1] || ldb->dual) {
+ if (adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC)
+ ldb->ldb_ctrl |= LDB_DI1_VS_POL_ACT_LOW;
+ else if (adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC)
+ ldb->ldb_ctrl &= ~LDB_DI1_VS_POL_ACT_LOW;
+ }
+
+ ldb_ch_set_bus_format(ldb_ch, ldb_ch->bus_format);
+}
+
+static void ldb_bridge_enable(struct drm_bridge *bridge)
+{
+ struct ldb_channel *ldb_ch = bridge_to_ldb_ch(bridge);
+ struct ldb *ldb = ldb_ch->ldb;
+
+ if (pm_runtime_enabled(ldb->dev))
+ pm_runtime_get_sync(ldb->dev);
+
+ regmap_write(ldb->regmap, ldb->ctrl_reg, ldb->ldb_ctrl);
+}
+
+static void ldb_bridge_disable(struct drm_bridge *bridge)
+{
+ struct ldb_channel *ldb_ch = bridge_to_ldb_ch(bridge);
+ struct ldb *ldb = ldb_ch->ldb;
+
+ if (ldb_ch == ldb->channel[0] || ldb->dual)
+ ldb->ldb_ctrl &= ~LDB_CH0_MODE_EN_MASK;
+ if (ldb_ch == ldb->channel[1] || ldb->dual)
+ ldb->ldb_ctrl &= ~LDB_CH1_MODE_EN_MASK;
+
+ regmap_write(ldb->regmap, ldb->ctrl_reg, ldb->ldb_ctrl);
+
+ if (pm_runtime_enabled(ldb->dev))
+ pm_runtime_put(ldb->dev);
+}
+
+static int ldb_bridge_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
+{
+ struct ldb_channel *ldb_ch = bridge_to_ldb_ch(bridge);
+ struct ldb *ldb = ldb_ch->ldb;
+
+ if (!bridge->encoder) {
+ dev_err(ldb->dev, "failed to find encoder object\n");
+ return -ENODEV;
+ }
+
+ if (!ldb_ch->next_bridge)
+ return 0;
+
+ return drm_bridge_attach(bridge->encoder,
+ ldb_ch->next_bridge, &ldb_ch->bridge, flags);
+}
+
+static const struct drm_bridge_funcs ldb_bridge_funcs = {
+ .mode_set = ldb_bridge_mode_set,
+ .enable = ldb_bridge_enable,
+ .disable = ldb_bridge_disable,
+ .attach = ldb_bridge_attach,
+};
+
+int ldb_bind(struct ldb *ldb, struct drm_encoder **encoder)
+{
+ struct device *dev = ldb->dev;
+ struct device_node *np = dev->of_node;
+ struct device_node *child;
+ int ret = 0;
+ int i;
+
+ ldb->regmap = syscon_regmap_lookup_by_phandle(np, "gpr");
+ if (IS_ERR(ldb->regmap)) {
+ dev_err(dev, "failed to get parent regmap\n");
+ return PTR_ERR(ldb->regmap);
+ }
+
+ if (pm_runtime_enabled(dev))
+ pm_runtime_get_sync(dev);
+
+ /* disable LDB by resetting the control register to POR default */
+ regmap_write(ldb->regmap, ldb->ctrl_reg, 0);
+
+ if (pm_runtime_enabled(dev))
+ pm_runtime_put(dev);
+
+ ldb->dual = of_property_read_bool(np, "fsl,dual-channel");
+ if (ldb->dual)
+ ldb->ldb_ctrl |= LDB_SPLIT_MODE_EN;
+
+ for_each_child_of_node(np, child) {
+ struct ldb_channel *ldb_ch;
+ int bus_format;
+
+ ret = of_property_read_u32(child, "reg", &i);
+ if (ret || i < 0 || i > 1) {
+ ret = -EINVAL;
+ goto free_child;
+ }
+
+ if (!of_device_is_available(child))
+ continue;
+
+ if (ldb->dual && i > 0) {
+ dev_warn(dev, "dual-channel mode, ignoring second output\n");
+ continue;
+ }
+
+ ldb_ch = ldb->channel[i];
+ ldb_ch->ldb = ldb;
+ ldb_ch->chno = i;
+ ldb_ch->is_valid = false;
+
+ ret = drm_of_find_panel_or_bridge(child,
+ ldb->output_port, 0,
+ &ldb_ch->panel,
+ &ldb_ch->next_bridge);
+ if (ret && ret != -ENODEV)
+ goto free_child;
+
+ bus_format = of_get_bus_format(dev, child);
+ if (bus_format == -EINVAL) {
+ /*
+ * If no bus format was specified in the device tree,
+ * we can still get it from the connected panel later.
+ */
+ if (ldb_ch->panel && ldb_ch->panel->funcs &&
+ ldb_ch->panel->funcs->get_modes)
+ bus_format = 0;
+ }
+ if (bus_format < 0) {
+ dev_err(dev, "could not determine data mapping: %d\n",
+ bus_format);
+ ret = bus_format;
+ goto free_child;
+ }
+ ldb_ch->bus_format = bus_format;
+ ldb_ch->child = child;
+
+ if (ldb_ch->panel) {
+ ldb_ch->next_bridge = devm_drm_panel_bridge_add(dev,
+ ldb_ch->panel);
+ if (IS_ERR(ldb_ch->next_bridge)) {
+ ret = PTR_ERR(ldb_ch->next_bridge);
+ goto free_child;
+ }
+ }
+
+ ldb_ch->bridge.driver_private = ldb_ch;
+ ldb_ch->bridge.funcs = &ldb_bridge_funcs;
+ ldb_ch->bridge.of_node = child;
+
+ ret = drm_bridge_attach(encoder[i], &ldb_ch->bridge, NULL, 0);
+ if (ret) {
+ dev_err(dev,
+ "failed to attach bridge with encoder: %d\n",
+ ret);
+ goto free_child;
+ }
+
+ ldb_ch->is_valid = true;
+ }
+
+ return 0;
+
+free_child:
+ of_node_put(child);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(ldb_bind);
+
+MODULE_DESCRIPTION("Freescale i.MX LVDS display bridge driver");
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform: fsl-imx-ldb");
diff --git a/drivers/gpu/drm/bridge/it6161.c b/drivers/gpu/drm/bridge/it6161.c
new file mode 100644
index 000000000000..b7bf3698db99
--- /dev/null
+++ b/drivers/gpu/drm/bridge/it6161.c
@@ -0,0 +1,2407 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2021 NXP
+ */
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <sound/hdmi-codec.h>
+
+#include "it6161.h"
+
+#define AUX_WAIT_TIMEOUT_MS 100
+#define DEFAULT_DRV_HOLD 0
+
+#define RGB_24b 0x3E
+#define RGB_18b 0x1E
+
+#define InvMCLK TRUE
+#define PDREFCLK FALSE
+#define SkipStg 4
+#define MShift 8
+#define PPSFFRdStg 0x04
+#define RegAutoSync TRUE
+
+#define LMDbgSel 0 /* 0~7 */
+#define InvPCLK FALSE
+#define PDREFCNT 0 /* when PDREFCLK=TRUE, 0:div2, 1:div4, 2:div8, 3:divg16 */
+#define EnIOIDDQ FALSE
+#define EnStb2Rst FALSE
+#define EnExtStdby FALSE
+#define EnStandby FALSE
+#define MPLaneSwap FALSE
+#define MPPNSwap FALSE /* TRUE: MTK , FALSE: Solomon */
+
+/* PPI */
+#define EnContCK TRUE
+#define HSSetNum 1
+#define EnDeSkew TRUE
+#define PPIDbgSel 12
+#define RegIgnrNull 1
+#define RegIgnrBlk 1
+#define RegEnDummyECC 0
+#define EOTPSel 0
+
+/* PPS option */
+#define EnMBPM FALSE /* enable MIPI Bypass Mode */
+#if (EnMBPM == TRUE)
+ #define PREC_Update TRUE /* enable P-timing update */
+ #define MREC_Update TRUE /* enable M-timing update */
+ #define EnTBPM TRUE /* enable HDMITX Bypass Mode */
+#else
+ #define PREC_Update FALSE
+ #define MREC_Update FALSE
+ #define EnTBPM FALSE
+#endif
+
+#define REGSELDEF FALSE
+#define EnHReSync FALSE
+#define EnVReSync FALSE
+#define EnFReSync FALSE
+#define EnVREnh FALSE
+#define EnVREnhSel 1 /* 0:Div2, 1:Div4, 2:Div8, 3:Div16, 4:Div32 */
+#define EnMAvg TRUE
+
+#define PShift 3
+#define EnFFAutoRst TRUE
+#define RegEnSyncErr FALSE
+#define EnTxCRC TRUE
+#define TxCRCnum 0x20
+
+#define ENABLE_MIPI_RX_EXTERNAL_CLOCK FALSE
+
+#define NRTXRCLK TRUE /* true:set TRCLK by self */
+#define RCLKFreqSel TRUE /* false: 10MHz(div1); true: 20 MHz(OSSDIV2) */
+#define ForceTxCLKStb TRUE
+
+#define HDMI_TX_PCLK_DIV2 FALSE
+
+#define HDMI_TX_MODE HDMI_TX_ENABLE_DE_ONLY
+
+enum hdmi_tx_mode {
+ HDMI_TX_NONE,
+ HDMI_TX_BY_PASS,
+ HDMI_TX_ENABLE_DE_ONLY,
+ HDMI_TX_ENABLE_PATTERN_GENERATOR,
+};
+
+enum it6161_active_level {
+ LOW,
+ HIGH,
+};
+
+const struct RegSetEntry HDMITX_Init_Table[] = {
+ {0x0F, 0x40, 0x00},
+ /*PLL Reset */
+ {0x62, 0x08, 0x00}, /* XP_RESETB */
+ {0x64, 0x04, 0x00}, /* IP_RESETB */
+ {0x0F, 0x01, 0x00}, /* bank 0 ;3 */
+ {0x8D, 0xFF, CEC_I2C_SLAVE_ADDR}, /* EnCEC */
+ {0xA9, 0x80, (EnTBPM << 7)},
+ {0xBF, 0x80, (NRTXRCLK << 7)},
+
+ /* Initial Value */
+ {0xF8, 0xFF, 0xC3},
+ {0xF8, 0xFF, 0xA5},
+ {0xF4, 0x0C, 0x00},
+ {0xF3, 0x02, 0x00},
+ {0xF8, 0xFF, 0xFF},
+ {0x5A, 0x0C, 0x0C},
+ {0xD1, 0x0A, ((ForceTxCLKStb) << 3) + 0x02},
+ {0x5D, 0x04, ((RCLKFreqSel) << 2)},
+ {0x65, 0x03, 0x00},
+ {0x71, 0xF9, 0x18},
+ {0xCF, 0xFF, 0x00},
+ {0xd1, 0x02, 0x00},
+ {0x59, 0xD0, 0x40},
+ {0xE1, 0x20, 0x00},
+ {0xF5, 0x40, 0x00},
+ {0x05, 0xC0, 0x40}, /* Setup INT Pin: Active Low & Open-Drain */
+ {0x0C, 0xFF, 0xFF},
+ {0x0D, 0xFF, 0xFF},
+ {0x0E, 0x03, 0x03}, /* Clear all Interrupt */
+ {0x0C, 0xFF, 0x00},
+ {0x0D, 0xFF, 0x00},
+ {0x0E, 0x02, 0x00},
+ {0x20, 0x01, 0x00}
+};
+
+const struct RegSetEntry HDMITX_DefaultVideo_Table[] = {
+ /* Config default output format */
+ {0x72, 0xff, 0x00},
+ {0x70, 0xff, 0x00},
+/* GenCSC\RGB2YUV_ITU709_16_235 */
+ {0x72, 0xFF, 0x02},
+ {0x73, 0xFF, 0x00},
+ {0x74, 0xFF, 0x80},
+ {0x75, 0xFF, 0x00},
+ {0x76, 0xFF, 0xB8},
+ {0x77, 0xFF, 0x05},
+ {0x78, 0xFF, 0xB4},
+ {0x79, 0xFF, 0x01},
+ {0x7A, 0xFF, 0x93},
+ {0x7B, 0xFF, 0x00},
+ {0x7C, 0xFF, 0x49},
+ {0x7D, 0xFF, 0x3C},
+ {0x7E, 0xFF, 0x18},
+ {0x7F, 0xFF, 0x04},
+ {0x80, 0xFF, 0x9F},
+ {0x81, 0xFF, 0x3F},
+ {0x82, 0xFF, 0xD9},
+ {0x83, 0xFF, 0x3C},
+ {0x84, 0xFF, 0x10},
+ {0x85, 0xFF, 0x3F},
+ {0x86, 0xFF, 0x18},
+ {0x87, 0xFF, 0x04},
+ {0x88, 0xF0, 0x00},
+};
+
+/* Config default HDMI Mode */
+const struct RegSetEntry HDMITX_SetHDMI_Table[] = {
+ {0xC0, 0x01, 0x01},
+ {0xC1, 0x03, 0x03},
+ {0xC6, 0x03, 0x03}
+};
+
+/* Config default avi infoframe */
+const struct RegSetEntry HDMITX_DefaultAVIInfo_Table[] = {
+ {0x0F, 0x01, 0x01},
+ {0x58, 0xFF, 0x10},
+ {0x59, 0xFF, 0x08},
+ {0x5A, 0xFF, 0x00},
+ {0x5B, 0xFF, 0x00},
+ {0x5C, 0xFF, 0x00},
+ {0x5D, 0xFF, 0x57},
+ {0x5E, 0xFF, 0x00},
+ {0x5F, 0xFF, 0x00},
+ {0x60, 0xFF, 0x00},
+ {0x61, 0xFF, 0x00},
+ {0x62, 0xFF, 0x00},
+ {0x63, 0xFF, 0x00},
+ {0x64, 0xFF, 0x00},
+ {0x65, 0xFF, 0x00},
+ {0x0F, 0x01, 0x00},
+ {0xCD, 0x03, 0x03}
+};
+
+/* Config default audio infoframe */
+const struct RegSetEntry HDMITX_DeaultAudioInfo_Table[] = {
+ {0x0F, 0x01, 0x01},
+ {0x68, 0xFF, 0x00},
+ {0x69, 0xFF, 0x00},
+ {0x6A, 0xFF, 0x00},
+ {0x6B, 0xFF, 0x00},
+ {0x6C, 0xFF, 0x00},
+ {0x6D, 0xFF, 0x71},
+ {0x0F, 0x01, 0x00},
+ {0xCE, 0x03, 0x03}
+};
+
+const struct RegSetEntry HDMITX_Aud_CHStatus_LPCM_20bit_48Khz[] = {
+ {0x0F, 0x01, 0x01},
+ {0x33, 0xFF, 0x00},
+ {0x34, 0xFF, 0x18},
+ {0x35, 0xFF, 0x00},
+ {0x91, 0xFF, 0x00},
+ {0x92, 0xFF, 0x00},
+ {0x93, 0xFF, 0x01},
+ {0x94, 0xFF, 0x00},
+ {0x98, 0xFF, 0x02},
+ {0x99, 0xFF, 0xDA},
+ {0x0F, 0x01, 0x00}
+};
+
+const struct RegSetEntry HDMITX_AUD_SPDIF_2ch_24bit[] = {
+ {0x0F, 0x11, 0x00},
+ {0x04, 0x14, 0x04},
+ {0xE0, 0xFF, 0xD1},
+ {0xE1, 0xFF, 0x01},
+ {0xE2, 0xFF, 0xE4},
+ {0xE3, 0xFF, 0x10},
+ {0xE4, 0xFF, 0x00},
+ {0xE5, 0xFF, 0x00},
+ {0x04, 0x14, 0x00}
+};
+
+const struct RegSetEntry HDMITX_PwrOn_Table[] = {
+ /* PwrOn RCLK , IACLK ,TXCLK */
+ {0x0F, 0x70, 0x00},
+ /* PLL PwrOn */
+ /* PwrOn DRV */
+ {0x61, 0x20, 0x00},
+ /* PwrOn XPLL */
+ {0x62, 0x44, 0x00},
+ /* PwrOn IPLL */
+ {0x64, 0x40, 0x00},
+ /* PLL Reset OFF */
+ /* DRV_RST */
+ {0x61, 0x10, 0x00},
+ /* XP_RESETB */
+ {0x62, 0x08, 0x08},
+ /* IP_RESETB */
+ {0x64, 0x04, 0x04}
+};
+
+struct it6161 {
+ struct drm_bridge bridge;
+ struct drm_connector connector;
+ struct i2c_client *i2c_mipi_rx;
+ struct i2c_client *i2c_hdmi_tx;
+ struct device_node *host_node;
+ struct mipi_dsi_device *dsi;
+ struct mutex mode_lock;
+
+ struct regmap *regmap_mipi_rx;
+ struct regmap *regmap_hdmi_tx;
+
+ u32 it6161_addr_hdmi_tx;
+
+ struct gpio_desc *enable_gpio;
+
+ u32 hdmi_tx_pclk;
+ u32 mipi_rx_rclk;
+
+ /* video mode output to hdmi tx */
+ struct drm_display_mode display_mode;
+ struct hdmi_avi_infoframe source_avi_infoframe;
+
+ u8 mipi_rx_lane_count;
+ bool enable_drv_hold;
+ u8 hdmi_tx_output_color_space;
+ u8 hdmi_tx_input_color_space;
+ u8 hdmi_tx_mode;
+ u8 support_audio;
+ bool hdmi_mode;
+ u8 bAudioChannelEnable;
+ u8 bridge_enable;
+};
+
+struct it6161 *it6161;
+
+static const struct regmap_range it6161_mipi_rx_bridge_volatile_ranges[] = {
+ {.range_min = 0, .range_max = 0xFF},
+};
+
+static const struct regmap_access_table it6161_mipi_rx_bridge_volatile_table = {
+ .yes_ranges = it6161_mipi_rx_bridge_volatile_ranges,
+ .n_yes_ranges = ARRAY_SIZE(it6161_mipi_rx_bridge_volatile_ranges),
+};
+
+static const struct regmap_config it6161_mipi_rx_bridge_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .volatile_table = &it6161_mipi_rx_bridge_volatile_table,
+ .cache_type = REGCACHE_NONE,
+};
+
+static const struct regmap_range it6161_hdmi_tx_bridge_volatile_ranges[] = {
+ {.range_min = 0, .range_max = 0xFF},
+};
+
+static const struct regmap_access_table it6161_hdmi_tx_bridge_volatile_table = {
+ .yes_ranges = it6161_hdmi_tx_bridge_volatile_ranges,
+ .n_yes_ranges = ARRAY_SIZE(it6161_hdmi_tx_bridge_volatile_ranges),
+};
+
+static const struct regmap_config it6161_hdmi_tx_bridge_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .volatile_table = &it6161_hdmi_tx_bridge_volatile_table,
+ .cache_type = REGCACHE_NONE,
+};
+
+static int it6161_mipi_rx_read(struct it6161 *it6161, u32 reg_addr)
+{
+ struct device *dev = &it6161->i2c_mipi_rx->dev;
+ u32 value;
+ int err;
+
+ err = regmap_read(it6161->regmap_mipi_rx, reg_addr, &value);
+ if (err < 0) {
+ DRM_DEV_ERROR(dev, "mipi rx read failed reg[0x%x] err: %d", reg_addr, err);
+ return err;
+ }
+
+ return value;
+}
+
+static int it6161_mipi_rx_write(struct it6161 *it6161, u32 addr, u32 val)
+{
+ struct device *dev = &it6161->i2c_mipi_rx->dev;
+ int err;
+
+ err = regmap_write(it6161->regmap_mipi_rx, addr, val);
+ if (err < 0) {
+ DRM_DEV_ERROR(dev, "mipi rx write failed reg[0x%x] = 0x%x err = %d",
+ addr, val, err);
+ return err;
+ }
+
+ return 0;
+}
+
+static int it6161_mipi_rx_set_bits(struct it6161 *it6161, u32 reg,
+ u32 mask, u32 value)
+{
+ struct device *dev = &it6161->i2c_mipi_rx->dev;
+ int err;
+
+ err = regmap_update_bits(it6161->regmap_mipi_rx, reg, mask, value);
+ if (err < 0) {
+ DRM_DEV_ERROR(dev, "mipi rx set reg[0x%x] = 0x%x mask = 0x%x failed err %d",
+ reg, value, mask, err);
+ return err;
+ }
+
+ return 0;
+}
+
+static int it6161_hdmi_tx_read(struct it6161 *it6161, u32 reg_addr)
+{
+ struct device *dev = &it6161->i2c_mipi_rx->dev;
+ u32 value;
+ int err;
+
+ err = regmap_read(it6161->regmap_hdmi_tx, reg_addr, &value);
+ if (err < 0) {
+ DRM_DEV_ERROR(dev, "hdmi tx read failed reg[0x%x] err: %d",
+ reg_addr, err);
+ return err;
+ }
+
+ return value;
+}
+
+static int it6161_hdmi_tx_write(struct it6161 *it6161, u32 addr, u32 val)
+{
+ struct device *dev = &it6161->i2c_mipi_rx->dev;
+ int err;
+
+ err = regmap_write(it6161->regmap_hdmi_tx, addr, val);
+
+ if (err < 0) {
+ DRM_DEV_ERROR(dev, "hdmi tx write failed reg[0x%x] = 0x%x err = %d",
+ addr, val, err);
+ return err;
+ }
+
+ return 0;
+}
+
+static int it6161_hdmi_tx_set_bits(struct it6161 *it6161, u32 reg,
+ u32 mask, u32 value)
+{
+ int err;
+ struct device *dev = &it6161->i2c_mipi_rx->dev;
+
+ err = regmap_update_bits(it6161->regmap_hdmi_tx, reg, mask, value);
+ if (err < 0) {
+ DRM_DEV_ERROR(dev, "hdmi tx set reg[0x%x] = 0x%x mask = 0x%x failed err %d",
+ reg, value, mask, err);
+ return err;
+ }
+
+ return 0;
+}
+
+static inline int it6161_hdmi_tx_change_bank(struct it6161 *it6161, int x)
+{
+ return it6161_hdmi_tx_set_bits(it6161, 0x0F, 0x03, x & 0x03);
+}
+
+static inline struct it6161 *connector_to_it6161(struct drm_connector *c)
+{
+ return container_of(c, struct it6161, connector);
+}
+
+static inline struct it6161 *bridge_to_it6161(struct drm_bridge *bridge)
+{
+ return container_of(bridge, struct it6161, bridge);
+}
+
+static void mipi_rx_logic_reset(struct it6161 *it6161)
+{
+ it6161_mipi_rx_set_bits(it6161, 0x05, 0x08, 0x08);
+}
+
+static void mipi_rx_logic_reset_release(struct it6161 *it6161)
+{
+ it6161_mipi_rx_set_bits(it6161, 0x05, 0x08, 0x00);
+}
+
+static void it6161_mipi_rx_int_mask_disable(struct it6161 *it6161)
+{
+ it6161_mipi_rx_set_bits(it6161, 0x0F, 0x03, 0x00);
+ it6161_mipi_rx_write(it6161, 0x09, 0x00);
+ it6161_mipi_rx_write(it6161, 0x0A, 0x00);
+ it6161_mipi_rx_write(it6161, 0x0B, 0x00);
+}
+
+static void it6161_mipi_rx_int_mask_enable(struct it6161 *it6161)
+{
+ it6161_hdmi_tx_set_bits(it6161, 0x0F, 0x03, 0x00);
+ it6161_mipi_rx_write(it6161, 0x09, 0x11);
+ it6161_mipi_rx_write(it6161, 0x0A, 0xc0);
+ it6161_mipi_rx_write(it6161, 0x0B, 0x40);
+}
+
+static void it6161_hdmi_tx_int_mask_disable(struct it6161 *it6161)
+{
+ it6161_mipi_rx_set_bits(it6161, 0x0F, 0x03, 0x00);
+ it6161_hdmi_tx_write(it6161, REG_TX_INT_MASK1, 0xFF);
+ it6161_hdmi_tx_write(it6161, REG_TX_INT_MASK3, 0xFF);
+}
+
+static void it6161_hdmi_tx_int_mask_enable(struct it6161 *it6161)
+{
+ it6161_hdmi_tx_set_bits(it6161, 0x0F, 0x03, 0x00);
+ it6161_hdmi_tx_write(it6161, REG_TX_INT_MASK1,
+ ~(B_TX_AUDIO_OVFLW_MASK | B_TX_DDC_FIFO_ERR_MASK |
+ B_TX_DDC_BUS_HANG_MASK | B_TX_HPD_MASK));
+ it6161_hdmi_tx_write(it6161, REG_TX_INT_MASK3, ~B_TX_VIDSTABLE_MASK);
+}
+
+static void it6161_hdmi_tx_write_table(struct it6161 *it6161,
+ const struct RegSetEntry table[], int size)
+{
+ int i;
+
+ for (i = 0; i < size; i++) {
+ if (table[i].mask == 0 && table[i].value == 0)
+ msleep(table[i].offset);
+ else if (table[i].mask == 0xFF)
+ it6161_hdmi_tx_write(it6161, table[i].offset, table[i].value);
+ else
+ it6161_hdmi_tx_set_bits(it6161, table[i].offset, table[i].mask, table[i].value);
+ }
+}
+
+static inline void it6161_set_interrupts_active_level(enum it6161_active_level level)
+{
+ it6161_mipi_rx_set_bits(it6161, 0x0D, 0x02, level == HIGH ? 0x02 : 0x00);
+ it6161_hdmi_tx_set_bits(it6161, 0x05, 0xC0, level == HIGH ? 0x80 : 0x40);
+}
+
+static void hdmi_tx_init(struct it6161 *it6161)
+{
+ struct device *dev = &it6161->i2c_hdmi_tx->dev;
+
+ DRM_DEV_DEBUG_DRIVER(dev, "it6161 init\n");
+
+ it6161_hdmi_tx_write_table(it6161, HDMITX_Init_Table,
+ ARRAY_SIZE(HDMITX_Init_Table));
+ it6161_hdmi_tx_write_table(it6161, HDMITX_PwrOn_Table,
+ ARRAY_SIZE(HDMITX_PwrOn_Table));
+ it6161_hdmi_tx_write_table(it6161, HDMITX_DefaultVideo_Table,
+ ARRAY_SIZE(HDMITX_DefaultVideo_Table));
+ it6161_hdmi_tx_write_table(it6161, HDMITX_SetHDMI_Table,
+ ARRAY_SIZE(HDMITX_SetHDMI_Table));
+ it6161_hdmi_tx_write_table(it6161, HDMITX_DefaultAVIInfo_Table,
+ ARRAY_SIZE(HDMITX_DefaultAVIInfo_Table));
+ it6161_hdmi_tx_write_table(it6161, HDMITX_DeaultAudioInfo_Table,
+ ARRAY_SIZE(HDMITX_DeaultAudioInfo_Table));
+ it6161_hdmi_tx_write_table(it6161, HDMITX_Aud_CHStatus_LPCM_20bit_48Khz,
+ ARRAY_SIZE(HDMITX_Aud_CHStatus_LPCM_20bit_48Khz));
+ it6161_hdmi_tx_write_table(it6161, HDMITX_AUD_SPDIF_2ch_24bit,
+ ARRAY_SIZE(HDMITX_AUD_SPDIF_2ch_24bit));
+}
+
+static bool mipi_rx_get_m_video_stable(struct it6161 *it6161)
+{
+ return it6161_mipi_rx_read(it6161, 0x0D) & 0x10;
+}
+
+static bool mipi_rx_get_p_video_stable(struct it6161 *it6161)
+{
+ return it6161_mipi_rx_read(it6161, 0x0D) & 0x20;
+}
+
+static void mipi_rx_afe_configuration(struct it6161 *it6161, u8 data_id)
+{
+ struct device *dev = &it6161->i2c_hdmi_tx->dev;
+ u8 MPLaneNum = (it6161->mipi_rx_lane_count - 1);
+
+ DRM_DEV_DEBUG_DRIVER(dev, "afe configuration data_id: 0x%02x", data_id);
+
+ if (data_id == RGB_18b) {
+ if (MPLaneNum == 3)
+ /* MPPCLKSel = 1; 4-lane : MCLK = 1/1 PCLK */
+ it6161_mipi_rx_set_bits(it6161, 0x80, 0x1F, 0x02);
+ else if (MPLaneNum == 1)
+ /* MPPCLKSel = 6; 2-lane : MCLK = 1/1 PCLK */
+ it6161_mipi_rx_set_bits(it6161, 0x80, 0x1F, 0x05);
+ else if (MPLaneNum == 0)
+ /* MPPCLKSel = 8; 1-lane : MCLK = 3/4 PCLK */
+ it6161_mipi_rx_set_bits(it6161, 0x80, 0x1F, 0x08);
+ } else {
+ if (MPLaneNum == 3)
+ /* MPPCLKSel = 1; 4-lane : MCLK = 3/4 PCLK */
+ it6161_mipi_rx_set_bits(it6161, 0x80, 0x1F, 0x02);
+ else if (MPLaneNum == 1)
+ /* MPPCLKSel = 3; 2-lane : MCLK = 3/4 PCLK */
+ it6161_mipi_rx_set_bits(it6161, 0x80, 0x1F, 0x05);
+ else if (MPLaneNum == 0)
+ /* MPPCLKSel = 5; 1-lane : MCLK = 3/4 PCLK */
+ it6161_mipi_rx_set_bits(it6161, 0x80, 0x1F, 0x0b);
+ }
+}
+
+static void mipi_rx_configuration(struct it6161 *it6161)
+{
+ struct device *dev = &it6161->i2c_hdmi_tx->dev;
+ u8 mipi_lane_config = (it6161->mipi_rx_lane_count - 1);
+
+ DRM_DEV_DEBUG_DRIVER(dev, "MIPI_LANE=%d\n", it6161->mipi_rx_lane_count);
+
+ it6161_mipi_rx_set_bits(it6161, 0x10, 0x0F, 0x0F);
+ msleep(1);
+ it6161_mipi_rx_set_bits(it6161, 0x10, 0x0F, 0x00);
+
+ mipi_rx_logic_reset(it6161);
+ msleep(1);
+ mipi_rx_logic_reset_release(it6161);
+
+ it6161_mipi_rx_int_mask_disable(it6161);
+
+ /* setup INT pin: active low */
+ it6161_mipi_rx_set_bits(it6161, 0x0d, 0x02, 0x00);
+
+ it6161_mipi_rx_set_bits(it6161, 0x0C, 0x0F,
+ (MPLaneSwap << 3) + (MPPNSwap << 2) + mipi_lane_config);
+
+ it6161_mipi_rx_set_bits(it6161, 0x11, 0x3F,
+ (EnIOIDDQ << 5) + (EnStb2Rst << 4) + (EnExtStdby << 3) +
+ (EnStandby << 2) + (InvPCLK << 1) + InvMCLK);
+
+ it6161_mipi_rx_set_bits(it6161, 0x12, 0x03, (PDREFCNT << 1) + PDREFCLK);
+
+ it6161_mipi_rx_set_bits(it6161, 0x18, 0xf7,
+ (RegEnSyncErr << 7) + (SkipStg << 4) + HSSetNum);
+ it6161_mipi_rx_set_bits(it6161, 0x19, 0xf3,
+ (PPIDbgSel << 4) + (EnContCK << 1) + EnDeSkew);
+ it6161_mipi_rx_set_bits(it6161, 0x20, 0xf7,
+ (EOTPSel << 4) + (RegEnDummyECC << 2) + (RegIgnrBlk << 1) + RegIgnrNull);
+ it6161_mipi_rx_set_bits(it6161, 0x21, 0x07, LMDbgSel);
+
+ it6161_mipi_rx_set_bits(it6161, 0x44, 0x3a,
+ (MREC_Update << 5) + (PREC_Update << 4) + (REGSELDEF << 3) + (RegAutoSync << 1));
+ it6161_mipi_rx_set_bits(it6161, 0x4B, 0x1f,
+ (EnFReSync << 4) + (EnVREnh << 3) + EnVREnhSel);
+ it6161_mipi_rx_write(it6161, 0x4C, PPSFFRdStg);
+ it6161_mipi_rx_set_bits(it6161, 0x4D, 0x01, (PPSFFRdStg >> 8) & 0x01);
+ it6161_mipi_rx_set_bits(it6161, 0x4E, 0x0C,
+ (EnVReSync << 3) + (EnHReSync << 2));
+ it6161_mipi_rx_set_bits(it6161, 0x4F, 0x03, EnFFAutoRst);
+
+ it6161_mipi_rx_set_bits(it6161, 0x70, 0x01, EnMAvg);
+ it6161_mipi_rx_write(it6161, 0x72, MShift);
+ it6161_mipi_rx_write(it6161, 0x73, PShift);
+ it6161_mipi_rx_set_bits(it6161, 0x80, 0x20, ENABLE_MIPI_RX_EXTERNAL_CLOCK << 5);
+
+ it6161_mipi_rx_write(it6161, 0x21, 0x00);
+ it6161_mipi_rx_set_bits(it6161, 0x84, 0x70, 0x00);
+
+ it6161_mipi_rx_set_bits(it6161, 0xA0, 0x01, EnMBPM);
+
+ /* enable auto detect format */
+ it6161_mipi_rx_set_bits(it6161, 0x21, 0x08, 0x08);
+
+ it6161_mipi_rx_set_bits(it6161, 0x70, 0x01, EnMAvg);
+ /* Video Clock Domain Reset */
+ it6161_mipi_rx_set_bits(it6161, 0x05, 0x02, 0x02);
+
+ if (EnMBPM) {
+ /* HRS offset */
+ it6161_mipi_rx_write(it6161, 0xA1, 0x00);
+ /* VRS offset */
+ it6161_mipi_rx_write(it6161, 0xA2, 0x00);
+ it6161_mipi_rx_write(it6161, 0xA3, 0x08);
+ it6161_mipi_rx_write(it6161, 0xA5, 0x04);
+ }
+
+ if (REGSELDEF == false) {
+ it6161_mipi_rx_set_bits(it6161, 0x31, 0x80, 0x00);
+ it6161_mipi_rx_set_bits(it6161, 0x33, 0x80, 0x00);
+ it6161_mipi_rx_set_bits(it6161, 0x35, 0x80, 0x00);
+ it6161_mipi_rx_set_bits(it6161, 0x37, 0x80, 0x00);
+ it6161_mipi_rx_set_bits(it6161, 0x39, 0x80, 0x00);
+ it6161_mipi_rx_set_bits(it6161, 0x3A, 0x80, 0x00);
+ it6161_mipi_rx_set_bits(it6161, 0x3C, 0x80, 0x00);
+ it6161_mipi_rx_set_bits(it6161, 0x3E, 0x80, 0x00);
+ it6161_mipi_rx_set_bits(it6161, 0x41, 0x80, 0x00);
+ it6161_mipi_rx_set_bits(it6161, 0x43, 0x80, 0x00);
+ }
+}
+
+static void mipi_rx_init(struct it6161 *it6161)
+{
+ mipi_rx_configuration(it6161);
+ /* Enable MPRX clock domain */
+ it6161_mipi_rx_set_bits(it6161, 0x05, 0x03, 0x00);
+}
+
+static void hdmi_tx_video_reset(struct it6161 *it6161)
+{
+ struct device *dev = &it6161->i2c_hdmi_tx->dev;
+
+ DRM_DEV_DEBUG_DRIVER(dev, "reg04: 0x%02x reg05: 0x%02x reg6: 0x%02x reg07: 0x%02x reg08: 0x%02x reg0e: 0x%02x",
+ it6161_hdmi_tx_read(it6161, 0x04),
+ it6161_hdmi_tx_read(it6161, 0x05),
+ it6161_hdmi_tx_read(it6161, 0x06),
+ it6161_hdmi_tx_read(it6161, 0x07),
+ it6161_hdmi_tx_read(it6161, 0x08),
+ it6161_hdmi_tx_read(it6161, 0x0e));
+
+ it6161_hdmi_tx_set_bits(it6161, REG_TX_SW_RST, B_HDMITX_VID_RST, B_HDMITX_VID_RST);
+ it6161_hdmi_tx_set_bits(it6161, REG_TX_SW_RST, B_HDMITX_VID_RST, 0x00);
+ msleep(10);
+}
+
+/* DDC master will set to be host */
+static void it6161_hdmi_tx_clear_ddc_fifo(struct it6161 *it6161)
+{
+ it6161_hdmi_tx_change_bank(it6161, 0);
+ it6161_hdmi_tx_write(it6161, REG_TX_DDC_MASTER_CTRL,
+ B_TX_MASTERDDC | B_TX_MASTERHOST);
+ it6161_hdmi_tx_write(it6161, REG_TX_DDC_CMD, CMD_FIFO_CLR);
+ it6161_hdmi_tx_set_bits(it6161, REG_TX_DDC_MASTER_CTRL, B_TX_MASTERHOST, 0x00);
+}
+
+static void hdmi_tx_generate_blank_timing(struct it6161 *it6161)
+{
+ struct drm_display_mode *display_mode = &it6161->display_mode;
+ bool force_hdmi_tx_clock_stable = true;
+ bool force_hdmi_tx_video_stable = true;
+ bool hdmi_tx_by_pass_mode = false;
+ bool de_generation = false;
+ bool enable_de_only = true;
+ u8 polarity;
+ u16 hsync_start, hsync_end, vsync_start, vsync_end, htotal, hde_start, vtotal;
+ u16 vsync_start_2nd = 0, vsync_end_2nd = 0, vsync_rising_at_h_2nd;
+
+ polarity =
+ ((display_mode->flags & DRM_MODE_FLAG_PHSYNC) == DRM_MODE_FLAG_PHSYNC) ? 0x02 : 0x00;
+ polarity |=
+ ((display_mode->flags & DRM_MODE_FLAG_PVSYNC) == DRM_MODE_FLAG_PVSYNC) ? 0x04 : 0x00;
+
+ hsync_start = display_mode->hsync_start - display_mode->hdisplay - 1;
+ hsync_end = hsync_start + display_mode->hsync_end - display_mode->hsync_start;
+ vsync_rising_at_h_2nd = hsync_start + display_mode->htotal / 2;
+ hde_start = display_mode->htotal - display_mode->hsync_start;
+
+ it6161_hdmi_tx_set_bits(it6161, 0xD1, 0x0C,
+ force_hdmi_tx_clock_stable << 3 | force_hdmi_tx_video_stable << 2);
+ it6161_hdmi_tx_set_bits(it6161, 0xA9, 0x80, hdmi_tx_by_pass_mode << 7);
+ it6161_hdmi_tx_set_bits(it6161, 0x90, 0x01, de_generation);
+ it6161_hdmi_tx_write(it6161, 0x91, vsync_rising_at_h_2nd >> 4);
+ it6161_hdmi_tx_set_bits(it6161, 0x90, 0xF0, (vsync_rising_at_h_2nd & 0x00F) << 4);
+ it6161_hdmi_tx_set_bits(it6161, 0x90, 0x06, polarity);
+ it6161_hdmi_tx_write(it6161, 0x95, (u8) hsync_start);
+ it6161_hdmi_tx_write(it6161, 0x96, (u8) hsync_end);
+ it6161_hdmi_tx_write(it6161, 0x97, (hsync_end & 0x0F00) >> 4 | hsync_start >> 8);
+
+ vsync_start = display_mode->vsync_start - display_mode->vdisplay;
+ vsync_end = display_mode->vsync_end - display_mode->vdisplay;
+
+ if ((display_mode->flags & DRM_MODE_FLAG_INTERLACE) != DRM_MODE_FLAG_INTERLACE) {
+ vsync_start_2nd = 0x0FFF;
+ vsync_end_2nd = 0x3F;
+ vtotal = display_mode->vtotal - 1;
+ it6161_hdmi_tx_set_bits(it6161, 0xA5, 0x10, 0x00);
+ } else {
+ vtotal = display_mode->vtotal * 2;
+ it6161_hdmi_tx_set_bits(it6161, 0xA5, 0x10, 0x10);
+ }
+ it6161_hdmi_tx_write(it6161, 0xA0, (u8) vsync_start);
+ it6161_hdmi_tx_write(it6161, 0xA1, (vsync_end & 0x0F) << 4 | vsync_start >> 8);
+ it6161_hdmi_tx_write(it6161, 0xA2, (u8) vsync_start_2nd);
+ it6161_hdmi_tx_write(it6161, 0xA6, (vsync_end_2nd & 0xF0) | vsync_end >> 4);
+ it6161_hdmi_tx_write(it6161, 0xA3, (vsync_end_2nd & 0x0F) << 4 | vsync_start_2nd >> 8);
+ it6161_hdmi_tx_write(it6161, 0xA4, vsync_rising_at_h_2nd);
+
+ it6161_hdmi_tx_set_bits(it6161, 0xB1, 0x51,
+ (hsync_end & 0x1000) >> 6 | (hsync_start & 0x1000) >> 8 | hde_start >> 12);
+ it6161_hdmi_tx_set_bits(it6161, 0xA5, 0x2F,
+ enable_de_only << 5 | vsync_rising_at_h_2nd >> 8);
+ it6161_hdmi_tx_set_bits(it6161, 0xB2, 0x05,
+ (vsync_rising_at_h_2nd & 0x1000) >> 10 | (vsync_rising_at_h_2nd & 0x1000) >> 12);
+
+ htotal = display_mode->htotal - 1;
+ it6161_hdmi_tx_set_bits(it6161, 0x90, 0xF0, (htotal & 0x0F) << 4);
+ it6161_hdmi_tx_write(it6161, 0x91, (htotal & 0x0FF0) >> 4);
+ it6161_hdmi_tx_set_bits(it6161, 0xB2, 0x01, (htotal & 0x1000) >> 12);
+ it6161_hdmi_tx_write(it6161, 0x98, vtotal & 0x0FF);
+ it6161_hdmi_tx_write(it6161, 0x99, (vtotal & 0xF00) >> 8);
+}
+
+/* force abort DDC and reset DDC bus */
+static void it6161_hdmi_tx_abort_ddc(struct it6161 *it6161)
+{
+ struct device *dev = &it6161->i2c_hdmi_tx->dev;
+ u8 sw_reset, ddc_master, retry = 2;
+ u8 uc, timeout, i;
+
+ DRM_DEV_DEBUG_DRIVER(dev, "ddc abort\n");
+ /* save the sw reset, ddc master and cp desire setting */
+ sw_reset = it6161_hdmi_tx_read(it6161, REG_TX_SW_RST);
+ ddc_master = it6161_hdmi_tx_read(it6161, REG_TX_DDC_MASTER_CTRL);
+
+ it6161_hdmi_tx_write(it6161, REG_TX_SW_RST, sw_reset | B_TX_HDCP_RST_HDMITX);
+ it6161_hdmi_tx_write(it6161, REG_TX_DDC_MASTER_CTRL, B_TX_MASTERDDC | B_TX_MASTERHOST);
+
+ /* do abort DDC */
+ for (i = 0; i < retry; i++) {
+ it6161_hdmi_tx_write(it6161, REG_TX_DDC_CMD, CMD_DDC_ABORT);
+ it6161_hdmi_tx_write(it6161, REG_TX_DDC_CMD, CMD_GEN_SCLCLK);
+
+ for (timeout = 0; timeout < 200; timeout++) {
+ uc = it6161_hdmi_tx_read(it6161, REG_TX_DDC_STATUS);
+ if (uc & B_TX_DDC_DONE)
+ break;
+
+ if (uc & (B_TX_DDC_NOACK | B_TX_DDC_WAITBUS | B_TX_DDC_ARBILOSE)) {
+ DRM_DEV_ERROR(dev, "it6161_hdmi_tx_abort_ddc Fail by reg16=%02X\n", (int)uc);
+ break;
+ }
+ /* delay 1 ms to stable */
+ msleep(1);
+ }
+ }
+}
+
+static bool hdmi_tx_get_video_state(struct it6161 *it6161)
+{
+ return B_TXVIDSTABLE & it6161_hdmi_tx_read(it6161, REG_TX_SYS_STATUS);
+}
+
+static inline bool hdmi_tx_get_sink_hpd(struct it6161 *it6161)
+{
+ return it6161_hdmi_tx_read(it6161, REG_TX_SYS_STATUS) & B_TX_HPDETECT;
+}
+
+static bool it6161_ddc_op_finished(struct it6161 *it6161)
+{
+ int reg16 = it6161_hdmi_tx_read(it6161, REG_TX_DDC_STATUS);
+
+ if (reg16 < 0)
+ return false;
+
+ return (reg16 & B_TX_DDC_DONE) == B_TX_DDC_DONE;
+}
+
+static int it6161_ddc_wait(struct it6161 *it6161)
+{
+ struct device *dev = &it6161->i2c_mipi_rx->dev;
+ int status;
+ unsigned long timeout;
+
+ timeout = jiffies + msecs_to_jiffies(AUX_WAIT_TIMEOUT_MS) + 1;
+
+ while (!it6161_ddc_op_finished(it6161)) {
+ if (time_after(jiffies, timeout)) {
+ DRM_DEV_ERROR(dev, "Timed out waiting AUX to finish");
+ return -ETIMEDOUT;
+ }
+ usleep_range(1000, 2000);
+ }
+
+ status = it6161_hdmi_tx_read(it6161, REG_TX_DDC_STATUS);
+ if (status < 0) {
+ DRM_DEV_ERROR(dev, "Failed to read DDC channel: 0x%02x", status);
+ return status;
+ }
+
+ if (status & B_TX_DDC_DONE)
+ return 0;
+ else {
+ DRM_DEV_ERROR(dev, "DDC error: 0x%02x", status);
+ return -EIO;
+ }
+}
+
+static void hdmi_tx_ddc_operation(struct it6161 *it6161, u8 addr, u8 offset, u8 size,
+ u8 segment, u8 cmd)
+{
+ size = min_t(u8, size, DDC_FIFO_MAXREQ);
+ it6161_hdmi_tx_change_bank(it6161, 0);
+ it6161_hdmi_tx_write(it6161, REG_TX_DDC_MASTER_CTRL, B_TX_MASTERDDC | B_TX_MASTERHOST);
+ it6161_hdmi_tx_write(it6161, REG_TX_DDC_HEADER, addr);
+ it6161_hdmi_tx_write(it6161, REG_TX_DDC_REQOFF, offset);
+ it6161_hdmi_tx_write(it6161, REG_TX_DDC_REQCOUNT, size);
+ it6161_hdmi_tx_write(it6161, REG_TX_DDC_EDIDSEG, segment);
+ it6161_hdmi_tx_write(it6161, REG_TX_DDC_CMD, cmd);
+}
+
+static int it6161_ddc_get_edid_operation(struct it6161 *it6161, u8 *buffer,
+ u8 segment, u8 offset, u8 size)
+{
+ struct device *dev = &it6161->i2c_hdmi_tx->dev;
+ int status, i;
+
+ if (!buffer)
+ return -ENOMEM;
+
+ if (it6161_hdmi_tx_read(it6161, REG_TX_INT_STAT1) & B_TX_INT_DDC_BUS_HANG) {
+ DRM_DEV_ERROR(dev, "Called it6161_hdmi_tx_abort_ddc()");
+ it6161_hdmi_tx_abort_ddc(it6161);
+ }
+
+ it6161_hdmi_tx_clear_ddc_fifo(it6161);
+ status = it6161_ddc_wait(it6161);
+ if (status < 0)
+ goto error;
+
+ hdmi_tx_ddc_operation(it6161, DDC_EDID_ADDRESS, offset, size, segment, CMD_EDID_READ);
+ status = it6161_ddc_wait(it6161);
+ if (status < 0)
+ goto error;
+
+ for (i = 0; i < size; i++) {
+ status = it6161_hdmi_tx_read(it6161, REG_TX_DDC_READFIFO);
+ if (status < 0)
+ goto error;
+
+ buffer[i] = status;
+ }
+
+ return i;
+
+error:
+ return status;
+}
+
+static int it6161_get_edid_block(void *data, u8 *buf, u32 block_num, size_t len)
+{
+ struct it6161 *it6161 = data;
+ u8 offset, step = 8;
+ int ret;
+
+ step = min_t(u8, step, DDC_FIFO_MAXREQ);
+
+ for (offset = 0; offset < len; offset += step) {
+ ret = it6161_ddc_get_edid_operation(it6161, buf + offset,
+ block_num / 2, (block_num % 2) * EDID_LENGTH + offset, step);
+ if (ret < 0)
+ return ret;
+ }
+ return 0;
+}
+
+static void hdmi_tx_set_capability_from_edid_parse(struct it6161 *it6161, struct edid *edid)
+{
+ struct device *dev = &it6161->i2c_hdmi_tx->dev;
+ struct drm_display_info *info = &it6161->connector.display_info;
+
+ it6161->hdmi_mode = drm_detect_hdmi_monitor(edid);
+ it6161->support_audio = drm_detect_monitor_audio(edid);
+
+ it6161->hdmi_tx_output_color_space = OUTPUT_COLOR_MODE;
+ it6161->hdmi_tx_input_color_space = INPUT_COLOR_MODE;
+ if (it6161->hdmi_tx_output_color_space == F_MODE_YUV444) {
+ if ((info->color_formats & DRM_COLOR_FORMAT_YCRCB444) != DRM_COLOR_FORMAT_YCRCB444) {
+ it6161->hdmi_tx_output_color_space &= ~F_MODE_CLRMOD_MASK;
+ it6161->hdmi_tx_output_color_space |= F_MODE_RGB444;
+ }
+ }
+
+ if (it6161->hdmi_tx_output_color_space == F_MODE_YUV422) {
+ if ((info->color_formats & DRM_COLOR_FORMAT_YCRCB422) != DRM_COLOR_FORMAT_YCRCB422) {
+ it6161->hdmi_tx_output_color_space &= ~F_MODE_CLRMOD_MASK;
+ it6161->hdmi_tx_output_color_space |= F_MODE_RGB444;
+ }
+ }
+ DRM_DEV_DEBUG_DRIVER(dev, "%s mode, monitor %ssupport audio, outputcolormode:%d color_formats:0x%08x color_depth:%d",
+ it6161->hdmi_mode ? "HDMI" : "DVI",
+ it6161->support_audio ? "" : "not ",
+ it6161->hdmi_tx_output_color_space,
+ info->color_formats,
+ info->bpc);
+
+ if ((info->color_formats & DRM_COLOR_FORMAT_RGB444) == DRM_COLOR_FORMAT_RGB444)
+ DRM_DEV_INFO(dev, "support RGB444 output");
+ if ((info->color_formats & DRM_COLOR_FORMAT_YCRCB444) == DRM_COLOR_FORMAT_YCRCB444)
+ DRM_DEV_INFO(dev, "support YUV444 output");
+ if ((info->color_formats & DRM_COLOR_FORMAT_YCRCB422) == DRM_COLOR_FORMAT_YCRCB422)
+ DRM_DEV_INFO(dev, "support YUV422 output");
+}
+
+static void it6161_variable_config(struct it6161 *it6161)
+{
+ it6161->hdmi_tx_mode = HDMI_TX_MODE;
+ it6161->mipi_rx_lane_count = MIPI_RX_LANE_COUNT;
+}
+
+static struct edid *it6161_get_edid(struct it6161 *it6161)
+{
+ struct device *dev = &it6161->i2c_hdmi_tx->dev;
+ struct edid *edid;
+
+ edid = drm_do_get_edid(&it6161->connector, it6161_get_edid_block, it6161);
+ if (!edid) {
+ DRM_DEV_ERROR(dev, "Failed to read EDID\n");
+ return 0;
+ }
+
+ hdmi_tx_set_capability_from_edid_parse(it6161, edid);
+
+ return edid;
+}
+
+static int it6161_get_modes(struct drm_connector *connector)
+{
+ struct it6161 *it6161 = connector_to_it6161(connector);
+ int err, num_modes = 0;
+ struct edid *edid;
+ struct device *dev = &it6161->i2c_mipi_rx->dev;
+
+ mutex_lock(&it6161->mode_lock);
+
+ edid = it6161_get_edid(it6161);
+ if (!edid) {
+ DRM_DEV_ERROR(dev, "Failed to read EDID\n");
+ return 0;
+ }
+
+ err = drm_connector_update_edid_property(connector, edid);
+ if (err) {
+ DRM_DEV_ERROR(dev, "Failed to update EDID property: %d", err);
+ goto unlock;
+ }
+
+ num_modes = drm_add_edid_modes(connector, edid);
+
+ kfree(edid);
+unlock:
+ DRM_DEV_DEBUG_DRIVER(dev, "edid mode number:%d", num_modes);
+ mutex_unlock(&it6161->mode_lock);
+
+ return num_modes;
+}
+
+static const struct drm_connector_helper_funcs it6161_connector_helper_funcs = {
+ .get_modes = it6161_get_modes,
+};
+
+static enum drm_connector_status it6161_detect(struct drm_connector *connector, bool force)
+{
+ struct it6161 *it6161 = connector_to_it6161(connector);
+ struct device *dev = &it6161->i2c_hdmi_tx->dev;
+ enum drm_connector_status status = connector_status_disconnected;
+ bool hpd;
+
+ hpd = hdmi_tx_get_sink_hpd(it6161);
+ if (hpd) {
+ it6161_variable_config(it6161);
+ status = connector_status_connected;
+ }
+ DRM_DEV_INFO(dev, "hpd:%s\n", hpd ? "high" : "low");
+
+ it6161_set_interrupts_active_level(HIGH);
+ it6161_mipi_rx_int_mask_enable(it6161);
+ it6161_hdmi_tx_int_mask_enable(it6161);
+
+ return status;
+}
+
+static const struct drm_connector_funcs it6161_connector_funcs = {
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .detect = it6161_detect,
+ .destroy = drm_connector_cleanup,
+ .reset = drm_atomic_helper_connector_reset,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static int it6161_attach_dsi(struct it6161 *it6161)
+{
+ struct mipi_dsi_host *host;
+ struct mipi_dsi_device *dsi;
+ int ret = 0;
+ const struct mipi_dsi_device_info info = {.type = "it6161", };
+ struct device *dev = &it6161->i2c_hdmi_tx->dev;
+
+ DRM_DEV_DEBUG_DRIVER(dev, "attach\n");
+ host = of_find_mipi_dsi_host_by_node(it6161->host_node);
+ if (!host) {
+ DRM_DEV_ERROR(dev, "it6161 failed to find dsi host\n");
+ return -EPROBE_DEFER;
+ }
+
+ dsi = mipi_dsi_device_register_full(host, &info);
+ if (IS_ERR(dsi)) {
+ DRM_DEV_ERROR(dev, "it6161 failed to create dsi device\n");
+ ret = PTR_ERR(dsi);
+ goto err_dsi_device;
+ }
+
+ it6161->dsi = dsi;
+
+ dsi->lanes = MIPI_RX_LANE_COUNT;
+ dsi->format = MIPI_DSI_FMT_RGB888;
+ dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
+ MIPI_DSI_MODE_NO_EOT_PACKET | MIPI_DSI_MODE_VIDEO_HSE;
+
+ ret = mipi_dsi_attach(dsi);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dev, "it6161 failed to attach dsi to host\n");
+ goto err_dsi_attach;
+ }
+
+ return 0;
+
+err_dsi_attach:
+ mipi_dsi_device_unregister(dsi);
+err_dsi_device:
+ return ret;
+}
+
+static int it6161_connector_init(struct drm_bridge *bridge, struct it6161 *it6161)
+{
+ struct device *dev;
+ int ret;
+
+ dev = &it6161->i2c_mipi_rx->dev;
+
+ if (!bridge->encoder) {
+ DRM_DEV_ERROR(dev, "Parent encoder object not found");
+ return -ENODEV;
+ }
+
+ it6161->connector.polled = DRM_CONNECTOR_POLL_HPD;
+
+ ret = drm_connector_init(bridge->dev, &it6161->connector,
+ &it6161_connector_funcs, DRM_MODE_CONNECTOR_HDMIA);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dev, "Failed to initialize connector: %d", ret);
+ return ret;
+ }
+
+ drm_connector_helper_add(&it6161->connector, &it6161_connector_helper_funcs);
+ drm_connector_attach_encoder(&it6161->connector, bridge->encoder);
+
+ return 0;
+}
+
+static int it6161_bridge_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
+{
+ struct it6161 *it6161 = bridge_to_it6161(bridge);
+ int ret;
+
+ if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) {
+ ret = it6161_connector_init(bridge, it6161);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = it6161_attach_dsi(it6161);
+
+ return ret;
+}
+
+static void it6161_detach_dsi(struct it6161 *it6161)
+{
+ mipi_dsi_detach(it6161->dsi);
+ mipi_dsi_device_unregister(it6161->dsi);
+}
+
+static void it6161_bridge_detach(struct drm_bridge *bridge)
+{
+ struct it6161 *it6161 = bridge_to_it6161(bridge);
+
+ drm_connector_unregister(&it6161->connector);
+ drm_connector_cleanup(&it6161->connector);
+ it6161_detach_dsi(it6161);
+}
+
+static enum drm_mode_status
+it6161_bridge_mode_valid(struct drm_bridge *bridge,
+ const struct drm_display_info *info,
+ const struct drm_display_mode *mode)
+{
+ if (mode->clock > 108000)
+ return MODE_CLOCK_HIGH;
+
+ /* TODO, Only 480p60 work with imx8ulp now */
+ if (mode->vdisplay > 480)
+ return MODE_BAD_VVALUE;
+
+ return MODE_OK;
+}
+
+static void it6161_bridge_mode_set(struct drm_bridge *bridge,
+ const struct drm_display_mode *mode,
+ const struct drm_display_mode *adjusted_mode)
+{
+ struct it6161 *it6161 = bridge_to_it6161(bridge);
+ struct device *dev = &it6161->i2c_hdmi_tx->dev;
+ u8 polarity;
+
+ DRM_DEV_DEBUG_DRIVER(dev, " mode " DRM_MODE_FMT "\n", DRM_MODE_ARG(mode));
+ DRM_DEV_DEBUG_DRIVER(dev, "adj mode " DRM_MODE_FMT "\n", DRM_MODE_ARG(adjusted_mode));
+
+ memcpy(&it6161->display_mode, mode, sizeof(struct drm_display_mode));
+
+ polarity = ((adjusted_mode->flags & DRM_MODE_FLAG_PHSYNC) == DRM_MODE_FLAG_PHSYNC) ? 0x01 : 0x00;
+ polarity |= ((adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC) == DRM_MODE_FLAG_PVSYNC) ? 0x02 : 0x00;
+
+ it6161_mipi_rx_set_bits(it6161, 0x4E, 0x03, polarity);
+}
+
+static void it6161_bridge_enable(struct drm_bridge *bridge)
+{
+ struct it6161 *it6161 = bridge_to_it6161(bridge);
+ struct device *dev = &it6161->i2c_hdmi_tx->dev;
+
+ DRM_DEV_DEBUG_DRIVER(dev, "start");
+
+ if (it6161->bridge_enable)
+ return;
+
+ mipi_rx_init(it6161);
+ hdmi_tx_init(it6161);
+ it6161_set_interrupts_active_level(HIGH);
+ it6161_mipi_rx_int_mask_enable(it6161);
+ it6161_hdmi_tx_int_mask_enable(it6161);
+
+ it6161->bridge_enable = true;
+
+}
+
+static void it6161_bridge_disable(struct drm_bridge *bridge)
+{
+ struct it6161 *it6161 = bridge_to_it6161(bridge);
+ struct device *dev = &it6161->i2c_hdmi_tx->dev;
+
+ DRM_DEV_DEBUG_DRIVER(dev, "start");
+
+ /* only keep HPD for cabe detect */
+ it6161_mipi_rx_int_mask_disable(it6161);
+ it6161_hdmi_tx_int_mask_disable(it6161);
+ it6161_hdmi_tx_set_bits(it6161, 0x0F, 0x03, 0x00);
+ it6161_hdmi_tx_write(it6161, REG_TX_INT_MASK1, ~B_TX_HPD_MASK);
+
+ it6161->bridge_enable = false;
+}
+
+static enum drm_connector_status it6161_bridge_detect(struct drm_bridge *bridge)
+{
+ struct it6161 *it6161 = bridge_to_it6161(bridge);
+ enum drm_connector_status status = connector_status_disconnected;
+ bool hpd = hdmi_tx_get_sink_hpd(it6161);
+ struct device *dev = &it6161->i2c_hdmi_tx->dev;
+
+ DRM_DEV_DEBUG_DRIVER(dev, "hpd:%s", hpd ? "high" : "low");
+
+ if (hpd) {
+ it6161_variable_config(it6161);
+ status = connector_status_connected;
+ }
+
+ it6161_set_interrupts_active_level(HIGH);
+ it6161_mipi_rx_int_mask_enable(it6161);
+ it6161_hdmi_tx_int_mask_enable(it6161);
+
+ return status;
+}
+
+static struct edid *it6161_bridge_get_edid(struct drm_bridge *bridge,
+ struct drm_connector *connector)
+{
+ struct it6161 *it6161 = bridge_to_it6161(bridge);
+
+ return it6161_get_edid(it6161);
+}
+
+static const struct drm_bridge_funcs it6161_bridge_funcs = {
+ .attach = it6161_bridge_attach,
+ .detach = it6161_bridge_detach,
+ .mode_valid = it6161_bridge_mode_valid,
+ .mode_set = it6161_bridge_mode_set,
+ .enable = it6161_bridge_enable,
+ .disable = it6161_bridge_disable,
+ .detect = it6161_bridge_detect,
+ .get_edid = it6161_bridge_get_edid,
+};
+
+static bool it6161_check_device_ready(struct it6161 *it6161)
+{
+ struct device *dev = &it6161->i2c_hdmi_tx->dev;
+ u8 Vendor_ID[2], Device_ID[2];
+
+ Vendor_ID[0] = it6161_mipi_rx_read(it6161, 0x00);
+ Vendor_ID[1] = it6161_mipi_rx_read(it6161, 0x01);
+ Device_ID[0] = it6161_mipi_rx_read(it6161, 0x02);
+ Device_ID[1] = it6161_mipi_rx_read(it6161, 0x03);
+ if (Vendor_ID[0] == 0x54 && Vendor_ID[1] == 0x49 &&
+ Device_ID[0] == 0x61 && Device_ID[1] == 0x61) {
+ DRM_DEV_INFO(dev, "Find it6161 revision: 0x%2x",
+ (u32) it6161_mipi_rx_read(it6161, 0x04));
+ return true;
+ }
+ DRM_DEV_INFO(dev, "find it6161 Fail");
+ return false;
+}
+
+static void it6161_hdmi_tx_set_av_mute(struct it6161 *it6161, u8 bEnable)
+{
+ it6161_hdmi_tx_change_bank(it6161, 0);
+ it6161_hdmi_tx_set_bits(it6161, REG_TX_GCP,
+ B_TX_SETAVMUTE, bEnable ? B_TX_SETAVMUTE : 0);
+ it6161_hdmi_tx_write(it6161, REG_TX_PKT_GENERAL_CTRL,
+ B_TX_ENABLE_PKT | B_TX_REPEAT_PKT);
+}
+
+static void hdmi_tx_setup_pclk_div2(struct it6161 *it6161)
+{
+ struct device *dev = &it6161->i2c_hdmi_tx->dev;
+
+ if (HDMI_TX_PCLK_DIV2) {
+ DRM_DEV_DEBUG_DRIVER(dev, "PCLK Divided by 2 mode");
+ it6161_hdmi_tx_set_bits(it6161, REG_TX_INPUT_MODE,
+ B_TX_PCLKDIV2, B_TX_PCLKDIV2);
+ }
+}
+
+/*************************************************************************
+ * Function: hdmi_tx_setup_csc
+ * Parameter: input_mode -
+ * D[1:0] - Color Mode
+ * D[4] - Colorimetry 0: ITU_BT601 1: ITU_BT709
+ * D[5] - Quantization 0: 0_255 1: 16_235
+ * D[6] - Up/Dn Filter 'Required'
+ * 0: no up/down filter
+ * 1: enable up/down filter when csc need.
+ * D[7] - Dither Filter 'Required'
+ * 0: no dither enabled.
+ * 1: enable dither and dither free go "when required".
+ * output_mode -
+ * D[1:0] - Color mode.
+ * Return: N/A
+ * Remark: reg72~reg8D will be programmed depended the input with table
+ * **********************************************************************/
+static void hdmi_tx_setup_csc(struct it6161 *it6161)
+{
+ struct device *dev = &it6161->i2c_hdmi_tx->dev;
+ u8 ucData, csc = 0, i;
+ u8 filter = 0; /* filter is for Video CTRL DN_FREE_GO,EN_DITHER,and ENUDFILT */
+ u8 input_mode = it6161->hdmi_tx_input_color_space;
+ u8 output_mode = it6161->hdmi_tx_output_color_space;
+ u8 *ptable = NULL;
+
+ /* (1) YUV422 in,RGB/YUV444 output (Output is 8-bit,input is 12-bit)
+ * (2) YUV444/422 in,RGB output (CSC enable,and output is not YUV422)
+ * (3) RGB in,YUV444 output (CSC enable,and output is not YUV422)
+ *
+ * YUV444/RGB24 <-> YUV422 need set up/down filter.
+ */
+ DRM_DEV_DEBUG_DRIVER(dev, "hdmi_tx_setup_csc(u8 input_mode = %x,u8 output_mode = %x)\n",
+ (int)input_mode, (int)output_mode);
+
+ switch (input_mode & F_MODE_CLRMOD_MASK) {
+ /* YUV444 INPUT */
+ case F_MODE_YUV444:
+ switch (output_mode & F_MODE_CLRMOD_MASK) {
+ case F_MODE_YUV444:
+ csc = B_HDMITX_CSC_BYPASS;
+ break;
+ case F_MODE_YUV422:
+ /* YUV444 to YUV422 need up/down filter for processing. */
+ if (input_mode & F_VIDMODE_EN_UDFILT)
+ filter |= B_TX_EN_UDFILTER;
+ csc = B_HDMITX_CSC_BYPASS;
+ break;
+ case F_MODE_RGB444:
+ csc = B_HDMITX_CSC_YUV2RGB;
+ /* YUV444 to RGB24 need dither */
+ if (input_mode & F_VIDMODE_EN_DITHER)
+ filter |= B_TX_EN_DITHER | B_TX_DNFREE_GO;
+ break;
+ }
+ break;
+
+ /* YUV422 INPUT */
+ case F_MODE_YUV422:
+ switch (output_mode & F_MODE_CLRMOD_MASK) {
+ case F_MODE_YUV444:
+ csc = B_HDMITX_CSC_BYPASS;
+ if (input_mode & F_VIDMODE_EN_UDFILT)
+ filter |= B_TX_EN_UDFILTER;
+ else if (input_mode & F_VIDMODE_EN_DITHER)
+ filter |= B_TX_EN_DITHER | B_TX_DNFREE_GO;
+ break;
+ case F_MODE_YUV422:
+ csc = B_HDMITX_CSC_BYPASS;
+ break;
+ case F_MODE_RGB444:
+ csc = B_HDMITX_CSC_YUV2RGB;
+ if (input_mode & F_VIDMODE_EN_UDFILT)
+ filter |= B_TX_EN_UDFILTER;
+ else if (input_mode & F_VIDMODE_EN_DITHER)
+ filter |= B_TX_EN_DITHER | B_TX_DNFREE_GO;
+ break;
+ }
+ break;
+
+ /* RGB444 INPUT */
+ case F_MODE_RGB444:
+ switch (output_mode & F_MODE_CLRMOD_MASK) {
+ case F_MODE_YUV444:
+ csc = B_HDMITX_CSC_RGB2YUV;
+ if (input_mode & F_VIDMODE_EN_DITHER)
+ filter |= B_TX_EN_DITHER | B_TX_DNFREE_GO;
+ break;
+ case F_MODE_YUV422:
+ if (input_mode & F_VIDMODE_EN_UDFILT)
+ filter |= B_TX_EN_UDFILTER;
+ else if (input_mode & F_VIDMODE_EN_DITHER)
+ filter |= B_TX_EN_DITHER | B_TX_DNFREE_GO;
+ csc = B_HDMITX_CSC_RGB2YUV;
+ break;
+ case F_MODE_RGB444:
+ csc = B_HDMITX_CSC_BYPASS;
+ break;
+ }
+ break;
+ }
+
+ /* set the CSC metrix registers by colorimetry and quantization */
+ if (csc == B_HDMITX_CSC_RGB2YUV) {
+ switch (input_mode & (F_VIDMODE_ITU709 | F_VIDMODE_16_235)) {
+ case F_VIDMODE_ITU709 | F_VIDMODE_16_235:
+ ptable = bCSCMtx_RGB2YUV_ITU709_16_235;
+ break;
+ case F_VIDMODE_ITU709 | F_VIDMODE_0_255:
+ ptable = bCSCMtx_RGB2YUV_ITU709_0_255;
+ break;
+ case F_VIDMODE_ITU601 | F_VIDMODE_16_235:
+ ptable = bCSCMtx_RGB2YUV_ITU601_16_235;
+ break;
+ case F_VIDMODE_ITU601 | F_VIDMODE_0_255:
+ default:
+ ptable = bCSCMtx_RGB2YUV_ITU601_0_255;
+ break;
+ }
+ }
+
+ if (csc == B_HDMITX_CSC_YUV2RGB) {
+ switch (input_mode & (F_VIDMODE_ITU709 | F_VIDMODE_16_235)) {
+ case F_VIDMODE_ITU709 | F_VIDMODE_16_235:
+ ptable = bCSCMtx_YUV2RGB_ITU709_16_235;
+ break;
+ case F_VIDMODE_ITU709 | F_VIDMODE_0_255:
+ ptable = bCSCMtx_YUV2RGB_ITU709_0_255;
+ break;
+ case F_VIDMODE_ITU601 | F_VIDMODE_16_235:
+ ptable = bCSCMtx_YUV2RGB_ITU601_16_235;
+ break;
+ case F_VIDMODE_ITU601 | F_VIDMODE_0_255:
+ default:
+ ptable = bCSCMtx_YUV2RGB_ITU601_0_255;
+ break;
+ }
+ }
+
+ if (csc == B_HDMITX_CSC_BYPASS)
+ it6161_hdmi_tx_set_bits(it6161, 0xF, 0x10, 0x10);
+ else {
+ if (ptable != NULL)
+ for (i = 0; i < SIZEOF_CSCMTX; i++)
+ it6161_hdmi_tx_write(it6161, REG_TX_CSC_YOFF + i, ptable[i]);
+ it6161_hdmi_tx_set_bits(it6161, 0xF, 0x10, 0x00);
+ }
+
+ ucData = it6161_hdmi_tx_read(it6161,
+ REG_TX_CSC_CTRL) & ~(M_TX_CSC_SEL |
+ B_TX_DNFREE_GO |
+ B_TX_EN_DITHER |
+ B_TX_EN_UDFILTER);
+ ucData |= filter | csc;
+
+ it6161_hdmi_tx_write(it6161, REG_TX_CSC_CTRL, ucData);
+}
+
+static void hdmi_tx_setup_afe(struct it6161 *it6161, u8 level)
+{
+ struct device *dev = &it6161->i2c_hdmi_tx->dev;
+
+ it6161_hdmi_tx_write(it6161, REG_TX_AFE_DRV_CTRL, B_TX_AFE_DRV_RST);
+ switch (level) {
+ case PCLK_HIGH:
+ it6161_hdmi_tx_set_bits(it6161, 0x62, 0x90, 0x80);
+ it6161_hdmi_tx_set_bits(it6161, 0x64, 0x89, 0x80);
+ it6161_hdmi_tx_set_bits(it6161, 0x68, 0x10, 0x00);
+ it6161_hdmi_tx_set_bits(it6161, 0x66, 0x80, 0x80);
+ break;
+ default:
+ it6161_hdmi_tx_set_bits(it6161, 0x62, 0x90, 0x10);
+ it6161_hdmi_tx_set_bits(it6161, 0x64, 0x89, 0x09);
+ it6161_hdmi_tx_set_bits(it6161, 0x68, 0x10, 0x10);
+ break;
+ }
+ DRM_DEV_DEBUG_DRIVER(dev, "setup afe: %s", level ? "high" : "low");
+}
+
+static void hdmi_tx_fire_afe(struct it6161 *it6161)
+{
+ it6161_hdmi_tx_change_bank(it6161, 0x00);
+ it6161_hdmi_tx_write(it6161, REG_TX_AFE_DRV_CTRL, 0x00);
+}
+
+static void hdmi_tx_disable_video_output(struct it6161 *it6161)
+{
+ it6161_hdmi_tx_set_bits(it6161, REG_TX_SW_RST, B_HDMITX_VID_RST, B_HDMITX_VID_RST);
+ it6161_hdmi_tx_write(it6161, REG_TX_AFE_DRV_CTRL, B_TX_AFE_DRV_RST | B_TX_AFE_DRV_PWD);
+ it6161_hdmi_tx_set_bits(it6161, 0x62, 0x90, 0x00);
+ it6161_hdmi_tx_set_bits(it6161, 0x64, 0x89, 0x00);
+}
+
+static void hdmi_tx_enable_video_output(struct it6161 *it6161, u8 level)
+{
+ it6161_hdmi_tx_write(it6161, REG_TX_SW_RST,
+ B_HDMITX_AUD_RST | B_TX_AREF_RST | B_TX_HDCP_RST_HDMITX);
+ it6161_hdmi_tx_change_bank(it6161, 1);
+ it6161_hdmi_tx_write(it6161, REG_TX_AVIINFO_DB1, 0x00);
+ it6161_hdmi_tx_change_bank(it6161, 0);
+
+ if (it6161->hdmi_mode)
+ it6161_hdmi_tx_set_av_mute(it6161, true);
+
+ hdmi_tx_setup_pclk_div2(it6161);
+ hdmi_tx_setup_csc(it6161);
+ it6161_hdmi_tx_write(it6161, REG_TX_HDMI_MODE,
+ it6161->hdmi_mode ? B_TX_HDMI_MODE : B_TX_DVI_MODE);
+ hdmi_tx_setup_afe(it6161, level);
+ hdmi_tx_fire_afe(it6161);
+}
+
+static void setHDMITX_ChStat(struct it6161 *it6161, u8 ucIEC60958ChStat[])
+{
+ u8 uc;
+
+ it6161_hdmi_tx_change_bank(it6161, 1);
+ uc = (ucIEC60958ChStat[0] << 1) & 0x7C;
+ it6161_hdmi_tx_write(it6161, REG_TX_AUDCHST_MODE, uc);
+ it6161_hdmi_tx_write(it6161, REG_TX_AUDCHST_CAT, ucIEC60958ChStat[1]);
+ it6161_hdmi_tx_write(it6161, REG_TX_AUDCHST_SRCNUM, ucIEC60958ChStat[2] & 0xF);
+ it6161_hdmi_tx_write(it6161, REG_TX_AUD0CHST_CHTNUM, (ucIEC60958ChStat[2] >> 4) & 0xF);
+ it6161_hdmi_tx_write(it6161, REG_TX_AUDCHST_CA_FS, ucIEC60958ChStat[3]);
+ it6161_hdmi_tx_write(it6161, REG_TX_AUDCHST_OFS_WL, ucIEC60958ChStat[4]);
+ it6161_hdmi_tx_change_bank(it6161, 0);
+}
+
+static void setHDMITX_LPCMAudio(u8 AudioSrcNum, u8 AudSWL, u8 bAudInterface)
+{
+ u8 AudioEnable = 0, AudioFormat = 0, bTDMSetting;
+
+ switch (AudSWL) {
+ case 16:
+ AudioEnable |= M_TX_AUD_16BIT;
+ break;
+ case 18:
+ AudioEnable |= M_TX_AUD_18BIT;
+ break;
+ case 20:
+ AudioEnable |= M_TX_AUD_20BIT;
+ break;
+ case 24:
+ default:
+ AudioEnable |= M_TX_AUD_24BIT;
+ break;
+ }
+ if (bAudInterface == SPDIF) {
+ AudioFormat &= ~0x40;
+ AudioEnable |= B_TX_AUD_SPDIF | B_TX_AUD_EN_I2S0;
+ } else {
+ AudioFormat |= 0x40;
+ switch (AudioSrcNum) {
+ case 4:
+ AudioEnable |=
+ B_TX_AUD_EN_I2S3 | B_TX_AUD_EN_I2S2 |
+ B_TX_AUD_EN_I2S1 | B_TX_AUD_EN_I2S0;
+ break;
+
+ case 3:
+ AudioEnable |=
+ B_TX_AUD_EN_I2S2 | B_TX_AUD_EN_I2S1 | B_TX_AUD_EN_I2S0;
+ break;
+
+ case 2:
+ AudioEnable |= B_TX_AUD_EN_I2S1 | B_TX_AUD_EN_I2S0;
+ break;
+
+ case 1:
+ default:
+ AudioFormat &= ~0x40;
+ AudioEnable |= B_TX_AUD_EN_I2S0;
+ break;
+
+ }
+ }
+ AudioFormat |= 0x01;
+ it6161->bAudioChannelEnable = AudioEnable;
+
+ it6161_hdmi_tx_change_bank(it6161, 0);
+ it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL0, AudioEnable & 0xF0);
+
+ it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL1, AudioFormat);
+ it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_FIFOMAP, 0xE4);
+
+ if (bAudInterface == SPDIF)
+ it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL3, B_TX_CHSTSEL);
+ else
+ it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL3, 0);
+
+ it6161_hdmi_tx_write(it6161, REG_TX_AUD_SRCVALID_FLAT, 0x00);
+ it6161_hdmi_tx_write(it6161, REG_TX_AUD_HDAUDIO, 0x00);
+
+ if (bAudInterface == SPDIF) {
+ u8 i;
+
+ it6161_hdmi_tx_set_bits(it6161, 0x5c, (1 << 6), (1 << 6));
+ for (i = 0; i < 100; i++)
+ if (it6161_hdmi_tx_read(it6161, REG_TX_CLK_STATUS2) & B_TX_OSF_LOCK)
+ break; /* stable clock. */
+ } else {
+ bTDMSetting = it6161_hdmi_tx_read(it6161, REG_TX_AUD_HDAUDIO);
+ if (bAudInterface == TDM) {
+ bTDMSetting |= B_TX_TDM;
+ bTDMSetting &= 0x9F;
+ bTDMSetting |= (AudioSrcNum - 1) << 5;
+ } else
+ bTDMSetting &= ~B_TX_TDM;
+
+ /* 1 channel NLPCM, no TDM mode. */
+ it6161_hdmi_tx_write(it6161, REG_TX_AUD_HDAUDIO, bTDMSetting);
+ }
+}
+
+static void setHDMITX_NLPCMAudio(u8 bAudInterface)
+{
+ u8 AudioEnable, AudioFormat;
+ u8 i;
+
+ /* NLPCM must use standard I2S mode. */
+ AudioFormat = 0x01;
+ if (bAudInterface == SPDIF)
+ AudioEnable = M_TX_AUD_24BIT | B_TX_AUD_SPDIF;
+ else
+ AudioEnable = M_TX_AUD_24BIT;
+
+ it6161_hdmi_tx_change_bank(it6161, 0);
+ it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL0, AudioEnable);
+ it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL1, 0x01);
+ it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_FIFOMAP, 0xE4);
+ it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL3, B_TX_CHSTSEL);
+ it6161_hdmi_tx_write(it6161, REG_TX_AUD_SRCVALID_FLAT, 0x00);
+ it6161_hdmi_tx_write(it6161, REG_TX_AUD_HDAUDIO, 0x00);
+
+ if (bAudInterface == SPDIF) {
+ for (i = 0; i < 100; i++)
+ if (it6161_hdmi_tx_read(it6161, REG_TX_CLK_STATUS2) & B_TX_OSF_LOCK)
+ break;
+ } else {
+ i = it6161_hdmi_tx_read(it6161, REG_TX_AUD_HDAUDIO);
+ i &= ~B_TX_TDM;
+ /* 2 channel NLPCM, no TDM mode. */
+ it6161_hdmi_tx_write(it6161, REG_TX_AUD_HDAUDIO, i);
+ }
+ it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL0,
+ AudioEnable | B_TX_AUD_EN_I2S0);
+}
+
+static void setHDMITX_HBRAudio(u8 bAudInterface)
+{
+ it6161_hdmi_tx_change_bank(it6161, 0);
+
+ it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL1, 0x47);
+ it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_FIFOMAP, 0xE4);
+
+ if (bAudInterface == SPDIF) {
+ it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL0,
+ M_TX_AUD_24BIT | B_TX_AUD_SPDIF);
+ it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL3, B_TX_CHSTSEL);
+ } else {
+ it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL0, M_TX_AUD_24BIT);
+ it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL3, 0);
+ }
+ it6161_hdmi_tx_write(it6161, REG_TX_AUD_SRCVALID_FLAT, 0x08);
+ it6161_hdmi_tx_write(it6161, REG_TX_AUD_HDAUDIO, B_TX_HBR);
+
+ if (bAudInterface == SPDIF) {
+ u8 i;
+
+ for (i = 0; i < 100; i++)
+ if (it6161_hdmi_tx_read(it6161, REG_TX_CLK_STATUS2) & B_TX_OSF_LOCK)
+ break;
+ it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL0,
+ M_TX_AUD_24BIT | B_TX_AUD_SPDIF | B_TX_AUD_EN_SPDIF);
+ } else {
+ it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL0,
+ M_TX_AUD_24BIT | B_TX_AUD_EN_I2S3 |
+ B_TX_AUD_EN_I2S2 | B_TX_AUD_EN_I2S1 |
+ B_TX_AUD_EN_I2S0);
+ }
+ it6161_hdmi_tx_set_bits(it6161, 0x5c, 1 << 6, 0x00);
+ it6161->bAudioChannelEnable = it6161_hdmi_tx_read(it6161, REG_TX_AUDIO_CTRL0);
+}
+
+static void setHDMITX_DSDAudio(void)
+{
+ it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL1, 0x41);
+ it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_FIFOMAP, 0xE4);
+ it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL0, M_TX_AUD_24BIT);
+ it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL3, 0);
+ it6161_hdmi_tx_write(it6161, REG_TX_AUD_SRCVALID_FLAT, 0x00);
+ it6161_hdmi_tx_write(it6161, REG_TX_AUD_HDAUDIO, B_TX_DSD);
+
+ it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL0,
+ M_TX_AUD_24BIT | B_TX_AUD_EN_I2S3 |
+ B_TX_AUD_EN_I2S2 | B_TX_AUD_EN_I2S1 |
+ B_TX_AUD_EN_I2S0);
+}
+
+static void HDMITX_DisableAudioOutput(struct it6161 *it6161)
+{
+ it6161_hdmi_tx_set_bits(it6161, REG_TX_SW_RST,
+ (B_HDMITX_AUD_RST | B_TX_AREF_RST),
+ (B_HDMITX_AUD_RST | B_TX_AREF_RST));
+ it6161_hdmi_tx_set_bits(it6161, 0x0F, 0x10, 0x10);
+}
+
+static void setHDMITX_NCTS(u8 Fs)
+{
+ u32 n, LastCTS = 0;
+ bool HBR_mode;
+
+ if (B_TX_HBR & it6161_hdmi_tx_read(it6161, REG_TX_AUD_HDAUDIO))
+ HBR_mode = true;
+ else
+ HBR_mode = false;
+
+ switch (Fs) {
+ case AUDFS_32KHz:
+ n = 4096;
+ break;
+ case AUDFS_44p1KHz:
+ n = 6272;
+ break;
+ case AUDFS_48KHz:
+ n = 6144;
+ break;
+ case AUDFS_88p2KHz:
+ n = 12544;
+ break;
+ case AUDFS_96KHz:
+ n = 12288;
+ break;
+ case AUDFS_176p4KHz:
+ n = 25088;
+ break;
+ case AUDFS_192KHz:
+ n = 24576;
+ break;
+ case AUDFS_768KHz:
+ n = 24576;
+ break;
+ default:
+ n = 6144;
+ }
+
+ it6161_hdmi_tx_change_bank(it6161, 1);
+ it6161_hdmi_tx_write(it6161, REGPktAudN0, (u8) ((n) & 0xFF));
+ it6161_hdmi_tx_write(it6161, REGPktAudN1, (u8) ((n >> 8) & 0xFF));
+ it6161_hdmi_tx_write(it6161, REGPktAudN2, (u8) ((n >> 16) & 0xF));
+
+ it6161_hdmi_tx_write(it6161, REGPktAudCTS0, (u8) ((LastCTS) & 0xFF));
+ it6161_hdmi_tx_write(it6161, REGPktAudCTS1, (u8) ((LastCTS >> 8) & 0xFF));
+ it6161_hdmi_tx_write(it6161, REGPktAudCTS2, (u8) ((LastCTS >> 16) & 0xF));
+ it6161_hdmi_tx_change_bank(it6161, 0);
+
+ it6161_hdmi_tx_write(it6161, 0xF8, 0xC3);
+ it6161_hdmi_tx_write(it6161, 0xF8, 0xA5);
+ /* D[1] = 0, HW auto count CTS */
+ it6161_hdmi_tx_set_bits(it6161, REG_TX_PKT_SINGLE_CTRL, B_TX_SW_CTS, 0x00);
+ it6161_hdmi_tx_write(it6161, 0xF8, 0xFF);
+
+ if (false == HBR_mode) {
+ /* LPCM */
+ u8 uData;
+
+ it6161_hdmi_tx_change_bank(it6161, 1);
+ Fs = AUDFS_768KHz;
+ it6161_hdmi_tx_write(it6161, REG_TX_AUDCHST_CA_FS, 0x00 | Fs);
+ /* OFS is the one's complement of FS */
+ Fs = ~Fs;
+ uData = (0x0f & it6161_hdmi_tx_read(it6161, REG_TX_AUDCHST_OFS_WL));
+ it6161_hdmi_tx_write(it6161, REG_TX_AUDCHST_OFS_WL, (Fs << 4) | uData);
+ it6161_hdmi_tx_change_bank(it6161, 0);
+ }
+}
+
+static void HDMITX_EnableAudioOutput(struct it6161 *it6161, u8 AudioType,
+ u8 bAudInterface, u32 SampleFreq, u8 ChNum, u8 *pIEC60958ChStat)
+{
+ struct device *dev = &it6161->i2c_hdmi_tx->dev;
+ static u8 ucIEC60958ChStat[5];
+ u8 Fs;
+
+ it6161->bAudioChannelEnable = 0;
+ it6161_hdmi_tx_set_bits(it6161, REG_TX_SW_RST,
+ (B_HDMITX_AUD_RST | B_TX_AREF_RST),
+ (B_HDMITX_AUD_RST | B_TX_AREF_RST));
+ it6161_hdmi_tx_write(it6161, REG_TX_CLK_CTRL0,
+ B_TX_AUTO_OVER_SAMPLING_CLOCK | B_TX_EXT_256FS | 0x01);
+
+ /* power on the ACLK */
+ it6161_hdmi_tx_set_bits(it6161, 0x0F, 0x10, 0x00);
+
+ if (bAudInterface == SPDIF) {
+ if (AudioType == T_AUDIO_HBR)
+ it6161_hdmi_tx_write(it6161, REG_TX_CLK_CTRL0, 0x81);
+
+ it6161_hdmi_tx_set_bits(it6161, REG_TX_AUDIO_CTRL0,
+ B_TX_AUD_SPDIF, B_TX_AUD_SPDIF);
+ } else
+ it6161_hdmi_tx_set_bits(it6161, REG_TX_AUDIO_CTRL0, B_TX_AUD_SPDIF, 0x00);
+
+ if (AudioType != T_AUDIO_DSD) {
+ /* one bit audio have no channel status. */
+ switch (SampleFreq) {
+ case 44100L:
+ Fs = AUDFS_44p1KHz;
+ break;
+ case 88200L:
+ Fs = AUDFS_88p2KHz;
+ break;
+ case 176400L:
+ Fs = AUDFS_176p4KHz;
+ break;
+ case 32000L:
+ Fs = AUDFS_32KHz;
+ break;
+ case 48000L:
+ Fs = AUDFS_48KHz;
+ break;
+ case 96000L:
+ Fs = AUDFS_96KHz;
+ break;
+ case 192000L:
+ Fs = AUDFS_192KHz;
+ break;
+ case 768000L:
+ Fs = AUDFS_768KHz;
+ break;
+ default:
+ SampleFreq = 48000L;
+ Fs = AUDFS_48KHz;
+ break;
+ }
+
+ setHDMITX_NCTS(Fs);
+ if (pIEC60958ChStat == NULL) {
+ ucIEC60958ChStat[0] = 0;
+ ucIEC60958ChStat[1] = 0;
+ ucIEC60958ChStat[2] = (ChNum + 1) / 2;
+
+ if (ucIEC60958ChStat[2] < 1)
+ ucIEC60958ChStat[2] = 1;
+ else if (ucIEC60958ChStat[2] > 4)
+ ucIEC60958ChStat[2] = 4;
+
+ ucIEC60958ChStat[3] = Fs;
+ ucIEC60958ChStat[4] = (((~Fs) << 4) & 0xF0) | CHTSTS_SWCODE;
+ pIEC60958ChStat = ucIEC60958ChStat;
+ }
+ }
+ it6161_hdmi_tx_set_bits(it6161, REG_TX_SW_RST,
+ (B_HDMITX_AUD_RST | B_TX_AREF_RST), B_TX_AREF_RST);
+
+ switch (AudioType) {
+ case T_AUDIO_HBR:
+ DRM_DEV_DEBUG_DRIVER(dev, "T_AUDIO_HBR\n");
+ pIEC60958ChStat[0] |= 1 << 1;
+ pIEC60958ChStat[2] = 0;
+ pIEC60958ChStat[3] &= 0xF0;
+ pIEC60958ChStat[3] |= AUDFS_768KHz;
+ pIEC60958ChStat[4] |= (((~AUDFS_768KHz) << 4) & 0xF0) | 0xB;
+ setHDMITX_ChStat(it6161, pIEC60958ChStat);
+ setHDMITX_HBRAudio(bAudInterface);
+ break;
+ case T_AUDIO_DSD:
+ DRM_DEV_DEBUG_DRIVER(dev, "T_AUDIO_DSD\n");
+ setHDMITX_DSDAudio();
+ break;
+ case T_AUDIO_NLPCM:
+ DRM_DEV_DEBUG_DRIVER(dev, "T_AUDIO_NLPCM\n");
+ pIEC60958ChStat[0] |= 1 << 1;
+ setHDMITX_ChStat(it6161, pIEC60958ChStat);
+ setHDMITX_NLPCMAudio(bAudInterface);
+ break;
+ case T_AUDIO_LPCM:
+ DRM_DEV_DEBUG_DRIVER(dev, "T_AUDIO_LPCM\n");
+ pIEC60958ChStat[0] &= ~(1 << 1);
+
+ setHDMITX_ChStat(it6161, pIEC60958ChStat);
+ setHDMITX_LPCMAudio((ChNum + 1) / 2, SUPPORT_AUDI_AudSWL, bAudInterface);
+ break;
+ }
+ it6161_hdmi_tx_set_bits(it6161, REG_TX_INT_MASK1, B_TX_AUDIO_OVFLW_MASK, 0x00);
+ it6161_hdmi_tx_write(it6161, REG_TX_AUDIO_CTRL0, it6161->bAudioChannelEnable);
+
+ it6161_hdmi_tx_set_bits(it6161, REG_TX_SW_RST, (B_HDMITX_AUD_RST | B_TX_AREF_RST), 0);
+}
+
+static void hdmi_audio_info_frame_set(struct it6161 *it6161, u8 channels)
+{
+ struct hdmi_audio_infoframe frame;
+ u8 buf[16];
+ int ret;
+
+ hdmi_audio_infoframe_init(&frame);
+
+ frame.channels = channels;
+ frame.coding_type = HDMI_AUDIO_CODING_TYPE_STREAM;
+
+ ret = hdmi_audio_infoframe_pack(&frame, buf, sizeof(buf));
+ if (ret < 0) {
+ DRM_ERROR("failed to pack audio infoframe: %d\n", ret);
+ return;
+ }
+
+ /* set audio Data byte */
+ it6161_hdmi_tx_change_bank(it6161, 1);
+ it6161_hdmi_tx_write(it6161, REG_TX_PKT_AUDINFO_SUM, buf[3]);
+ it6161_hdmi_tx_write(it6161, REG_TX_PKT_AUDINFO_CC, buf[4]);
+ it6161_hdmi_tx_write(it6161, REG_TX_PKT_AUDINFO_SF, buf[5]);
+ it6161_hdmi_tx_write(it6161, REG_TX_PKT_AUDINFO_CA, buf[7]);
+ it6161_hdmi_tx_write(it6161, REG_TX_PKT_AUDINFO_DM_LSV, buf[8]);
+
+ /* Enable Audio info frame */
+ it6161_hdmi_tx_change_bank(it6161, 0);
+ it6161_hdmi_tx_write(it6161, REG_TX_AUD_INFOFRM_CTRL, B_TX_ENABLE_PKT | B_TX_REPEAT_PKT);
+}
+
+static void hdmi_tx_audio_process(struct it6161 *it6161)
+{
+ if (it6161->support_audio) {
+ hdmi_audio_info_frame_set(it6161, (u8) OUTPUT_CHANNEL);
+
+ HDMITX_EnableAudioOutput(it6161,
+ CNOFIG_INPUT_AUDIO_TYPE,
+ CONFIG_INPUT_AUDIO_INTERFACE,
+ INPUT_SAMPLE_FREQ,
+ OUTPUT_CHANNEL,
+ NULL);
+ }
+}
+
+static inline void hdmi_tx_disable_avi_infoframe(struct it6161 *it6161)
+{
+ it6161_hdmi_tx_change_bank(it6161, 0);
+ it6161_hdmi_tx_write(it6161, REG_TX_AVI_INFOFRM_CTRL, 0x00);
+}
+
+static inline void hdmi_tx_enable_avi_infoframe(struct it6161 *it6161)
+{
+ it6161_hdmi_tx_change_bank(it6161, 0);
+ it6161_hdmi_tx_write(it6161, REG_TX_AVI_INFOFRM_CTRL,
+ B_TX_ENABLE_PKT | B_TX_REPEAT_PKT);
+}
+
+static int hdmi_tx_avi_infoframe_set(struct it6161 *it6161)
+{
+ struct device *dev = &it6161->i2c_hdmi_tx->dev;
+ struct hdmi_avi_infoframe *frame = &it6161->source_avi_infoframe;
+ struct drm_display_mode *display_mode = &it6161->display_mode;
+ u8 buf[32], i, *ptr;
+ int ret;
+
+ DRM_DEV_DEBUG_DRIVER(dev, "avinfo set\n");
+ hdmi_tx_disable_avi_infoframe(it6161);
+
+ ret = drm_hdmi_avi_infoframe_from_display_mode(
+ frame, &it6161->connector, display_mode);
+ if (ret) {
+ DRM_DEV_ERROR(dev, "Failed to setup AVI infoframe: %d", ret);
+ return ret;
+ }
+
+ if ((it6161->hdmi_tx_output_color_space & F_MODE_CLRMOD_MASK) == F_MODE_RGB444)
+ frame->colorspace = HDMI_COLORSPACE_RGB;
+
+ if ((it6161->hdmi_tx_output_color_space & F_MODE_CLRMOD_MASK) == F_MODE_YUV444)
+ frame->colorspace = HDMI_COLORSPACE_YUV444;
+
+ if ((it6161->hdmi_tx_output_color_space & F_MODE_CLRMOD_MASK) == F_MODE_YUV422)
+ frame->colorspace = HDMI_COLORSPACE_YUV422;
+
+ ret = hdmi_avi_infoframe_pack(&it6161->source_avi_infoframe, buf, sizeof(buf));
+ if (ret < 0) {
+ DRM_DEV_ERROR(dev, "Failed to pack AVI infoframe: %d", ret);
+ return ret;
+ }
+
+ /* fill PB */
+ it6161_hdmi_tx_change_bank(it6161, 1);
+ ptr = buf + HDMI_INFOFRAME_HEADER_SIZE;
+ for (i = 0; i < it6161->source_avi_infoframe.length; i++)
+ it6161_hdmi_tx_write(it6161, REG_TX_AVIINFO_DB1 + i, ptr[i]);
+
+ it6161_hdmi_tx_write(it6161, REG_TX_AVIINFO_SUM, buf[3]);
+
+ /* Enable */
+ hdmi_tx_enable_avi_infoframe(it6161);
+ return 0;
+}
+
+static void hdmi_tx_set_output_process(struct it6161 *it6161)
+{
+ struct device *dev = &it6161->i2c_hdmi_tx->dev;
+ u8 level;
+ u32 TMDSClock;
+
+ DRM_DEV_DEBUG_DRIVER(dev, "hdmi tx set\n");
+
+ TMDSClock = it6161->hdmi_tx_pclk * 1000 *
+ (it6161->source_avi_infoframe.pixel_repeat + 1);
+
+ HDMITX_DisableAudioOutput(it6161);
+ hdmi_tx_disable_avi_infoframe(it6161);
+
+ if (TMDSClock > 80000000L)
+ level = PCLK_HIGH;
+ else if (TMDSClock > 20000000L)
+ level = PCLK_MEDIUM;
+ else
+ level = PCLK_LOW;
+
+ hdmi_tx_enable_video_output(it6161, level);
+
+ if (it6161->hdmi_mode) {
+ hdmi_tx_avi_infoframe_set(it6161);
+ hdmi_tx_audio_process(it6161);
+ }
+
+ it6161_hdmi_tx_set_av_mute(it6161, false);
+}
+
+static void mipi_rx_calc_rclk(struct it6161 *it6161)
+{
+ struct device *dev = &it6161->i2c_mipi_rx->dev;
+ u32 sum = 0, i, retry = 5;
+ int t10usint;
+
+ for (i = 0; i < retry; i++) {
+ /* Enable RCLK 100ms count */
+ it6161_mipi_rx_set_bits(it6161, 0x94, 0x80, 0x80);
+ msleep(100);
+ /* Disable RCLK 100ms count */
+ it6161_mipi_rx_set_bits(it6161, 0x94, 0x80, 0x00);
+
+ it6161->mipi_rx_rclk = it6161_mipi_rx_read(it6161, 0x97);
+ it6161->mipi_rx_rclk <<= 8;
+ it6161->mipi_rx_rclk += it6161_mipi_rx_read(it6161, 0x96);
+ it6161->mipi_rx_rclk <<= 8;
+ it6161->mipi_rx_rclk += it6161_mipi_rx_read(it6161, 0x95);
+ sum += it6161->mipi_rx_rclk;
+ }
+
+ sum /= retry;
+ it6161->mipi_rx_rclk = sum / 108;
+ t10usint = it6161->mipi_rx_rclk;
+ DRM_DEV_DEBUG_DRIVER(dev, "mipi_rx_rclk = %d,%03d,%03d\n",
+ (sum * 10) / 1000000, ((sum * 10) % 1000000) / 1000, ((sum * 10) % 100));
+ it6161_mipi_rx_write(it6161, 0x91, t10usint & 0xFF);
+}
+
+static void mipi_rx_reset_p_domain(struct it6161 *it6161)
+{
+ /* Video Clock Domain Reset */
+ it6161_mipi_rx_set_bits(it6161, 0x05, 0x04, 0x04);
+ /* Release Video Clock Domain Reset */
+ it6161_mipi_rx_set_bits(it6161, 0x05, 0x04, 0x00);
+}
+
+static void it6161_mipi_rx_interrupt_clear(struct it6161 *it6161, u8 reg06, u8 reg07, u8 reg08)
+{
+ it6161_mipi_rx_write(it6161, 0x06, reg06);
+ it6161_mipi_rx_write(it6161, 0x07, reg07);
+ it6161_mipi_rx_write(it6161, 0x08, reg08);
+}
+
+static void it6161_mipi_rx_interrupt_reg06_process(struct it6161 *it6161, u8 reg06)
+{
+ struct device *dev = &it6161->i2c_mipi_rx->dev;
+ bool m_video_stable, p_video_stable;
+ u8 data_id;
+
+ if (reg06 & 0x01) {
+ m_video_stable = mipi_rx_get_m_video_stable(it6161);
+ DRM_DEV_DEBUG_DRIVER(dev, "PPS M video stable Change Interrupt, %sstable",
+ m_video_stable ? "" : "un");
+
+ if (m_video_stable) {
+ data_id = it6161_mipi_rx_read(it6161, 0x28);
+ DRM_DEV_DEBUG_DRIVER(dev, "mipi receive video format: 0x%02x", data_id);
+ mipi_rx_calc_rclk(it6161);
+ mipi_rx_afe_configuration(it6161, data_id);
+ mipi_rx_reset_p_domain(it6161);
+ }
+ } else if (reg06 & 0x10) {
+
+ p_video_stable = mipi_rx_get_p_video_stable(it6161);
+ DRM_DEV_DEBUG_DRIVER(dev, "PPS P video stable Change Interrupt, %sstable", p_video_stable ? "" : "un");
+ if (p_video_stable) {
+ DRM_DEV_DEBUG_DRIVER(dev, "PVidStb Change to HIGH");
+ mipi_rx_calc_rclk(it6161);
+
+ it6161_mipi_rx_write(it6161, 0xC0, (EnTxCRC << 7) + TxCRCnum);
+ /* setup 1 sec timer interrupt */
+ it6161_mipi_rx_set_bits(it6161, 0x0b, 0x40, 0x40);
+
+ switch (it6161->hdmi_tx_mode) {
+ case HDMI_TX_BY_PASS:
+ it6161_hdmi_tx_set_bits(it6161, 0xA9, 0x80, 0x80);
+ break;
+
+ case HDMI_TX_ENABLE_DE_ONLY:
+ hdmi_tx_generate_blank_timing(it6161);
+ break;
+
+ default:
+ DRM_DEV_ERROR(dev, "use hdmi tx normal mode");
+ break;
+ }
+
+ hdmi_tx_video_reset(it6161);
+ }
+ } else
+ DRM_DEV_DEBUG_DRIVER(dev, "MIPI Rx int reg06=0x%x\n", reg06);
+}
+
+static void it6161_mipi_rx_interrupt_reg07_process(struct it6161 *it6161, u8 reg07)
+{
+ struct device *dev = &it6161->i2c_mipi_rx->dev;
+
+ if (reg07 & 0x40) {
+ DRM_DEV_DEBUG_DRIVER(dev, "PPS FIFO over read Interrupt !!! tx video statle:%d",
+ hdmi_tx_get_video_state(it6161));
+ it6161_mipi_rx_set_bits(it6161, 0x07, 0x40, 0x40);
+ } else if (reg07 & 0x80) {
+ DRM_DEV_DEBUG_DRIVER(dev, "PPS FIFO over write Interrupt !!!\n");
+ it6161_mipi_rx_set_bits(it6161, 0x07, 0x80, 0x80);
+ } else
+ DRM_DEV_DEBUG_DRIVER(dev, "MIPI Rx int reg07=0x%x\n", reg07);
+}
+
+static void it6161_mipi_rx_interrupt_reg08_process(struct it6161 *it6161, u8 reg08)
+{
+ struct device *dev = &it6161->i2c_mipi_rx->dev;
+ int crc;
+
+ if (reg08 & 0x40) {
+ it6161_mipi_rx_set_bits(it6161, 0x0b, 0x40, 0x00);
+
+ if ((it6161_mipi_rx_read(it6161, 0xC1) & 0x03) == 0x03)
+ DRM_DEV_DEBUG_DRIVER(dev, "CRC Fail !!!\n");
+
+ if ((it6161_mipi_rx_read(it6161, 0xC1) & 0x05) == 0x05) {
+ DRM_DEV_DEBUG_DRIVER(dev, "CRC Pass !!!\n");
+ crc = it6161_mipi_rx_read(it6161, 0xC2) +
+ (it6161_mipi_rx_read(it6161, 0xC3) << 8);
+ DRM_DEV_DEBUG_DRIVER(dev, "CRCR = 0x%x !!!\n", crc);
+ crc = it6161_mipi_rx_read(it6161, 0xC4) +
+ (it6161_mipi_rx_read(it6161, 0xC5) << 8);
+ DRM_DEV_DEBUG_DRIVER(dev, "CRCG = 0x%x !!!\n", crc);
+ crc = it6161_mipi_rx_read(it6161, 0xC6) +
+ (it6161_mipi_rx_read(it6161, 0xC7) << 8);
+ DRM_DEV_DEBUG_DRIVER(dev, "CRCB = 0x%x !!!\n", crc);
+ }
+ } else
+ DRM_DEV_DEBUG_DRIVER(dev, "MIPI Rx int reg08=0x%x\n", reg08);
+}
+
+static void it6161_hdmi_tx_interrupt_clear(struct it6161 *it6161, u8 reg06, u8 reg08)
+{
+ struct device *dev = &it6161->i2c_hdmi_tx->dev;
+ u8 int_clear;
+
+ if (reg06 & B_TX_INT_AUD_OVERFLOW) {
+ DRM_DEV_ERROR(dev, "B_TX_INT_AUD_OVERFLOW");
+ it6161_hdmi_tx_set_bits(it6161, REG_TX_SW_RST,
+ (B_HDMITX_AUD_RST | B_TX_AREF_RST),
+ (B_HDMITX_AUD_RST | B_TX_AREF_RST));
+ it6161_hdmi_tx_set_bits(it6161, REG_TX_SW_RST, B_HDMITX_AUD_RST | B_TX_AREF_RST, 0x00);
+ } else if (reg06 & B_TX_INT_DDCFIFO_ERR) {
+ DRM_DEV_ERROR(dev, "DDC FIFO Error");
+ it6161_hdmi_tx_clear_ddc_fifo(it6161);
+ } else if (reg06 & B_TX_INT_DDC_BUS_HANG) {
+ DRM_DEV_ERROR(dev, "DDC BUS HANG");
+ it6161_hdmi_tx_abort_ddc(it6161);
+ }
+
+ /* clear interrupt */
+ it6161_hdmi_tx_write(it6161, REG_TX_INT_CLR0, 0xFF);
+ it6161_hdmi_tx_write(it6161, REG_TX_INT_CLR1, 0xFF);
+ /* write B_TX_INTACTDONE '1' to trigger clear interrupt */
+ int_clear = (it6161_hdmi_tx_read(it6161, REG_TX_SYS_STATUS)) | B_TX_CLR_AUD_CTS | B_TX_INTACTDONE;
+ it6161_hdmi_tx_write(it6161, REG_TX_SYS_STATUS, int_clear);
+}
+
+static void it6161_hdmi_tx_interrupt_reg06_process(struct it6161 *it6161, u8 reg06)
+{
+ struct device *dev = &it6161->i2c_hdmi_tx->dev;
+
+ if (reg06 & B_TX_INT_HPD_PLUG) {
+ /*
+ * sometimes the interrupt is triggered before init bridge.dev.
+ * So avoid null pointer
+ */
+ if (it6161->bridge.dev)
+ drm_helper_hpd_irq_event(it6161->bridge.dev);
+ if (hdmi_tx_get_sink_hpd(it6161)) {
+ DRM_DEV_INFO(dev, "HDMI Cable Plug In\n");
+ hdmi_tx_video_reset(it6161);
+ } else {
+ DRM_DEV_INFO(dev, "HDMI Cable Plug Out");
+ hdmi_tx_disable_video_output(it6161);
+ }
+ }
+}
+
+static void it6161_hdmi_tx_interrupt_reg08_process(struct it6161 *it6161, u8 reg08)
+{
+ if (reg08 & B_TX_INT_VIDSTABLE) {
+ it6161_hdmi_tx_write(it6161, REG_TX_INT_STAT3, reg08);
+ if (hdmi_tx_get_video_state(it6161)) {
+ hdmi_tx_set_output_process(it6161);
+ it6161_hdmi_tx_set_av_mute(it6161, FALSE);
+ }
+ }
+}
+
+static irqreturn_t it6161_intp_threaded_handler(int unused, void *data)
+{
+ struct it6161 *it6161 = data;
+ struct device *dev = &it6161->i2c_hdmi_tx->dev;
+ u8 mipi_rx_reg06, mipi_rx_reg07, mipi_rx_reg08, mipi_rx_reg0d;
+ u8 hdmi_tx_reg06, hdmi_tx_reg08;
+
+ mipi_rx_reg06 = it6161_mipi_rx_read(it6161, 0x06);
+ mipi_rx_reg07 = it6161_mipi_rx_read(it6161, 0x07);
+ mipi_rx_reg08 = it6161_mipi_rx_read(it6161, 0x08);
+ mipi_rx_reg0d = it6161_mipi_rx_read(it6161, 0x0D);
+
+ hdmi_tx_reg06 = it6161_hdmi_tx_read(it6161, 0x06);
+ hdmi_tx_reg08 = it6161_hdmi_tx_read(it6161, 0x08);
+
+ if ((mipi_rx_reg06 != 0) || (mipi_rx_reg07 != 0) || (mipi_rx_reg08 != 0)) {
+ DRM_DEV_DEBUG_DRIVER(dev, "[MIPI rx ++] reg06: 0x%02x reg07: 0x%02x reg08: 0x%02x reg0d: 0x%02x",
+ mipi_rx_reg06, mipi_rx_reg07, mipi_rx_reg08, mipi_rx_reg0d);
+ it6161_mipi_rx_interrupt_clear(it6161, mipi_rx_reg06, mipi_rx_reg07, mipi_rx_reg08);
+ }
+
+ if ((hdmi_tx_reg06 != 0) || (hdmi_tx_reg08 != 0)) {
+ DRM_DEV_DEBUG_DRIVER(dev, "[HDMI tx ++] reg06: 0x%02x reg08: 0x%02x",
+ hdmi_tx_reg06, hdmi_tx_reg08);
+ it6161_hdmi_tx_interrupt_clear(it6161, hdmi_tx_reg06, hdmi_tx_reg08);
+ }
+
+ it6161_mipi_rx_interrupt_reg08_process(it6161, mipi_rx_reg08);
+ it6161_mipi_rx_interrupt_reg06_process(it6161, mipi_rx_reg06);
+ it6161_mipi_rx_interrupt_reg07_process(it6161, mipi_rx_reg07);
+ it6161_hdmi_tx_interrupt_reg06_process(it6161, hdmi_tx_reg06);
+ it6161_hdmi_tx_interrupt_reg08_process(it6161, hdmi_tx_reg08);
+
+ return IRQ_HANDLED;
+}
+
+static ssize_t hdmi_output_color_space_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct it6161 *it6161 = dev_get_drvdata(dev);
+
+ DRM_DEV_DEBUG_DRIVER(dev, "config color space: %s", buf);
+ it6161->hdmi_tx_output_color_space &= ~F_MODE_CLRMOD_MASK;
+
+ if (strncmp(buf, "ycbcr444", strlen(buf) - 1) == 0
+ || strncmp(buf, "yuv444", strlen(buf) - 1) == 0) {
+ it6161->hdmi_tx_output_color_space |= F_MODE_YUV444;
+ goto end;
+ }
+
+ if (strncmp(buf, "ycbcr422", strlen(buf) - 1) == 0
+ || strncmp(buf, "yuv422", strlen(buf) - 1) == 0) {
+ it6161->hdmi_tx_output_color_space |= F_MODE_YUV422;
+ goto end;
+ }
+
+ if (strncmp(buf, "rgb444", strlen(buf) - 1) == 0) {
+ it6161->hdmi_tx_output_color_space |= F_MODE_RGB444;
+ goto end;
+ }
+
+ DRM_DEV_DEBUG_DRIVER(dev,
+ "not support this color space, only support ycbcr444/yuv444, ycbcr422/yuv422, rgb444");
+ return count;
+
+end:
+ DRM_DEV_INFO(dev, "nconfig color space: %s value:0x%02x", buf,
+ it6161->hdmi_tx_output_color_space);
+ return count;
+}
+
+static ssize_t hdmi_output_color_space_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct it6161 *it6161 = dev_get_drvdata(dev);
+ char *str = buf, *end = buf + PAGE_SIZE;
+
+ str += scnprintf(str, end - str,
+ "it6161->hdmi_tx_output_color_space:%d\n", it6161->hdmi_tx_output_color_space);
+
+ return str - buf;
+}
+
+static DEVICE_ATTR_RW(hdmi_output_color_space);
+
+static const struct attribute *it6161_attrs[] = {
+ &dev_attr_hdmi_output_color_space.attr,
+ NULL,
+};
+
+static int it6161_parse_dt(struct it6161 *it6161, struct device_node *np)
+{
+ struct device *dev = &it6161->i2c_mipi_rx->dev;
+
+ it6161->host_node = of_graph_get_remote_node(np, 0, 0);
+ if (!it6161->host_node) {
+ DRM_DEV_ERROR(dev, "no host node");
+ return -ENODEV;
+ }
+ of_node_put(it6161->host_node);
+
+ return 0;
+}
+
+static int it6161_i2c_probe(struct i2c_client *i2c_mipi_rx,
+ const struct i2c_device_id *id)
+{
+ struct device *dev = &i2c_mipi_rx->dev;
+ int err, intp_irq;
+
+ it6161 = devm_kzalloc(dev, sizeof(*it6161), GFP_KERNEL);
+ if (!it6161)
+ return -ENOMEM;
+
+ it6161->i2c_mipi_rx = i2c_mipi_rx;
+ mutex_init(&it6161->mode_lock);
+
+ it6161->bridge.of_node = i2c_mipi_rx->dev.of_node;
+
+ it6161_parse_dt(it6161, dev->of_node);
+ it6161->regmap_mipi_rx = devm_regmap_init_i2c(i2c_mipi_rx, &it6161_mipi_rx_bridge_regmap_config);
+ if (IS_ERR(it6161->regmap_mipi_rx)) {
+ DRM_DEV_ERROR(dev, "regmap_mipi_rx i2c init failed");
+ return PTR_ERR(it6161->regmap_mipi_rx);
+ }
+
+ if (device_property_read_u32(dev, "it6161-addr-hdmi-tx", &it6161->it6161_addr_hdmi_tx) < 0)
+ it6161->it6161_addr_hdmi_tx = 0x4C;
+ it6161->i2c_hdmi_tx = i2c_new_dummy_device(i2c_mipi_rx->adapter, it6161->it6161_addr_hdmi_tx);
+ it6161->regmap_hdmi_tx = devm_regmap_init_i2c(it6161->i2c_hdmi_tx, &it6161_hdmi_tx_bridge_regmap_config);
+ if (IS_ERR(it6161->regmap_hdmi_tx)) {
+ DRM_DEV_ERROR(dev, "regmap_hdmi_tx i2c init failed");
+ return PTR_ERR(it6161->regmap_hdmi_tx);
+ }
+
+ if (!it6161_check_device_ready(it6161))
+ return -ENODEV;
+
+ /* The enable GPIO is optional. */
+ it6161->enable_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_LOW);
+ if (IS_ERR(it6161->enable_gpio))
+ DRM_DEV_INFO(dev, "No enable GPIO");
+ else
+ gpiod_set_value_cansleep(it6161->enable_gpio, 1);
+
+ it6161->enable_drv_hold = DEFAULT_DRV_HOLD;
+ it6161_set_interrupts_active_level(HIGH);
+
+ intp_irq = i2c_mipi_rx->irq;
+
+ if (!intp_irq) {
+ DRM_DEV_ERROR(dev, "it6112 failed to get INTP IRQ");
+ return -ENODEV;
+ }
+
+ err = devm_request_threaded_irq(&i2c_mipi_rx->dev, intp_irq, NULL,
+ it6161_intp_threaded_handler,
+ IRQF_TRIGGER_HIGH | IRQF_ONESHOT, "it6161-intp", it6161);
+ if (err) {
+ DRM_DEV_ERROR(dev, "it6112 failed to request INTP threaded IRQ: %d", err);
+ return err;
+ }
+
+ i2c_set_clientdata(i2c_mipi_rx, it6161);
+ it6161->bridge.funcs = &it6161_bridge_funcs;
+ it6161->bridge.of_node = i2c_mipi_rx->dev.of_node;
+ it6161->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID |
+ DRM_BRIDGE_OP_HPD | DRM_BRIDGE_OP_MODES;
+ it6161->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
+
+ drm_bridge_add(&it6161->bridge);
+
+ err = sysfs_create_files(&i2c_mipi_rx->dev.kobj, it6161_attrs);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+static int it6161_remove(struct i2c_client *i2c_mipi_rx)
+{
+ struct it6161 *it6161 = i2c_get_clientdata(i2c_mipi_rx);
+
+ drm_connector_unregister(&it6161->connector);
+ drm_connector_cleanup(&it6161->connector);
+ drm_bridge_remove(&it6161->bridge);
+ return 0;
+}
+
+static const struct i2c_device_id it6161_id[] = {
+ {"it6161", 0},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, it6161_id);
+
+static const struct of_device_id it6161_of_match[] = {
+ {.compatible = "ite,it6161"},
+ {}
+};
+
+static struct i2c_driver it6161_i2c_driver = {
+ .driver = {
+ .name = "it6161_mipirx_hdmitx",
+ .of_match_table = it6161_of_match,
+ },
+ .id_table = it6161_id,
+ .probe = it6161_i2c_probe,
+ .remove = it6161_remove,
+};
+
+module_i2c_driver(it6161_i2c_driver);
+
+MODULE_AUTHOR("allen chen <allen.chen@ite.com.tw>");
+MODULE_DESCRIPTION("it6161 HDMI Transmitter driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/bridge/it6161.h b/drivers/gpu/drm/bridge/it6161.h
new file mode 100644
index 000000000000..93900406bcb3
--- /dev/null
+++ b/drivers/gpu/drm/bridge/it6161.h
@@ -0,0 +1,322 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * IT6161 MIPI to HDMI Converter driver
+ *
+ * Copyright (C) 2021 NXP
+ */
+#ifndef __IT6161_H__
+#define __IT6161_H__
+
+/* Video Configuration */
+#define F_MODE_RGB444 0
+#define F_MODE_YUV422 1
+#define F_MODE_YUV444 2
+#define F_MODE_CLRMOD_MASK 3
+
+#define F_VIDMODE_ITU709 (1<<4)
+#define F_VIDMODE_ITU601 0
+#define F_VIDMODE_16_235 (1<<5)
+#define F_VIDMODE_0_255 0
+
+#define F_VIDMODE_EN_UDFILT (1<<6)
+#define F_VIDMODE_EN_DITHER (1<<7)
+
+#define INPUT_COLOR_MODE F_MODE_RGB444
+#define OUTPUT_COLOR_MODE F_MODE_RGB444
+
+/* Audio Configuration */
+
+/* Audio interface */
+#define I2S 0
+#define SPDIF 1
+#define TDM 2
+
+/* Audio sample clock */
+#define AUDFS_44p1KHz 0
+#define AUDFS_88p2KHz 8
+#define AUDFS_176p4KHz 12
+#define AUDFS_32KHz 3
+#define AUDFS_48KHz 2
+#define AUDFS_96KHz 10
+#define AUDFS_192KHz 14
+#define AUDFS_768KHz 9
+
+#define F_AUDIO_ON (1<<7)
+#define F_AUDIO_HBR (1<<6)
+#define F_AUDIO_DSD (1<<5)
+#define F_AUDIO_NLPCM (1<<4)
+#define F_AUDIO_LAYOUT_1 (1<<3)
+#define F_AUDIO_LAYOUT_0 (0<<3)
+
+#define T_AUDIO_HBR (F_AUDIO_ON | F_AUDIO_HBR)
+#define T_AUDIO_DSD (F_AUDIO_ON | F_AUDIO_DSD)
+#define T_AUDIO_NLPCM (F_AUDIO_ON | F_AUDIO_NLPCM)
+#define T_AUDIO_LPCM (F_AUDIO_ON)
+
+/* #define SUPPORT_HBR_AUDIO */
+#ifndef SUPPORT_HBR_AUDIO
+ #define INPUT_SAMPLE_FREQ AUDFS_48KHz
+ #define INPUT_SAMPLE_FREQ_HZ 48000L
+ #define OUTPUT_CHANNEL 2 /* 3,4,5,6,7,8 */
+ #define CNOFIG_INPUT_AUDIO_TYPE T_AUDIO_LPCM
+ #define CONFIG_INPUT_AUDIO_INTERFACE SPDIF
+ #define I2S_FORMAT 0x01 /* 32bit I2S audio */
+#else /* SUPPORT_HBR_AUDIO */
+ #define INPUT_SAMPLE_FREQ AUDFS_768KHz
+ #define INPUT_SAMPLE_FREQ_HZ 768000L
+ #define OUTPUT_CHANNEL 8
+ #define CNOFIG_INPUT_AUDIO_TYPE T_AUDIO_HBR
+ #define CONFIG_INPUT_AUDIO_INTERFACE SPDIF
+ #define I2S_FORMAT 0x47 /* 32bit audio */
+#endif
+
+/* MIPI Rx Configuration */
+#define MIPI_RX_LANE_COUNT 2 /* 1~4 */
+
+#define SUPPORT_AUDI_AudSWL 24
+
+#if (SUPPORT_AUDI_AudSWL == 16)
+ #define CHTSTS_SWCODE 0x02
+#elif (SUPPORT_AUDI_AudSWL == 18)
+ #define CHTSTS_SWCODE 0x04
+#elif (SUPPORT_AUDI_AudSWL == 20)
+ #define CHTSTS_SWCODE 0x03
+#else
+ #define CHTSTS_SWCODE 0x0B
+#endif
+
+#define DDC_FIFO_MAXREQ 0x20
+
+/* I2C address */
+#define DDC_EDID_ADDRESS 0xA0
+#define CEC_I2C_SLAVE_ADDR 0x9C
+
+/* HDMI TX Register offset */
+#define REG_TX_SW_RST 0x04
+#define B_TX_AREF_RST (1<<4)
+#define B_HDMITX_VID_RST (1<<3)
+#define B_HDMITX_AUD_RST (1<<2)
+#define B_TX_HDMI_RST (1<<1)
+#define B_TX_HDCP_RST_HDMITX (1<<0)
+
+#define REG_TX_INT_STAT1 0x06
+#define B_TX_INT_AUD_OVERFLOW (1<<7)
+#define B_TX_INT_DDCFIFO_ERR (1<<4)
+#define B_TX_INT_DDC_BUS_HANG (1<<2)
+#define B_TX_INT_HPD_PLUG (1<<0)
+
+#define REG_TX_INT_STAT3 0x08
+#define B_TX_INT_VIDSTABLE (1<<4)
+
+#define REG_TX_INT_MASK1 0x09
+#define B_TX_AUDIO_OVFLW_MASK (1<<7)
+#define B_TX_DDC_FIFO_ERR_MASK (1<<4)
+#define B_TX_DDC_BUS_HANG_MASK (1<<2)
+#define B_TX_RXSEN_MASK (1<<1)
+#define B_TX_HPD_MASK (1<<0)
+
+#define REG_TX_INT_MASK3 0x0B
+#define B_TX_VIDSTABLE_MASK (1<<3)
+
+#define REG_TX_INT_CLR0 0x0C
+#define REG_TX_INT_CLR1 0x0D
+#define REG_TX_SYS_STATUS 0x0E
+#define B_TX_HPDETECT (1<<6)
+#define B_TX_RXSENDETECT (1<<5)
+#define B_TXVIDSTABLE (1<<4)
+#define B_TX_CLR_AUD_CTS (1<<1)
+#define B_TX_INTACTDONE (1<<0)
+
+/* DDC */
+#define REG_TX_DDC_MASTER_CTRL 0x10
+#define B_TX_MASTERROM (1<<1)
+#define B_TX_MASTERDDC (0<<1)
+#define B_TX_MASTERHOST (1<<0)
+
+#define REG_TX_DDC_HEADER 0x11
+#define REG_TX_DDC_REQOFF 0x12
+#define REG_TX_DDC_REQCOUNT 0x13
+#define REG_TX_DDC_EDIDSEG 0x14
+#define REG_TX_DDC_CMD 0x15
+#define CMD_EDID_READ 3
+#define CMD_FIFO_CLR 9
+#define CMD_GEN_SCLCLK 0xA
+#define CMD_DDC_ABORT 0xF
+
+#define REG_TX_DDC_STATUS 0x16
+#define B_TX_DDC_DONE (1<<7)
+#define B_TX_DDC_NOACK (1<<5)
+#define B_TX_DDC_WAITBUS (1<<4)
+#define B_TX_DDC_ARBILOSE (1<<3)
+
+#define REG_TX_DDC_READFIFO 0x17
+#define REG_TX_CLK_CTRL0 0x58
+#define B_TX_AUTO_OVER_SAMPLING_CLOCK (1<<4)
+#define O_TX_EXT_MCLK_SEL 2
+#define M_TX_EXT_MCLK_SEL (3<<O_TX_EXT_MCLK_SEL)
+#define B_TX_EXT_128FS (0<<O_TX_EXT_MCLK_SEL)
+#define B_TX_EXT_256FS (1<<O_TX_EXT_MCLK_SEL)
+#define B_TX_EXT_512FS (2<<O_TX_EXT_MCLK_SEL)
+#define B_TX_EXT_1024FS (3<<O_TX_EXT_MCLK_SEL)
+
+#define REG_TX_CLK_STATUS2 0x5F
+#define B_TX_OSF_LOCK (1<<5)
+
+#define REG_TX_AFE_DRV_CTRL 0x61
+#define B_TX_AFE_DRV_PWD (1<<5)
+#define B_TX_AFE_DRV_RST (1<<4)
+
+/* Input Data Format Register */
+#define REG_TX_INPUT_MODE 0x70
+#define B_TX_PCLKDIV2 (1<<5)
+
+#define REG_TX_CSC_CTRL 0x72
+#define B_HDMITX_CSC_BYPASS 0
+#define B_HDMITX_CSC_RGB2YUV 2
+#define B_HDMITX_CSC_YUV2RGB 3
+#define M_TX_CSC_SEL 3
+#define B_TX_EN_DITHER (1<<7)
+#define B_TX_EN_UDFILTER (1<<6)
+#define B_TX_DNFREE_GO (1<<5)
+#define SIZEOF_CSCMTX 21
+
+#define REG_TX_CSC_YOFF 0x73
+#define REG_TX_CSC_COFF 0x74
+#define REG_TX_CSC_RGBOFF 0x75
+
+/* HDMI General Control Registers */
+#define REG_TX_HDMI_MODE 0xC0
+#define B_TX_HDMI_MODE 1
+#define B_TX_DVI_MODE 0
+#define REG_TX_GCP 0xC1
+#define B_TX_SETAVMUTE (1<<0)
+
+#define O_TX_COLOR_DEPTH 4
+#define M_TX_COLOR_DEPTH 7
+#define B_TX_COLOR_DEPTH_MASK (M_TX_COLOR_DEPTH << O_TX_COLOR_DEPTH)
+#define REG_TX_PKT_GENERAL_CTRL 0xC6
+
+/* Audio Channel Control */
+#define REG_TX_AUDIO_CTRL0 0xE0
+#define M_TX_AUD_16BIT (0<<6)
+#define M_TX_AUD_18BIT (1<<6)
+#define M_TX_AUD_20BIT (2<<6)
+#define M_TX_AUD_24BIT (3<<6)
+#define B_TX_AUD_SPDIF (1<<4)
+#define B_TX_AUD_I2S (0<<4)
+#define B_TX_AUD_EN_I2S3 (1<<3)
+#define B_TX_AUD_EN_I2S2 (1<<2)
+#define B_TX_AUD_EN_I2S1 (1<<1)
+#define B_TX_AUD_EN_I2S0 (1<<0)
+#define B_TX_AUD_EN_SPDIF 1
+
+#define REG_TX_AUDIO_CTRL1 0xE1
+#define REG_TX_AUDIO_FIFOMAP 0xE2
+#define REG_TX_AUDIO_CTRL3 0xE3
+#define B_TX_CHSTSEL (1<<4)
+
+#define REG_TX_AUD_SRCVALID_FLAT 0xE4
+
+#define REG_TX_AUD_HDAUDIO 0xE5
+#define B_TX_HBR (1<<3)
+#define B_TX_DSD (1<<1)
+#define B_TX_TDM (1<<0)
+
+/* HDMI TX reg Bank 1 */
+#define REGPktAudCTS0 0x30 /* 7:0 */
+#define REGPktAudCTS1 0x31 /* 15:8 */
+#define REGPktAudCTS2 0x32 /* 19:16 */
+#define REGPktAudN0 0x33 /* 7:0 */
+#define REGPktAudN1 0x34 /* 15:8 */
+#define REGPktAudN2 0x35 /* 19:16 */
+#define REG_TX_AVIINFO_DB1 0x58
+#define REG_TX_AVIINFO_SUM 0x5D
+#define REG_TX_PKT_AUDINFO_CC 0x68 /* [2:0] */
+#define REG_TX_PKT_AUDINFO_SF 0x69 /* [4:2] */
+#define REG_TX_PKT_AUDINFO_CA 0x6B /* [7:0] */
+#define REG_TX_PKT_AUDINFO_DM_LSV 0x6C /* [7][6:3] */
+#define REG_TX_PKT_AUDINFO_SUM 0x6D /* [7:0] */
+#define REG_TX_AUDCHST_MODE 0x91 /* 191 REG_TX_AUD_CHSTD[2:0] 6:4 */
+#define REG_TX_AUDCHST_CAT 0x92 /* 192 REG_TX_AUD_CHSTCAT 7:0 */
+#define REG_TX_AUDCHST_SRCNUM 0x93 /* 193 REG_TX_AUD_CHSTSRC 3:0 */
+#define REG_TX_AUD0CHST_CHTNUM 0x94 /* 194 REG_TX_AUD0_CHSTCHR 7:4 */
+#define REG_TX_AUDCHST_CA_FS 0x98 /* 198 REG_TX_AUD_CHSTCA 5:4 */
+#define REG_TX_AUDCHST_OFS_WL 0x99 /* 199 REG_TX_AUD_CHSTOFS 7:4 */
+
+#define REG_TX_PKT_SINGLE_CTRL 0xC5
+#define B_TX_SW_CTS (1<<1)
+
+#define REG_TX_AVI_INFOFRM_CTRL 0xCD
+#define REG_TX_AUD_INFOFRM_CTRL 0xCE
+#define B_TX_ENABLE_PKT 1
+#define B_TX_REPEAT_PKT (1<<1)
+
+struct RegSetEntry {
+ u8 offset;
+ u8 mask;
+ u8 value;
+};
+
+enum {
+ PCLK_LOW = 0,
+ PCLK_MEDIUM,
+ PCLK_HIGH
+};
+
+u8 bCSCMtx_RGB2YUV_ITU601_16_235[] = {
+ 0x00, 0x80, 0x00,
+ 0xB2, 0x04, 0x65, 0x02, 0xE9, 0x00,
+ 0x93, 0x3C, 0x18, 0x04, 0x55, 0x3F,
+ 0x49, 0x3D, 0x9F, 0x3E, 0x18, 0x04
+};
+
+u8 bCSCMtx_RGB2YUV_ITU601_0_255[] = {
+ 0x10, 0x80, 0x10,
+ 0x09, 0x04, 0x0E, 0x02, 0xC9, 0x00,
+ 0x0F, 0x3D, 0x84, 0x03, 0x6D, 0x3F,
+ 0xAB, 0x3D, 0xD1, 0x3E, 0x84, 0x03
+};
+
+u8 bCSCMtx_RGB2YUV_ITU709_16_235[] = {
+ 0x00, 0x80, 0x00,
+ 0xB8, 0x05, 0xB4, 0x01, 0x94, 0x00,
+ 0x4a, 0x3C, 0x17, 0x04, 0x9F, 0x3F,
+ 0xD9, 0x3C, 0x10, 0x3F, 0x17, 0x04
+};
+
+u8 bCSCMtx_RGB2YUV_ITU709_0_255[] = {
+ 0x10, 0x80, 0x10,
+ 0xEa, 0x04, 0x77, 0x01, 0x7F, 0x00,
+ 0xD0, 0x3C, 0x83, 0x03, 0xAD, 0x3F,
+ 0x4B, 0x3D, 0x32, 0x3F, 0x83, 0x03
+};
+
+u8 bCSCMtx_YUV2RGB_ITU601_16_235[] = {
+ 0x00, 0x00, 0x00,
+ 0x00, 0x08, 0x6B, 0x3A, 0x50, 0x3D,
+ 0x00, 0x08, 0xF5, 0x0A, 0x02, 0x00,
+ 0x00, 0x08, 0xFD, 0x3F, 0xDA, 0x0D
+};
+
+u8 bCSCMtx_YUV2RGB_ITU601_0_255[] = {
+ 0x04, 0x00, 0xA7,
+ 0x4F, 0x09, 0x81, 0x39, 0xDD, 0x3C,
+ 0x4F, 0x09, 0xC4, 0x0C, 0x01, 0x00,
+ 0x4F, 0x09, 0xFD, 0x3F, 0x1F, 0x10
+};
+
+u8 bCSCMtx_YUV2RGB_ITU709_16_235[] = {
+ 0x00, 0x00, 0x00,
+ 0x00, 0x08, 0x55, 0x3C, 0x88, 0x3E,
+ 0x00, 0x08, 0x51, 0x0C, 0x00, 0x00,
+ 0x00, 0x08, 0x00, 0x00, 0x84, 0x0E
+};
+
+u8 bCSCMtx_YUV2RGB_ITU709_0_255[] = {
+ 0x04, 0x00, 0xA7,
+ 0x4F, 0x09, 0xBA, 0x3B, 0x4B, 0x3E,
+ 0x4F, 0x09, 0x57, 0x0E, 0x02, 0x00,
+ 0x4F, 0x09, 0xFE, 0x3F, 0xE8, 0x10
+};
+
+#endif
diff --git a/drivers/gpu/drm/bridge/it6263.c b/drivers/gpu/drm/bridge/it6263.c
new file mode 100644
index 000000000000..a687a2754097
--- /dev/null
+++ b/drivers/gpu/drm/bridge/it6263.c
@@ -0,0 +1,1040 @@
+/*
+ * Copyright 2017-2019 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <drm/drm_vblank.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_print.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/regmap.h>
+
+#define REG_VENDOR_ID(n) (0x00 + (n)) /* n: 0/1 */
+#define REG_DEVICE_ID(n) (0x02 + (n)) /* n: 0/1 */
+#define LVDS_VENDER_ID_LOW 0x15
+#define LVDS_VENDER_ID_HIGH 0xCA
+#define LVDS_DEVICE_ID_LOW 0x61
+#define LVDS_DEVICE_ID_HIGH 0x62
+#define HDMI_VENDER_ID_LOW 0x01
+#define HDMI_VENDER_ID_HIGH 0xCA
+#define HDMI_DEVICE_ID_LOW 0x13
+#define HDMI_DEVICE_ID_HIGH 0x76
+
+/* LVDS registers */
+#define LVDS_REG_SW_RST 0x05
+#define SOFT_REFCLK_DM_RST BIT(0)
+#define SOFT_PCLK_DM_RST BIT(1)
+
+#define LVDS_REG_MODE 0x2C
+#define LVDS_COLOR_DEPTH 0x3
+enum {
+ LVDS_COLOR_DEPTH_18,
+ LVDS_COLOR_DEPTH_24,
+ LVDS_COLOR_DEPTH_30,
+ LVDS_COLOR_DEPTH_36,
+};
+#define LVDS_OUT_MAP BIT(4)
+#define VESA BIT(4)
+#define JEIDA 0
+#define DMODE BIT(7)
+#define SPLIT_MODE BIT(7)
+#define SINGLE_MODE 0
+
+#define LVDS_REG_STABLE 0x30
+#define VIDEO_STABLE BIT(0)
+#define PCLK_LOCK BIT(1)
+
+#define LVDS_REG_39 0x39
+
+#define LVDS_REG_PLL 0x3C
+#define LVDS_REG_AFE_3E 0x3E
+#define LVDS_REG_AFE_3F 0x3F
+#define LVDS_REG_AFE_47 0x47
+#define LVDS_REG_AFE_48 0x48
+#define LVDS_REG_AFE_4F 0x4F
+#define LVDS_REG_52 0x52
+#define LVDS_REG_PCLK_CNT_HIGH 0x57
+#define LVDS_REG_PCLK_CNT_LOW 0x58
+
+/*
+ * HDMI registers
+ *
+ * Registers are separated into three banks:
+ * 1) common bank: 0x00 ~ 0x2F
+ * 2) bank0: 0x30 ~ 0xFF
+ * 3) bank1: 0x130 ~ 0x1FF (HDMI packet registers)
+ *
+ * Use register HDMI_REG_BANK_CTRL @ 0x0F[1:0] to select bank0/1:
+ * 2b'00 - bank0
+ * 2b'01 - bank1
+ */
+
+/******************************/
+/* HDMI register common bank */
+/******************************/
+
+/* HDMI genernal registers */
+#define HDMI_REG_SW_RST 0x04
+#define SOFTREF_RST BIT(5)
+#define SOFTA_RST BIT(4)
+#define SOFTV_RST BIT(3)
+#define AUD_RST BIT(2)
+#define HDCP_RST BIT(0)
+#define HDMI_RST_ALL (SOFTREF_RST | SOFTA_RST | SOFTV_RST | \
+ AUD_RST | HDCP_RST)
+
+#define HDMI_REG_INT_CTRL 0x05
+#define INTPOL_ACTH BIT(7)
+#define INTPOL_ACTL 0
+#define INTIOMODE_OPENDRAIN BIT(6)
+#define INTIOMODE_PUSHPULL 0
+#define SELXTAL BIT(5) /* REFCLK <= XTALCLK */
+#define SELXTAL_QUARTER 0 /* REFCLK <= OSCCLK/4 */
+#define PDREFCNT(n) (((n) >> 2) << 2) /* REFCLK Div(n) */
+#define PDREFCLK BIT(1)
+#define PDTXCLK_GATED BIT(0)
+#define PDTXCLK_ACTIVE 0
+
+#define HDMI_REG_INT_STAT(n) (0x05 + (n)) /* n: 1/2/3 */
+#define HDMI_REG_INT_MASK(n) (0x08 + (n)) /* n: 1/2/3 */
+
+/* INT1 */
+#define INT_AUD_OVERFLOW BIT(7)
+#define INT_RDDC_NOACK BIT(5)
+#define INT_DDCFIFO_ERR BIT(4)
+#define INT_DDC_BUS_HANG BIT(2)
+#define INT_RX_SENSE BIT(1)
+#define INT_HPD BIT(0)
+
+/* INT2 */
+#define INT_VID_UNSTABLE BIT(6)
+#define INT_PKTACP BIT(5)
+#define INT_PKTNULL BIT(4)
+#define INT_PKTGEN BIT(3)
+#define INT_KSVLIST_CHK BIT(2)
+#define INT_AUTH_DONE BIT(1)
+#define INT_AUTH_FAIL BIT(0)
+
+/* INT3 */
+#define INT_AUD_CTS BIT(6)
+#define INT_VSYNC BIT(5)
+#define INT_VIDSTABLE BIT(4)
+#define INT_PKTMPG BIT(3)
+#define INT_PKTGBD BIT(2)
+#define INT_PKTAUD BIT(1)
+#define INT_PKTAVI BIT(0)
+
+#define INT_MASK_AUD_CTS BIT(5)
+#define INT_MASK_VSYNC BIT(4)
+#define INT_MASK_VIDSTABLE BIT(3)
+#define INT_MASK_PKTMPG BIT(2)
+#define INT_MASK_PKTGBD BIT(1)
+#define INT_MASK_PKTAUD BIT(0)
+
+#define HDMI_REG_INT_CLR(n) (0x0C + (n)) /* n: 0/1 */
+
+/* CLR0 */
+#define INT_CLR_PKTACP BIT(7)
+#define INT_CLR_PKTNULL BIT(6)
+#define INT_CLR_PKTGEN BIT(5)
+#define INT_CLR_KSVLIST_CHK BIT(4)
+#define INT_CLR_AUTH_DONE BIT(3)
+#define INT_CLR_AUTH_FAIL BIT(2)
+#define INT_CLR_RXSENSE BIT(1)
+#define INT_CLR_HPD BIT(0)
+
+/* CLR1 */
+#define INT_CLR_VSYNC BIT(7)
+#define INT_CLR_VIDSTABLE BIT(6)
+#define INT_CLR_PKTMPG BIT(5)
+#define INT_CLR_PKTGBD BIT(4)
+#define INT_CLR_PKTAUD BIT(3)
+#define INT_CLR_PKTAVI BIT(2)
+#define INT_CLR_VID_UNSTABLE BIT(0)
+
+#define HDMI_REG_SYS_STATUS 0x0E
+#define INT_ACTIVE BIT(7)
+#define HPDETECT BIT(6)
+#define RXSENDETECT BIT(5)
+#define TXVIDSTABLE BIT(4)
+#define CTSINTSTEP 0xC
+#define CLR_AUD_CTS BIT(1)
+#define INTACTDONE BIT(0)
+
+#define HDMI_REG_BANK_CTRL 0x0F
+#define BANK_SEL(n) ((n) ? 1 : 0)
+
+/* HDMI System DDC control registers */
+#define HDMI_REG_DDC_MASTER_CTRL 0x10
+#define MASTER_SEL_HOST BIT(0)
+#define MASTER_SEL_HDCP 0
+
+#define HDMI_REG_DDC_HEADER 0x11
+#define DDC_HDCP_ADDRESS 0x74
+
+#define HDMI_REG_DDC_REQOFF 0x12
+#define HDMI_REG_DDC_REQCOUNT 0x13
+#define HDMI_REG_DDC_EDIDSEG 0x14
+
+#define HDMI_REG_DDC_CMD 0x15
+#define DDC_CMD_SEQ_BURSTREAD 0x0
+#define DDC_CMD_LINK_CHKREAD 0x2
+#define DDC_CMD_EDID_READ 0x3
+#define DDC_CMD_FIFO_CLR 0x9
+#define DDC_CMD_GEN_SCLCLK 0xA
+#define DDC_CMD_ABORT 0xF
+
+#define HDMI_REG_DDC_STATUS 0x16
+#define DDC_DONE BIT(7)
+#define DDC_ACT BIT(6)
+#define DDC_NOACK BIT(5)
+#define DDC_WAITBUS BIT(4)
+#define DDC_ARBILOSE BIT(3)
+#define DDC_ERROR (DDC_NOACK | DDC_WAITBUS | DDC_ARBILOSE)
+#define DDC_FIFOFULL BIT(2)
+#define DDC_FIFOEMPTY BIT(1)
+
+#define HDMI_DDC_FIFO_SIZE 32 /* bytes */
+#define HDMI_REG_DDC_READFIFO 0x17
+#define HDMI_REG_ROM_STAT 0x1C
+#define HDMI_REG_LVDS_PORT 0x1D /* LVDS input ctrl i2c addr */
+#define HDMI_REG_LVDS_PORT_EN 0x1E /* and to enable */
+#define LVDS_INPUT_CTRL_I2C_ADDR 0x33
+
+/***********************/
+/* HDMI register bank0 */
+/***********************/
+
+/* HDMI clock control registers */
+#define HDMI_REG_CLK_CTRL1 0x59
+#define EN_TXCLK_COUNT BIT(5)
+#define VDO_LATCH_EDGE BIT(3)
+
+/* HDMI AFE registers */
+#define HDMI_REG_AFE_DRV_CTRL 0x61
+#define AFE_DRV_PWD BIT(5)
+#define AFE_DRV_RST BIT(4)
+#define AFE_DRV_PDRXDET BIT(2)
+#define AFE_DRV_TERMON BIT(1)
+#define AFE_DRV_ENCAL BIT(0)
+
+#define HDMI_REG_AFE_XP_CTRL 0x62
+#define AFE_XP_GAINBIT BIT(7)
+#define AFE_XP_PWDPLL BIT(6)
+#define AFE_XP_ENI BIT(5)
+#define AFE_XP_ER0 BIT(4)
+#define AFE_XP_RESETB BIT(3)
+#define AFE_XP_PWDI BIT(2)
+#define AFE_XP_DEI BIT(1)
+#define AFE_XP_DER BIT(0)
+
+#define HDMI_REG_AFE_ISW_CTRL 0x63
+#define AFE_RTERM_SEL BIT(7)
+#define AFE_IP_BYPASS BIT(6)
+#define AFE_DRV_ISW 0x38
+#define AFE_DRV_ISWK 7
+
+#define HDMI_REG_AFE_IP_CTRL 0x64
+#define AFE_IP_GAINBIT BIT(7)
+#define AFE_IP_PWDPLL BIT(6)
+#define AFE_IP_CKSEL 0x30
+#define AFE_IP_ER0 BIT(3)
+#define AFE_IP_RESETB BIT(2)
+#define AFE_IP_ENC BIT(1)
+#define AFE_IP_EC1 BIT(0)
+
+/* HDMI input data format registers */
+#define HDMI_REG_INPUT_MODE 0x70
+#define IN_RGB 0x00
+#define IN_YUV422 0x40
+#define IN_YUV444 0x80
+
+#define HDMI_REG_TXFIFO_RST 0x71
+#define ENAVMUTERST BIT(0)
+#define TXFFRST BIT(1)
+
+/* HDMI pattern generation SYNC/DE registers */
+#define HDMI_REG_9X(n) (0x90 + (n)) /* n: 0x0 ~ 0xF */
+#define HDMI_REG_AX(n) (0xA0 + (n)) /* n: 0x0 ~ 0xF */
+#define HDMI_REG_B0 0xB0
+
+/* HDMI general control registers */
+#define HDMI_REG_HDMI_MODE 0xC0
+#define TX_HDMI_MODE 1
+#define TX_DVI_MODE 0
+
+#define HDMI_REG_GCP 0xC1
+#define AVMUTE BIT(0)
+#define BLUE_SCR_MUTE BIT(1)
+#define NODEF_PHASE BIT(2)
+#define PHASE_RESYNC BIT(3)
+#define HDMI_COLOR_DEPTH 0x70
+enum {
+ HDMI_COLOR_DEPTH_DEF = 0x0, /* default as 24bit */
+ HDMI_COLOR_DEPTH_24 = 0x40,
+ HDMI_COLOR_DEPTH_30 = 0x50,
+ HDMI_COLOR_DEPTH_36 = 0x60,
+ HDMI_COLOR_DEPTH_48 = 0x70,
+};
+
+#define HDMI_REG_OESS_CYCLE 0xC3
+#define HDMI_REG_ENCRYPTION 0xC4 /* HDCP */
+
+#define HDMI_REG_PKT_SINGLE_CTRL 0xC5
+#define SINGLE_PKT BIT(0)
+#define BURST_PKT 0
+
+#define HDMI_REG_PKT_GENERAL_CTRL 0xC6
+#define HDMI_REG_NULL_CTRL 0xC9
+#define HDMI_REG_ACP_CTRL 0xCA
+#define HDMI_REG_ISRC1_CTRL 0xCB
+#define HDMI_REG_ISRC2_CTRL 0xCC
+#define HDMI_REG_AVI_INFOFRM_CTRL 0xCD
+#define HDMI_REG_AUD_INFOFRM_CTRL 0xCE
+#define HDMI_REG_SPD_INFOFRM_CTRL 0xCF
+#define HDMI_REG_MPG_INFOFRM_CTRL 0xD0
+#define ENABLE_PKT BIT(0)
+#define REPEAT_PKT BIT(1)
+
+/***********************/
+/* HDMI register bank1 */
+/***********************/
+
+/* AVI packet registers */
+#define HDMI_REG_AVI_DB1 0x58
+#define AVI_DB1_COLOR_SPACE 0x60
+enum {
+ AVI_COLOR_SPACE_RGB = 0x00,
+ AVI_COLOR_SPACE_YUV422 = 0x20,
+ AVI_COLOR_SPACE_YUV444 = 0x40,
+};
+
+struct it6263 {
+ struct i2c_client *hdmi_i2c;
+ struct i2c_client *lvds_i2c;
+ struct regmap *hdmi_regmap;
+ struct regmap *lvds_regmap;
+ struct drm_bridge bridge;
+ struct drm_connector connector;
+ struct gpio_desc *reset_gpio;
+ bool is_hdmi;
+ bool split_mode;
+};
+
+struct it6263_minimode {
+ int hdisplay;
+ int vdisplay;
+ int vrefresh;
+};
+
+static const struct it6263_minimode it6263_bad_mode_db[] = {
+ {1600, 900, 60},
+ {1280, 1024, 60},
+ {1280, 720, 30},
+ {1280, 720, 25},
+ {1280, 720, 24},
+ {1152, 864, 75},
+};
+
+static inline struct it6263 *bridge_to_it6263(struct drm_bridge *bridge)
+{
+ return container_of(bridge, struct it6263, bridge);
+}
+
+static inline struct it6263 *connector_to_it6263(struct drm_connector *con)
+{
+ return container_of(con, struct it6263, connector);
+}
+
+static inline void lvds_update_bits(struct it6263 *it6263, unsigned int reg,
+ unsigned int mask, unsigned int val)
+{
+ regmap_update_bits(it6263->lvds_regmap, reg, mask, val);
+}
+
+static inline void hdmi_update_bits(struct it6263 *it6263, unsigned int reg,
+ unsigned int mask, unsigned int val)
+{
+ regmap_update_bits(it6263->hdmi_regmap, reg, mask, val);
+}
+
+static void it6263_reset(struct it6263 *it6263)
+{
+ if (!it6263->reset_gpio)
+ return;
+
+ gpiod_set_value_cansleep(it6263->reset_gpio, 0);
+
+ usleep_range(1000, 2000);
+
+ gpiod_set_value_cansleep(it6263->reset_gpio, 1);
+
+ /*
+ * The chip maker says the low pulse should be at least 40ms,
+ * so 41ms is sure to be enough.
+ */
+ usleep_range(41000, 45000);
+
+ gpiod_set_value_cansleep(it6263->reset_gpio, 0);
+
+ /* somehow, addtional time to wait the high voltage to be stable */
+ usleep_range(5000, 6000);
+}
+
+static void it6263_lvds_reset(struct it6263 *it6263)
+{
+ /* AFE PLL reset */
+ lvds_update_bits(it6263, LVDS_REG_PLL, 0x1, 0x0);
+ usleep_range(1000, 2000);
+ lvds_update_bits(it6263, LVDS_REG_PLL, 0x1, 0x1);
+
+ /* pclk reset */
+ lvds_update_bits(it6263, LVDS_REG_SW_RST,
+ SOFT_PCLK_DM_RST, SOFT_PCLK_DM_RST);
+ usleep_range(1000, 2000);
+ lvds_update_bits(it6263, LVDS_REG_SW_RST, SOFT_PCLK_DM_RST, 0x0);
+
+ usleep_range(1000, 2000);
+}
+
+static void it6263_lvds_set_interface(struct it6263 *it6263)
+{
+ /* color depth */
+ lvds_update_bits(it6263, LVDS_REG_MODE, LVDS_COLOR_DEPTH,
+ LVDS_COLOR_DEPTH_24);
+
+ /* jeida mapping */
+ lvds_update_bits(it6263, LVDS_REG_MODE, LVDS_OUT_MAP, JEIDA);
+
+ if (it6263->split_mode) {
+ lvds_update_bits(it6263, LVDS_REG_MODE, DMODE, SPLIT_MODE);
+ lvds_update_bits(it6263, LVDS_REG_52, BIT(1), BIT(1));
+ } else {
+ lvds_update_bits(it6263, LVDS_REG_MODE, DMODE, SINGLE_MODE);
+ lvds_update_bits(it6263, LVDS_REG_52, BIT(1), 0);
+ }
+}
+
+static void it6263_lvds_set_afe(struct it6263 *it6263)
+{
+ struct regmap *regmap = it6263->lvds_regmap;
+
+ regmap_write(regmap, LVDS_REG_AFE_3E, 0xaa);
+ regmap_write(regmap, LVDS_REG_AFE_3F, 0x02);
+ regmap_write(regmap, LVDS_REG_AFE_47, 0xaa);
+ regmap_write(regmap, LVDS_REG_AFE_48, 0x02);
+ regmap_write(regmap, LVDS_REG_AFE_4F, 0x11);
+
+ lvds_update_bits(it6263, LVDS_REG_PLL, 0x07, 0);
+}
+
+static void it6263_lvds_config(struct it6263 *it6263)
+{
+ it6263_lvds_reset(it6263);
+ it6263_lvds_set_interface(it6263);
+ it6263_lvds_set_afe(it6263);
+}
+
+static void it6263_hdmi_config(struct it6263 *it6263)
+{
+ regmap_write(it6263->hdmi_regmap, HDMI_REG_INPUT_MODE, IN_RGB);
+
+ hdmi_update_bits(it6263, HDMI_REG_GCP, HDMI_COLOR_DEPTH,
+ HDMI_COLOR_DEPTH_24);
+}
+
+static bool it6263_hpd_is_connected(struct it6263 *it6263)
+{
+ unsigned int status;
+
+ regmap_read(it6263->hdmi_regmap, HDMI_REG_SYS_STATUS, &status);
+
+ return !!(status & HPDETECT);
+}
+
+static enum drm_connector_status
+it6263_connector_detect(struct drm_connector *connector, bool force)
+{
+ struct it6263 *it6263 = connector_to_it6263(connector);
+ int i;
+
+ if (force) {
+ /*
+ * FIXME: We read status tens of times to workaround
+ * cable detection failure issue at boot time on some
+ * platforms.
+ * Spin on this for up to one second.
+ */
+ for (i = 0; i < 100; i++) {
+ if (it6263_hpd_is_connected(it6263))
+ return connector_status_connected;
+ usleep_range(5000, 10000);
+ }
+ } else {
+ if (it6263_hpd_is_connected(it6263))
+ return connector_status_connected;
+ }
+
+ return connector_status_disconnected;
+}
+
+static const struct drm_connector_funcs it6263_connector_funcs = {
+ .detect = it6263_connector_detect,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = drm_connector_cleanup,
+ .reset = drm_atomic_helper_connector_reset,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static int
+it6263_read_edid(void *data, u8 *buf, unsigned int block, size_t len)
+{
+ struct it6263 *it6263 = data;
+ struct regmap *regmap = it6263->hdmi_regmap;
+ unsigned long timeout;
+ unsigned int status, count, val;
+ unsigned int segment = block >> 1;
+ unsigned int start = (block % 2) * EDID_LENGTH;
+
+ regmap_write(regmap, HDMI_REG_DDC_MASTER_CTRL, MASTER_SEL_HOST);
+ regmap_write(regmap, HDMI_REG_DDC_HEADER, DDC_ADDR << 1);
+ regmap_write(regmap, HDMI_REG_DDC_EDIDSEG, segment);
+
+ while (len) {
+ /* clear DDC FIFO */
+ regmap_write(regmap, HDMI_REG_DDC_CMD, DDC_CMD_FIFO_CLR);
+
+ timeout = jiffies + msecs_to_jiffies(10);
+ do {
+ regmap_read(regmap, HDMI_REG_DDC_STATUS, &status);
+ } while (!(status & DDC_DONE) && time_before(jiffies, timeout));
+
+ if (!(status & DDC_DONE)) {
+ dev_err(&it6263->hdmi_i2c->dev,
+ "failed to clear DDC FIFO\n");
+ return -ETIMEDOUT;
+ }
+
+ count = len > HDMI_DDC_FIFO_SIZE ? HDMI_DDC_FIFO_SIZE : len;
+
+ /* fire the read command */
+ regmap_write(regmap, HDMI_REG_DDC_REQOFF, start);
+ regmap_write(regmap, HDMI_REG_DDC_REQCOUNT, count);
+ regmap_write(regmap, HDMI_REG_DDC_CMD, DDC_CMD_EDID_READ);
+
+ start += count;
+ len -= count;
+
+ /* wait for reading done */
+ timeout = jiffies + msecs_to_jiffies(250);
+ do {
+ regmap_read(regmap, HDMI_REG_DDC_STATUS, &status);
+ if (status & DDC_ERROR) {
+ dev_err(&it6263->hdmi_i2c->dev, "DDC error\n");
+ return -EIO;
+ }
+ } while (!(status & DDC_DONE) && time_before(jiffies, timeout));
+
+ if (!(status & DDC_DONE)) {
+ dev_err(&it6263->hdmi_i2c->dev,
+ "failed to read EDID\n");
+ return -ETIMEDOUT;
+ }
+
+ /* cache to buffer */
+ for (; count > 0; count--) {
+ regmap_read(regmap, HDMI_REG_DDC_READFIFO, &val);
+ *(buf++) = val;
+ }
+ }
+
+ return 0;
+}
+
+static int it6263_get_modes(struct drm_connector *connector)
+{
+ struct it6263 *it6263 = connector_to_it6263(connector);
+ u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24;
+ struct edid *edid;
+ int num = 0;
+ int ret;
+
+ edid = drm_do_get_edid(connector, it6263_read_edid, it6263);
+ drm_connector_update_edid_property(connector, edid);
+ if (edid) {
+ num = drm_add_edid_modes(connector, edid);
+ it6263->is_hdmi = drm_detect_hdmi_monitor(edid);
+ kfree(edid);
+ }
+
+ ret = drm_display_info_set_bus_formats(&connector->display_info,
+ &bus_format, 1);
+ if (ret)
+ dev_dbg(&it6263->hdmi_i2c->dev,
+ "failed to set the supported bus format %d\n", ret);
+
+ return num;
+}
+
+static enum drm_mode_status it6263_mode_valid(struct drm_connector *connector,
+ struct drm_display_mode *mode)
+{
+ const struct it6263_minimode *m;
+ int i, vrefresh = drm_mode_vrefresh(mode);
+
+ if (mode->clock > 150000)
+ return MODE_CLOCK_HIGH;
+
+ for (i = 0; i < ARRAY_SIZE(it6263_bad_mode_db); i++) {
+ m = &it6263_bad_mode_db[i];
+ if ((mode->hdisplay == m->hdisplay) &&
+ (mode->vdisplay == m->vdisplay) &&
+ (vrefresh == m->vrefresh))
+ return MODE_BAD;
+ }
+
+ return MODE_OK;
+}
+
+static const struct drm_connector_helper_funcs it6263_connector_helper_funcs = {
+ .get_modes = it6263_get_modes,
+ .mode_valid = it6263_mode_valid,
+};
+
+static void it6263_bridge_disable(struct drm_bridge *bridge)
+{
+ struct it6263 *it6263 = bridge_to_it6263(bridge);
+ struct regmap *regmap = it6263->hdmi_regmap;
+
+ /* AV mute */
+ hdmi_update_bits(it6263, HDMI_REG_GCP, AVMUTE, AVMUTE);
+
+ if (it6263->is_hdmi)
+ regmap_write(regmap, HDMI_REG_PKT_GENERAL_CTRL, 0);
+
+ hdmi_update_bits(it6263, HDMI_REG_SW_RST, SOFTV_RST, SOFTV_RST);
+ regmap_write(regmap, HDMI_REG_AFE_DRV_CTRL, AFE_DRV_RST | AFE_DRV_PWD);
+}
+
+static void it6263_bridge_enable(struct drm_bridge *bridge)
+{
+ struct it6263 *it6263 = bridge_to_it6263(bridge);
+ struct regmap *regmap = it6263->hdmi_regmap;
+ unsigned long timeout;
+ unsigned int status;
+ bool is_stable = false;
+ int i;
+
+ regmap_write(it6263->hdmi_regmap, HDMI_REG_BANK_CTRL, BANK_SEL(1));
+ /* set the color space to RGB in the AVI packet */
+ hdmi_update_bits(it6263, HDMI_REG_AVI_DB1, AVI_DB1_COLOR_SPACE,
+ AVI_COLOR_SPACE_RGB);
+ regmap_write(it6263->hdmi_regmap, HDMI_REG_BANK_CTRL, BANK_SEL(0));
+
+ /* software video reset */
+ hdmi_update_bits(it6263, HDMI_REG_SW_RST, SOFTV_RST, SOFTV_RST);
+ usleep_range(1000, 2000);
+ hdmi_update_bits(it6263, HDMI_REG_SW_RST, SOFTV_RST, 0);
+
+ /* reconfigure LVDS and retry several times in case video is instable */
+ for (i = 0; i < 3; i++) {
+ timeout = jiffies + msecs_to_jiffies(500);
+ do {
+ regmap_read(regmap, HDMI_REG_SYS_STATUS, &status);
+ } while (!(status & TXVIDSTABLE) &&
+ time_before(jiffies, timeout));
+
+ if (status & TXVIDSTABLE) {
+ is_stable = true;
+ break;
+ }
+
+ it6263_lvds_config(it6263);
+
+ dev_dbg(&it6263->hdmi_i2c->dev,
+ "retry to lock input video %d\n", i);
+ }
+
+ if (!is_stable)
+ dev_warn(&it6263->hdmi_i2c->dev,
+ "failed to wait for video stable\n");
+
+ regmap_write(regmap, HDMI_REG_AFE_DRV_CTRL, 0);
+
+ /* AV unmute */
+ hdmi_update_bits(it6263, HDMI_REG_GCP, AVMUTE, 0);
+
+ if (it6263->is_hdmi)
+ regmap_write(regmap, HDMI_REG_PKT_GENERAL_CTRL,
+ ENABLE_PKT | REPEAT_PKT);
+}
+
+static void it6263_bridge_mode_set(struct drm_bridge *bridge,
+ const struct drm_display_mode *mode,
+ const struct drm_display_mode *adj)
+{
+ struct it6263 *it6263 = bridge_to_it6263(bridge);
+ struct regmap *regmap = it6263->hdmi_regmap;
+ bool pclk_high = adj->clock > 80000 ? true : false;
+
+ regmap_write(regmap, HDMI_REG_HDMI_MODE,
+ it6263->is_hdmi ? TX_HDMI_MODE : TX_DVI_MODE);
+
+ dev_dbg(&it6263->hdmi_i2c->dev, "%s mode\n",
+ it6263->is_hdmi ? "HDMI" : "DVI");
+
+ /* setup AFE */
+ regmap_write(regmap, HDMI_REG_AFE_DRV_CTRL, AFE_DRV_RST);
+ if (pclk_high)
+ regmap_write(regmap, HDMI_REG_AFE_XP_CTRL,
+ AFE_XP_GAINBIT | AFE_XP_RESETB);
+ else
+ regmap_write(regmap, HDMI_REG_AFE_XP_CTRL,
+ AFE_XP_ER0 | AFE_XP_RESETB);
+ regmap_write(regmap, HDMI_REG_AFE_ISW_CTRL, 0x10);
+ if (pclk_high)
+ regmap_write(regmap, HDMI_REG_AFE_IP_CTRL,
+ AFE_IP_GAINBIT | AFE_IP_RESETB);
+ else
+ regmap_write(regmap, HDMI_REG_AFE_IP_CTRL,
+ AFE_IP_ER0 | AFE_IP_RESETB);
+}
+
+static int it6263_bridge_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
+{
+ struct it6263 *it6263 = bridge_to_it6263(bridge);
+ struct drm_device *drm = bridge->dev;
+ int ret;
+
+ if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
+ DRM_ERROR("Fix bridge driver to make connector optional!");
+ return -EINVAL;
+ }
+
+ if (!drm_core_check_feature(drm, DRIVER_ATOMIC)) {
+ dev_err(&it6263->hdmi_i2c->dev,
+ "it6263 driver only copes with atomic updates\n");
+ return -ENOTSUPP;
+ }
+
+ it6263->connector.polled = DRM_CONNECTOR_POLL_CONNECT |
+ DRM_CONNECTOR_POLL_DISCONNECT;
+ ret = drm_connector_init(drm, &it6263->connector,
+ &it6263_connector_funcs,
+ DRM_MODE_CONNECTOR_HDMIA);
+ if (ret) {
+ dev_err(&it6263->hdmi_i2c->dev,
+ "Failed to initialize connector with drm\n");
+ return ret;
+ }
+
+ drm_connector_helper_add(&it6263->connector,
+ &it6263_connector_helper_funcs);
+ drm_connector_attach_encoder(&it6263->connector, bridge->encoder);
+
+ return ret;
+}
+
+static const struct drm_bridge_funcs it6263_bridge_funcs = {
+ .attach = it6263_bridge_attach,
+ .mode_set = it6263_bridge_mode_set,
+ .disable = it6263_bridge_disable,
+ .enable = it6263_bridge_enable,
+};
+
+static int it6263_check_chipid(struct it6263 *it6263)
+{
+ struct device *dev = &it6263->hdmi_i2c->dev;
+ u8 vendor_id[2], device_id[2];
+ int ret;
+
+ ret = regmap_bulk_read(it6263->hdmi_regmap, REG_VENDOR_ID(0),
+ &vendor_id, 2);
+ if (ret) {
+ dev_err(dev, "regmap_bulk_read failed %d\n", ret);
+ return ret;
+ }
+
+ if (vendor_id[0] != HDMI_VENDER_ID_LOW ||
+ vendor_id[1] != HDMI_VENDER_ID_HIGH) {
+ dev_err(dev,
+ "Invalid hdmi vendor id %02x %02x(expect 0x01 0xca)\n",
+ vendor_id[0], vendor_id[1]);
+ return -EINVAL;
+ }
+
+ ret = regmap_bulk_read(it6263->hdmi_regmap, REG_DEVICE_ID(0),
+ &device_id, 2);
+ if (ret) {
+ dev_err(dev, "regmap_bulk_read failed %d\n", ret);
+ return ret;
+ }
+
+ if (device_id[0] != HDMI_DEVICE_ID_LOW ||
+ device_id[1] != HDMI_DEVICE_ID_HIGH) {
+ dev_err(dev,
+ "Invalid hdmi device id %02x %02x(expect 0x13 0x76)\n",
+ device_id[0], device_id[1]);
+ return -EINVAL;
+ }
+
+ ret = regmap_bulk_read(it6263->lvds_regmap, REG_VENDOR_ID(0),
+ &vendor_id, 2);
+ if (ret) {
+ dev_err(dev, "regmap_bulk_read failed %d\n", ret);
+ return ret;
+ }
+
+ if (vendor_id[0] != LVDS_VENDER_ID_LOW ||
+ vendor_id[1] != LVDS_VENDER_ID_HIGH) {
+ dev_err(dev,
+ "Invalid lvds vendor id %02x %02x(expect 0x15 0xca)\n",
+ vendor_id[0], vendor_id[1]);
+ return -EINVAL;
+ }
+
+ ret = regmap_bulk_read(it6263->lvds_regmap, REG_DEVICE_ID(0),
+ &device_id, 2);
+ if (ret) {
+ dev_err(dev, "regmap_bulk_read failed %d\n", ret);
+ return ret;
+ }
+
+ if (device_id[0] != LVDS_DEVICE_ID_LOW ||
+ device_id[1] != LVDS_DEVICE_ID_HIGH) {
+ dev_err(dev,
+ "Invalid lvds device id %02x %02x(expect 0x61 0x62)\n",
+ device_id[0], device_id[1]);
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static const struct regmap_range it6263_hdmi_volatile_ranges[] = {
+ { .range_min = 0, .range_max = 0x1ff },
+};
+
+static const struct regmap_access_table it6263_hdmi_volatile_table = {
+ .yes_ranges = it6263_hdmi_volatile_ranges,
+ .n_yes_ranges = ARRAY_SIZE(it6263_hdmi_volatile_ranges),
+};
+
+static const struct regmap_config it6263_hdmi_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .volatile_table = &it6263_hdmi_volatile_table,
+ .cache_type = REGCACHE_NONE,
+};
+
+static const struct regmap_range it6263_lvds_volatile_ranges[] = {
+ { .range_min = 0, .range_max = 0xff },
+};
+
+static const struct regmap_access_table it6263_lvds_volatile_table = {
+ .yes_ranges = it6263_lvds_volatile_ranges,
+ .n_yes_ranges = ARRAY_SIZE(it6263_lvds_volatile_ranges),
+};
+
+static const struct regmap_config it6263_lvds_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .volatile_table = &it6263_lvds_volatile_table,
+ .cache_type = REGCACHE_NONE,
+};
+
+static int it6263_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct device *dev = &client->dev;
+ struct device_node *np = dev->of_node;
+#if IS_ENABLED(CONFIG_OF_DYNAMIC)
+ struct device_node *remote_node = NULL, *endpoint = NULL;
+ struct of_changeset ocs;
+ struct property *prop;
+#endif
+ struct it6263 *it6263;
+ int ret;
+
+ it6263 = devm_kzalloc(dev, sizeof(*it6263), GFP_KERNEL);
+ if (!it6263)
+ return -ENOMEM;
+
+ it6263->split_mode = of_property_read_bool(np, "split-mode");
+
+ it6263->hdmi_i2c = client;
+ it6263->lvds_i2c = i2c_new_dummy_device(client->adapter,
+ LVDS_INPUT_CTRL_I2C_ADDR);
+ if (!it6263->lvds_i2c) {
+ ret = -ENODEV;
+ goto of_reconfig;
+ }
+
+ it6263->hdmi_regmap = devm_regmap_init_i2c(client,
+ &it6263_hdmi_regmap_config);
+ if (IS_ERR(it6263->hdmi_regmap)) {
+ ret = PTR_ERR(it6263->hdmi_regmap);
+ goto unregister_lvds_i2c;
+ }
+
+ it6263->lvds_regmap = devm_regmap_init_i2c(it6263->lvds_i2c,
+ &it6263_lvds_regmap_config);
+ if (IS_ERR(it6263->lvds_regmap)) {
+ ret = PTR_ERR(it6263->lvds_regmap);
+ goto unregister_lvds_i2c;
+ }
+
+ it6263->reset_gpio = devm_gpiod_get_optional(dev, "reset",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(it6263->reset_gpio)) {
+ ret = PTR_ERR(it6263->reset_gpio);
+
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "Failed to get reset gpio: %d\n", ret);
+
+ goto unregister_lvds_i2c;
+ }
+
+ it6263_reset(it6263);
+
+ ret = regmap_write(it6263->hdmi_regmap, HDMI_REG_SW_RST, HDMI_RST_ALL);
+ if (ret)
+ goto unregister_lvds_i2c;
+
+ usleep_range(1000, 2000);
+
+ ret = regmap_write(it6263->hdmi_regmap, HDMI_REG_LVDS_PORT,
+ LVDS_INPUT_CTRL_I2C_ADDR << 1);
+ if (ret)
+ goto unregister_lvds_i2c;
+
+ ret = regmap_write(it6263->hdmi_regmap, HDMI_REG_LVDS_PORT_EN, 0x01);
+ if (ret)
+ goto unregister_lvds_i2c;
+
+ /* select HDMI bank0 */
+ ret = regmap_write(it6263->hdmi_regmap, HDMI_REG_BANK_CTRL,
+ BANK_SEL(0));
+ if (ret)
+ goto unregister_lvds_i2c;
+
+ ret = it6263_check_chipid(it6263);
+ if (ret)
+ goto unregister_lvds_i2c;
+
+ it6263_lvds_config(it6263);
+ it6263_hdmi_config(it6263);
+
+ it6263->bridge.funcs = &it6263_bridge_funcs;
+ it6263->bridge.of_node = np;
+ drm_bridge_add(&it6263->bridge);
+
+ i2c_set_clientdata(client, it6263);
+
+ return ret;
+
+unregister_lvds_i2c:
+ i2c_unregister_device(it6263->lvds_i2c);
+ if (ret == -EPROBE_DEFER)
+ return ret;
+
+of_reconfig:
+#if IS_ENABLED(CONFIG_OF_DYNAMIC)
+ endpoint = of_graph_get_next_endpoint(dev->of_node, NULL);
+ if (endpoint)
+ remote_node = of_graph_get_remote_port_parent(endpoint);
+
+ if (remote_node) {
+ int num_endpoints = 0;
+
+ /*
+ * Remote node should have two endpoints (input and output: us)
+ * If remote node has more than two endpoints, probably that it
+ * has more outputs, so there is no need to disable it.
+ */
+ endpoint = NULL;
+ while ((endpoint = of_graph_get_next_endpoint(remote_node,
+ endpoint)))
+ num_endpoints++;
+
+ if (num_endpoints > 2) {
+ of_node_put(remote_node);
+ return ret;
+ }
+
+ prop = devm_kzalloc(dev, sizeof(*prop), GFP_KERNEL);
+ prop->name = devm_kstrdup(dev, "status", GFP_KERNEL);
+ prop->value = devm_kstrdup(dev, "disabled", GFP_KERNEL);
+ prop->length = 9;
+ of_changeset_init(&ocs);
+ of_changeset_update_property(&ocs, remote_node, prop);
+ ret = of_changeset_apply(&ocs);
+ if (!ret)
+ dev_warn(dev,
+ "Probe failed. Remote port '%s' disabled\n",
+ remote_node->full_name);
+
+ of_node_put(remote_node);
+ };
+#endif
+
+ return ret;
+}
+
+static int it6263_remove(struct i2c_client *client)
+
+{
+ struct it6263 *it6263 = i2c_get_clientdata(client);
+
+ drm_bridge_remove(&it6263->bridge);
+ i2c_unregister_device(it6263->lvds_i2c);
+
+ return 0;
+}
+
+static const struct of_device_id it6263_dt_ids[] = {
+ { .compatible = "ite,it6263", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, it6263_dt_ids);
+
+static const struct i2c_device_id it6263_i2c_ids[] = {
+ { "it6263", 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, it6263_i2c_ids);
+
+static struct i2c_driver it6263_driver = {
+ .probe = it6263_probe,
+ .remove = it6263_remove,
+ .driver = {
+ .name = "it6263",
+ .of_match_table = it6263_dt_ids,
+ },
+ .id_table = it6263_i2c_ids,
+};
+module_i2c_driver(it6263_driver);
+
+MODULE_AUTHOR("NXP Semiconductor");
+MODULE_DESCRIPTION("ITE Tech. Inc. IT6263 LVDS->HDMI bridge");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/bridge/nwl-dsi.c b/drivers/gpu/drm/bridge/nwl-dsi.c
index 691039aba87f..b19cf038025d 100644
--- a/drivers/gpu/drm/bridge/nwl-dsi.c
+++ b/drivers/gpu/drm/bridge/nwl-dsi.c
@@ -9,7 +9,9 @@
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/clk.h>
+#include <linux/component.h>
#include <linux/irq.h>
+#include <linux/firmware/imx/sci.h>
#include <linux/math64.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
@@ -22,6 +24,7 @@
#include <linux/sys_soc.h>
#include <linux/time64.h>
+#include <drm/drm_atomic_helper.h>
#include <drm/drm_atomic_state_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_mipi_dsi.h>
@@ -29,6 +32,8 @@
#include <drm/drm_panel.h>
#include <drm/drm_print.h>
+#include <dt-bindings/firmware/imx/rsrc.h>
+
#include <video/mipi_display.h>
#include "nwl-dsi.h"
@@ -41,13 +46,54 @@
#define NWL_DSI_MIPI_FIFO_TIMEOUT msecs_to_jiffies(500)
+/* Maximum Video PLL frequency: 1.2GHz */
+#define MAX_PLL_FREQ 1200000000
+
+#define MBPS(x) ((x) * 1000000)
+#define MIN_PHY_RATE MBPS(24)
+#define MAX_PHY_RATE MBPS(30)
+
+#define DC_ID(x) IMX_SC_R_DC_ ## x
+#define MIPI_ID(x) IMX_SC_R_MIPI_ ## x
+#define SYNC_CTRL(x) IMX_SC_C_SYNC_CTRL ## x
+#define PXL_VLD(x) IMX_SC_C_PXL_LINK_MST ## x ## _VLD
+#define PXL_ADDR(x) IMX_SC_C_PXL_LINK_MST ## x ## _ADDR
+
+#define IMX8ULP_DSI_CM_MASK BIT(1)
+#define IMX8ULP_DSI_CM_NORMAL BIT(1)
+
+/* Possible valid PHY reference clock rates*/
+static u32 phyref_rates[] = {
+ 27000000,
+ 25000000,
+ 24000000,
+};
+
+/*
+ * TODO: find a better way to access imx_crtc_state
+ */
+struct imx_crtc_state {
+ struct drm_crtc_state base;
+ u32 bus_format;
+ u32 bus_flags;
+ int di_hsync_pin;
+ int di_vsync_pin;
+};
+
+static inline struct imx_crtc_state *to_imx_crtc_state(struct drm_crtc_state *s)
+{
+ return container_of(s, struct imx_crtc_state, base);
+}
+
enum transfer_direction {
DSI_PACKET_SEND,
DSI_PACKET_RECEIVE,
};
-#define NWL_DSI_ENDPOINT_LCDIF 0
-#define NWL_DSI_ENDPOINT_DCSS 1
+#define NWL_DSI_ENDPOINT_LCDIF 0
+#define NWL_DSI_ENDPOINT_DCSS 1
+#define NWL_DSI_ENDPOINT_DCNANO 0
+#define NWL_DSI_ENDPOINT_EPDC 1
struct nwl_dsi_transfer {
const struct mipi_dsi_msg *msg;
@@ -63,7 +109,21 @@ struct nwl_dsi_transfer {
size_t rx_len; /* in bytes */
};
+struct mode_config {
+ int clock;
+ int crtc_clock;
+ unsigned int lanes;
+ unsigned long bitclock;
+ unsigned long phy_rates[3];
+ unsigned long pll_rates[3];
+ int phy_rate_idx;
+ struct list_head list;
+};
+
+struct nwl_dsi_platform_data;
+
struct nwl_dsi {
+ struct drm_encoder encoder;
struct drm_bridge bridge;
struct mipi_dsi_host dsi_host;
struct drm_bridge *panel_bridge;
@@ -71,8 +131,11 @@ struct nwl_dsi {
struct phy *phy;
union phy_configure_opts phy_cfg;
unsigned int quirks;
+ unsigned int instance;
+ const struct nwl_dsi_platform_data *pdata;
struct regmap *regmap;
+ struct regmap *csr;
int irq;
/*
* The DSI host controller needs this reset sequence according to NWL:
@@ -97,6 +160,9 @@ struct nwl_dsi {
struct clk *rx_esc_clk;
struct clk *tx_esc_clk;
struct clk *core_clk;
+ struct clk *bypass_clk;
+ struct clk *pixel_clk;
+ struct clk *pll_clk;
/*
* hardware bug: the i.MX8MQ needs this clock on during reset
* even when not using LCDIF.
@@ -111,6 +177,9 @@ struct nwl_dsi {
int error;
struct nwl_dsi_transfer *xfer;
+ struct list_head valid_modes;
+ u32 clk_drop_lvl;
+ bool use_dcss;
};
static const struct regmap_config nwl_dsi_regmap_config = {
@@ -121,6 +190,34 @@ static const struct regmap_config nwl_dsi_regmap_config = {
.name = DRV_NAME,
};
+typedef enum {
+ NWL_DSI_CORE_CLK = BIT(1),
+ NWL_DSI_LCDIF_CLK = BIT(2),
+ NWL_DSI_BYPASS_CLK = BIT(3),
+ NWL_DSI_PIXEL_CLK = BIT(4),
+} nwl_dsi_clks;
+
+struct nwl_dsi_platform_data {
+ int (*pclk_reset)(struct nwl_dsi *dsi, bool reset);
+ int (*mipi_reset)(struct nwl_dsi *dsi, bool reset);
+ int (*dpi_reset)(struct nwl_dsi *dsi, bool reset);
+ nwl_dsi_clks clks;
+ u32 reg_tx_ulps;
+ u32 reg_pxl2dpi;
+ u32 reg_cm;
+ u32 max_instances;
+ u32 tx_clk_rate;
+ u32 rx_clk_rate;
+ bool mux_present;
+ bool shared_phy;
+ u32 bit_bta_timeout;
+ u32 bit_hs_tx_timeout;
+ bool use_lcdif_or_dcss;
+ bool use_dcnano_or_epdc;
+ bool rx_clk_quirk; /* enable rx_esc clock to access registers */
+};
+
+
static inline struct nwl_dsi *bridge_to_dsi(struct drm_bridge *bridge)
{
return container_of(bridge, struct nwl_dsi, bridge);
@@ -221,13 +318,15 @@ static int nwl_dsi_config_host(struct nwl_dsi *dsi)
DRM_DEV_DEBUG_DRIVER(dsi->dev, "DSI Lanes %d\n", dsi->lanes);
nwl_dsi_write(dsi, NWL_DSI_CFG_NUM_LANES, dsi->lanes - 1);
- if (dsi->dsi_mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) {
+ if (dsi->dsi_mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS)
nwl_dsi_write(dsi, NWL_DSI_CFG_NONCONTINUOUS_CLK, 0x01);
- nwl_dsi_write(dsi, NWL_DSI_CFG_AUTOINSERT_EOTP, 0x01);
- } else {
+ else
nwl_dsi_write(dsi, NWL_DSI_CFG_NONCONTINUOUS_CLK, 0x00);
+
+ if (dsi->dsi_mode_flags & MIPI_DSI_MODE_NO_EOT_PACKET)
nwl_dsi_write(dsi, NWL_DSI_CFG_AUTOINSERT_EOTP, 0x00);
- }
+ else
+ nwl_dsi_write(dsi, NWL_DSI_CFG_AUTOINSERT_EOTP, 0x01);
/* values in byte clock cycles */
cycles = ui2bc(cfg->clk_pre);
@@ -261,6 +360,7 @@ static int nwl_dsi_config_dpi(struct nwl_dsi *dsi)
bool burst_mode;
int hfront_porch, hback_porch, vfront_porch, vback_porch;
int hsync_len, vsync_len;
+ int hfp, hbp, hsa;
hfront_porch = dsi->mode.hsync_start - dsi->mode.hdisplay;
hsync_len = dsi->mode.hsync_end - dsi->mode.hsync_start;
@@ -314,9 +414,35 @@ static int nwl_dsi_config_dpi(struct nwl_dsi *dsi)
dsi->mode.hdisplay);
}
- nwl_dsi_write(dsi, NWL_DSI_HFP, hfront_porch);
- nwl_dsi_write(dsi, NWL_DSI_HBP, hback_porch);
- nwl_dsi_write(dsi, NWL_DSI_HSA, hsync_len);
+ if (of_device_is_compatible(dsi->panel_bridge->of_node,
+ "raydium,rm68200") ||
+ of_device_is_compatible(dsi->panel_bridge->of_node,
+ "rocktech,hx8394f")) {
+ int bytes = mipi_dsi_pixel_format_to_bpp(dsi->format) >> 3;
+
+ /*
+ * FIXME: This is a workaround for display shift
+ * of the RM68200 and HX8394F panels. It is based
+ * on previous knowledge got from support task for
+ * external DSI bridges by turning the hfp/hbp/hsa
+ * to be in bytes and substracting certain magic values.
+ * Furthermore, rounding hsa up to 2 is needed.
+ * This can be fixed as soon as formulas to
+ * determine the settings are available.
+ */
+ hfp = bytes * hfront_porch - 12;
+ hbp = bytes * hback_porch - 10;
+ hsa = bytes * hsync_len - 10;
+ hsa = roundup(hsa, 2);
+ } else {
+ hfp = hfront_porch;
+ hbp = hback_porch;
+ hsa = hsync_len;
+ }
+
+ nwl_dsi_write(dsi, NWL_DSI_HFP, hfp);
+ nwl_dsi_write(dsi, NWL_DSI_HBP, hbp);
+ nwl_dsi_write(dsi, NWL_DSI_HSA, hsa);
nwl_dsi_write(dsi, NWL_DSI_ENABLE_MULT_PKTS, 0x0);
nwl_dsi_write(dsi, NWL_DSI_BLLP_MODE, 0x1);
@@ -341,7 +467,7 @@ static int nwl_dsi_init_interrupts(struct nwl_dsi *dsi)
irq_enable = ~(u32)(NWL_DSI_TX_PKT_DONE_MASK |
NWL_DSI_RX_PKT_HDR_RCVD_MASK |
NWL_DSI_TX_FIFO_OVFLW_MASK |
- NWL_DSI_HS_TX_TIMEOUT_MASK);
+ dsi->pdata->bit_hs_tx_timeout);
nwl_dsi_write(dsi, NWL_DSI_IRQ_MASK, irq_enable);
@@ -367,6 +493,18 @@ static int nwl_dsi_host_attach(struct mipi_dsi_host *dsi_host,
return 0;
}
+static int nwl_dsi_host_detach(struct mipi_dsi_host *dsi_host,
+ struct mipi_dsi_device *device)
+{
+ struct nwl_dsi *dsi = container_of(dsi_host, struct nwl_dsi, dsi_host);
+
+ dsi->lanes = 0;
+ dsi->format = 0;
+ dsi->dsi_mode_flags = 0;
+
+ return 0;
+}
+
static bool nwl_dsi_read_packet(struct nwl_dsi *dsi, u32 status)
{
struct device *dev = dsi->dev;
@@ -630,6 +768,7 @@ static ssize_t nwl_dsi_host_transfer(struct mipi_dsi_host *dsi_host,
static const struct mipi_dsi_host_ops nwl_dsi_host_ops = {
.attach = nwl_dsi_host_attach,
+ .detach = nwl_dsi_host_detach,
.transfer = nwl_dsi_host_transfer,
};
@@ -643,7 +782,7 @@ static irqreturn_t nwl_dsi_irq_handler(int irq, void *data)
if (irq_status & NWL_DSI_TX_FIFO_OVFLW)
DRM_DEV_ERROR_RATELIMITED(dsi->dev, "tx fifo overflow\n");
- if (irq_status & NWL_DSI_HS_TX_TIMEOUT)
+ if (irq_status & dsi->pdata->bit_hs_tx_timeout)
DRM_DEV_ERROR_RATELIMITED(dsi->dev, "HS tx timeout\n");
if (irq_status & NWL_DSI_TX_PKT_DONE ||
@@ -654,7 +793,7 @@ static irqreturn_t nwl_dsi_irq_handler(int irq, void *data)
return IRQ_HANDLED;
}
-static int nwl_dsi_mode_set(struct nwl_dsi *dsi)
+static int nwl_dsi_enable(struct nwl_dsi *dsi)
{
struct device *dev = dsi->dev;
union phy_configure_opts *phy_cfg = &dsi->phy_cfg;
@@ -732,45 +871,261 @@ static int nwl_dsi_disable(struct nwl_dsi *dsi)
/* Disabling the clock before the phy breaks enabling dsi again */
clk_disable_unprepare(dsi->tx_esc_clk);
+ /* Disable rx_esc clock as registers are not accessed any more. */
+ if (dsi->pdata->rx_clk_quirk)
+ clk_disable_unprepare(dsi->rx_esc_clk);
+
return 0;
}
static void
-nwl_dsi_bridge_atomic_disable(struct drm_bridge *bridge,
- struct drm_bridge_state *old_bridge_state)
+nwl_dsi_bridge_atomic_post_disable(struct drm_bridge *bridge,
+ struct drm_bridge_state *old_bridge_state)
{
struct nwl_dsi *dsi = bridge_to_dsi(bridge);
int ret;
+ /*
+ * Call panel_bridge's post_disable() callback(if any) so that
+ * it may send any MIPI DSI command before this MIPI DSI controller
+ * and it's PHY are disabled.
+ */
+ if (dsi->panel_bridge->funcs->post_disable)
+ dsi->panel_bridge->funcs->post_disable(dsi->panel_bridge);
+
nwl_dsi_disable(dsi);
- ret = reset_control_assert(dsi->rst_dpi);
+ ret = dsi->pdata->dpi_reset(dsi, true);
if (ret < 0) {
DRM_DEV_ERROR(dsi->dev, "Failed to assert DPI: %d\n", ret);
return;
}
- ret = reset_control_assert(dsi->rst_byte);
+ ret = dsi->pdata->mipi_reset(dsi, true);
if (ret < 0) {
- DRM_DEV_ERROR(dsi->dev, "Failed to assert ESC: %d\n", ret);
+ DRM_DEV_ERROR(dsi->dev, "Failed to assert DSI: %d\n", ret);
return;
}
- ret = reset_control_assert(dsi->rst_esc);
- if (ret < 0) {
- DRM_DEV_ERROR(dsi->dev, "Failed to assert BYTE: %d\n", ret);
- return;
- }
- ret = reset_control_assert(dsi->rst_pclk);
+ ret = dsi->pdata->pclk_reset(dsi, true);
if (ret < 0) {
DRM_DEV_ERROR(dsi->dev, "Failed to assert PCLK: %d\n", ret);
return;
}
- clk_disable_unprepare(dsi->core_clk);
- clk_disable_unprepare(dsi->lcdif_clk);
+ if (dsi->core_clk)
+ clk_disable_unprepare(dsi->core_clk);
+ if (dsi->bypass_clk)
+ clk_disable_unprepare(dsi->bypass_clk);
+ if (dsi->pixel_clk)
+ clk_disable_unprepare(dsi->pixel_clk);
+ if (dsi->lcdif_clk)
+ clk_disable_unprepare(dsi->lcdif_clk);
pm_runtime_put(dsi->dev);
}
+static unsigned long nwl_dsi_get_bit_clock(struct nwl_dsi *dsi,
+ unsigned long pixclock, u32 lanes)
+{
+ int bpp;
+
+ if (lanes < 1 || lanes > 4)
+ return 0;
+
+ bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
+
+ return (pixclock * bpp) / lanes;
+}
+
+/*
+ * Utility function to calculate least commom multiple, using an improved
+ * version of the Euclidean algorithm for greatest common factor.
+ */
+static unsigned long nwl_dsi_get_lcm(unsigned long a, unsigned long b)
+{
+ u32 gcf = 0; /* greatest common factor */
+ unsigned long tmp_a = a;
+ unsigned long tmp_b = b;
+
+ if (!a || !b)
+ return 0;
+
+ while (tmp_a % tmp_b) {
+ gcf = tmp_a % tmp_b;
+ tmp_a = tmp_b;
+ tmp_b = gcf;
+ }
+
+ if (!gcf)
+ return a;
+
+ return ((unsigned long long)a * b) / gcf;
+}
+
+/*
+ * This function tries to adjust the crtc_clock for a DSI device in such a way
+ * that the video pll will be able to satisfy both Display Controller pixel
+ * clock (feeding out DPI interface) and our input phy_ref clock.
+ * Also, the DC pixel clock must be lower than the actual clock in order to
+ * have enough blanking space to send DSI commands, if the device is a panel.
+ */
+static void nwl_dsi_setup_pll_config(struct mode_config *config, u32 lvl)
+{
+ unsigned long pll_rate;
+ int div;
+ size_t i, num_rates = ARRAY_SIZE(config->phy_rates);
+
+ config->crtc_clock = 0;
+
+ for (i = 0; i < num_rates; i++) {
+ int crtc_clock;
+
+ if (!config->phy_rates[i])
+ break;
+ /*
+ * First, we need to check if phy_ref can actually be obtained
+ * from pixel clock. To do this, we check their lowest common
+ * multiple, which has to be in PLL range.
+ */
+ pll_rate = nwl_dsi_get_lcm(config->clock, config->phy_rates[i]);
+ if (pll_rate > MAX_PLL_FREQ) {
+ /* Drop pll_rate to a realistic value */
+ while (pll_rate > MAX_PLL_FREQ)
+ pll_rate >>= 1;
+ /* Make sure pll_rate can provide phy_ref rate */
+ div = DIV_ROUND_UP(pll_rate, config->phy_rates[i]);
+ pll_rate = config->phy_rates[i] * div;
+ } else {
+ /*
+ * Increase the pll rate to highest possible rate for
+ * better accuracy.
+ */
+ while (pll_rate <= MAX_PLL_FREQ)
+ pll_rate <<= 1;
+ pll_rate >>= 1;
+ }
+
+ /*
+ * Next, we need to tweak the pll_rate to a value that can also
+ * satisfy the crtc_clock.
+ */
+ div = DIV_ROUND_CLOSEST(pll_rate, config->clock);
+ if (lvl)
+ pll_rate -= config->phy_rates[i] * lvl;
+ crtc_clock = pll_rate / div;
+ config->pll_rates[i] = pll_rate;
+
+ /*
+ * Pick a crtc_clock which is closest to pixel clock.
+ * Also, make sure that the pixel clock is a multiply of
+ * 50Hz.
+ */
+ if (!(crtc_clock % 50) &&
+ abs(config->clock - crtc_clock) <
+ abs(config->clock - config->crtc_clock)) {
+ config->crtc_clock = crtc_clock;
+ config->phy_rate_idx = i;
+ }
+ }
+}
+
+
+/*
+ * This function will try the required phy speed for current mode
+ * If the phy speed can be achieved, the phy will save the speed
+ * configuration
+ */
+static struct mode_config *nwl_dsi_mode_probe(struct nwl_dsi *dsi,
+ const struct drm_display_mode *mode)
+{
+ struct device *dev = dsi->dev;
+ struct mode_config *config;
+ union phy_configure_opts phy_opts;
+ unsigned long clock = mode->clock * 1000;
+ unsigned long bit_clk = 0;
+ unsigned long phy_rates[3] = {0};
+ int match_rates = 0;
+ u32 lanes = dsi->lanes;
+ size_t i = 0, num_rates = ARRAY_SIZE(phyref_rates);
+
+ list_for_each_entry(config, &dsi->valid_modes, list)
+ if (config->clock == clock)
+ return config;
+
+ phy_mipi_dphy_get_default_config(clock,
+ mipi_dsi_pixel_format_to_bpp(dsi->format),
+ lanes, &phy_opts.mipi_dphy);
+ phy_opts.mipi_dphy.lp_clk_rate = clk_get_rate(dsi->tx_esc_clk);
+
+ while (i < num_rates) {
+ int ret;
+
+ bit_clk = nwl_dsi_get_bit_clock(dsi, clock, lanes);
+
+ clk_set_rate(dsi->pll_clk, phyref_rates[i] * 32);
+ clk_set_rate(dsi->phy_ref_clk, phyref_rates[i]);
+ ret = phy_validate(dsi->phy, PHY_MODE_MIPI_DPHY, 0, &phy_opts);
+
+ /* Pick the non-failing rate, and search for more */
+ if (!ret) {
+ phy_rates[match_rates++] = phyref_rates[i++];
+ continue;
+ }
+
+ if (match_rates)
+ break;
+
+ /* Reached the end of phyref_rates, try another lane config */
+ if ((i++ == num_rates - 1) && (--lanes > 2)) {
+ i = 0;
+ continue;
+ }
+ }
+
+ /*
+ * Try swinging between min and max pll rates and see what rate (in terms
+ * of kHz) we can custom use to get the required bit-clock.
+ */
+ if (!match_rates) {
+ int min_div, max_div;
+ int bit_clk_khz;
+
+ lanes = dsi->lanes;
+ bit_clk = nwl_dsi_get_bit_clock(dsi, clock, lanes);
+
+ min_div = DIV_ROUND_UP(bit_clk, MAX_PHY_RATE);
+ max_div = DIV_ROUND_DOWN_ULL(bit_clk, MIN_PHY_RATE);
+ bit_clk_khz = bit_clk / 1000;
+
+ for (i = max_div; i > min_div; i--) {
+ if (!(bit_clk_khz % i)) {
+ phy_rates[0] = bit_clk / i;
+ match_rates = 1;
+ break;
+ }
+ }
+ }
+
+ if (!match_rates) {
+ DRM_DEV_DEBUG_DRIVER(dev,
+ "Cannot setup PHY for mode: %ux%u @%d kHz\n",
+ mode->hdisplay,
+ mode->vdisplay,
+ mode->clock);
+
+ return NULL;
+ }
+
+ config = devm_kzalloc(dsi->dev, sizeof(struct mode_config), GFP_KERNEL);
+ config->clock = clock;
+ config->lanes = lanes;
+ config->bitclock = bit_clk;
+ memcpy(&config->phy_rates, &phy_rates, sizeof(phy_rates));
+ list_add(&config->list, &dsi->valid_modes);
+
+ return config;
+}
+
+
static int nwl_dsi_get_dphy_params(struct nwl_dsi *dsi,
const struct drm_display_mode *mode,
union phy_configure_opts *phy_opts)
@@ -804,14 +1159,29 @@ nwl_dsi_bridge_mode_valid(struct drm_bridge *bridge,
const struct drm_display_mode *mode)
{
struct nwl_dsi *dsi = bridge_to_dsi(bridge);
- int bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
+ struct mode_config *config;
+ unsigned long pll_rate;
+ int bit_rate;
- if (mode->clock * bpp > 15000000 * dsi->lanes)
+ bit_rate = nwl_dsi_get_bit_clock(dsi, mode->clock * 1000, dsi->lanes);
+
+ DRM_DEV_DEBUG_DRIVER(dsi->dev, "Validating mode:");
+ drm_mode_debug_printmodeline(mode);
+
+ if (bit_rate > MBPS(1500))
return MODE_CLOCK_HIGH;
- if (mode->clock * bpp < 80000 * dsi->lanes)
+ if (bit_rate < MBPS(80))
return MODE_CLOCK_LOW;
+ config = nwl_dsi_mode_probe(dsi, mode);
+ if (!config)
+ return MODE_NOCLOCK;
+
+ pll_rate = config->pll_rates[config->phy_rate_idx];
+ if (dsi->pll_clk && !pll_rate)
+ nwl_dsi_setup_pll_config(config, dsi->clk_drop_lvl);
+
return MODE_OK;
}
@@ -820,20 +1190,56 @@ static int nwl_dsi_bridge_atomic_check(struct drm_bridge *bridge,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
- struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode;
+ struct nwl_dsi *dsi = bridge_to_dsi(bridge);
+ struct drm_display_mode *adjusted = &crtc_state->adjusted_mode;
+ struct mode_config *config;
+ unsigned long pll_rate;
- /* At least LCDIF + NWL needs active high sync */
- adjusted_mode->flags |= (DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC);
- adjusted_mode->flags &= ~(DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC);
+ DRM_DEV_DEBUG_DRIVER(dsi->dev, "atomic check:\n");
+ drm_mode_debug_printmodeline(adjusted);
- /*
- * Do a full modeset if crtc_state->active is changed to be true.
- * This ensures our ->mode_set() is called to get the DSI controller
- * and the PHY ready to send DCS commands, when only the connector's
- * DPMS is brought out of "Off" status.
- */
- if (crtc_state->active_changed && crtc_state->active)
- crtc_state->mode_changed = true;
+ config = nwl_dsi_mode_probe(dsi, adjusted);
+ if (!config)
+ return -EINVAL;
+
+ DRM_DEV_DEBUG_DRIVER(dsi->dev, "lanes=%u, data_rate=%lu\n",
+ config->lanes, config->bitclock);
+ if (config->lanes < 2 || config->lanes > 4)
+ return -EINVAL;
+
+ /* Max data rate for this controller is 1.5Gbps */
+ if (config->bitclock > 1500000000)
+ return -EINVAL;
+
+ pll_rate = config->pll_rates[config->phy_rate_idx];
+ if (dsi->pll_clk && pll_rate) {
+ clk_set_rate(dsi->pll_clk, pll_rate);
+ DRM_DEV_DEBUG_DRIVER(dsi->dev,
+ "Video pll rate: %lu (actual: %lu)",
+ pll_rate, clk_get_rate(dsi->pll_clk));
+ }
+ /* Update the crtc_clock to be used by display controller */
+ if (config->crtc_clock)
+ adjusted->crtc_clock = config->crtc_clock / 1000;
+ else if (dsi->clk_drop_lvl) {
+ int div;
+ unsigned long phy_ref_rate;
+
+ phy_ref_rate = config->phy_rates[config->phy_rate_idx];
+ pll_rate = config->bitclock;
+ div = DIV_ROUND_CLOSEST(pll_rate, config->clock);
+ pll_rate -= phy_ref_rate * dsi->clk_drop_lvl;
+ adjusted->crtc_clock = (pll_rate / div) / 1000;
+ }
+
+ if (!dsi->use_dcss && !dsi->pdata->use_dcnano_or_epdc) {
+ /* At least LCDIF + NWL needs active high sync */
+ adjusted->flags |= (DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC);
+ adjusted->flags &= ~(DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC);
+ } else {
+ adjusted->flags &= ~(DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC);
+ adjusted->flags |= (DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC);
+ }
return 0;
}
@@ -847,54 +1253,108 @@ nwl_dsi_bridge_mode_set(struct drm_bridge *bridge,
struct device *dev = dsi->dev;
union phy_configure_opts new_cfg;
unsigned long phy_ref_rate;
+ struct mode_config *config;
int ret;
+ DRM_DEV_DEBUG_DRIVER(dsi->dev, "Setting mode:\n");
+ drm_mode_debug_printmodeline(adjusted_mode);
+
+ config = nwl_dsi_mode_probe(dsi, adjusted_mode);
+ /* New mode? This should NOT happen */
+ if (!config) {
+ DRM_DEV_ERROR(dsi->dev, "Unsupported mode provided:\n");
+ drm_mode_debug_printmodeline(adjusted_mode);
+ return;
+ }
+
+ /*
+ * If bypass and pixel clocks are present, we need to set their rates
+ * now.
+ */
+ if (dsi->bypass_clk)
+ clk_set_rate(dsi->bypass_clk, adjusted_mode->crtc_clock * 1000);
+ if (dsi->pixel_clk)
+ clk_set_rate(dsi->pixel_clk, adjusted_mode->crtc_clock * 1000);
+
+ memcpy(&dsi->mode, adjusted_mode, sizeof(dsi->mode));
+
+ phy_ref_rate = config->phy_rates[config->phy_rate_idx];
+ clk_set_rate(dsi->phy_ref_clk, phy_ref_rate);
ret = nwl_dsi_get_dphy_params(dsi, adjusted_mode, &new_cfg);
if (ret < 0)
return;
- phy_ref_rate = clk_get_rate(dsi->phy_ref_clk);
- DRM_DEV_DEBUG_DRIVER(dev, "PHY at ref rate: %lu\n", phy_ref_rate);
+ DRM_DEV_DEBUG_DRIVER(dev,
+ "PHY at ref rate: %lu (actual: %lu)\n",
+ phy_ref_rate, clk_get_rate(dsi->phy_ref_clk));
+
/* Save the new desired phy config */
memcpy(&dsi->phy_cfg, &new_cfg, sizeof(new_cfg));
+}
- memcpy(&dsi->mode, adjusted_mode, sizeof(dsi->mode));
- drm_mode_debug_printmodeline(adjusted_mode);
+static void
+nwl_dsi_bridge_atomic_pre_enable(struct drm_bridge *bridge,
+ struct drm_bridge_state *old_bridge_state)
+{
+ struct nwl_dsi *dsi = bridge_to_dsi(bridge);
+ int ret;
- if (pm_runtime_resume_and_get(dev) < 0)
+ if (pm_runtime_resume_and_get(dsi->dev) < 0)
return;
- if (clk_prepare_enable(dsi->lcdif_clk) < 0)
+ dsi->pdata->dpi_reset(dsi, true);
+ dsi->pdata->mipi_reset(dsi, true);
+ dsi->pdata->pclk_reset(dsi, true);
+
+ if (dsi->lcdif_clk && clk_prepare_enable(dsi->lcdif_clk) < 0)
goto runtime_put;
- if (clk_prepare_enable(dsi->core_clk) < 0)
+ if (dsi->core_clk && clk_prepare_enable(dsi->core_clk) < 0)
+ goto runtime_put;
+ if (dsi->bypass_clk && clk_prepare_enable(dsi->bypass_clk) < 0)
+ goto runtime_put;
+ if (dsi->pixel_clk && clk_prepare_enable(dsi->pixel_clk) < 0)
+ goto runtime_put;
+ /*
+ * Enable rx_esc clock for some platforms to access DSI host controller
+ * and PHY registers.
+ */
+ if (dsi->pdata->rx_clk_quirk && clk_prepare_enable(dsi->rx_esc_clk) < 0)
goto runtime_put;
+ /* Always use normal mode(full mode) for Type-4 display */
+ if (dsi->pdata->reg_cm)
+ regmap_update_bits(dsi->csr, dsi->pdata->reg_cm,
+ IMX8ULP_DSI_CM_MASK, IMX8ULP_DSI_CM_NORMAL);
+
/* Step 1 from DSI reset-out instructions */
- ret = reset_control_deassert(dsi->rst_pclk);
+ ret = dsi->pdata->pclk_reset(dsi, false);
if (ret < 0) {
- DRM_DEV_ERROR(dev, "Failed to deassert PCLK: %d\n", ret);
+ DRM_DEV_ERROR(dsi->dev, "Failed to deassert PCLK: %d\n", ret);
goto runtime_put;
}
/* Step 2 from DSI reset-out instructions */
- nwl_dsi_mode_set(dsi);
+ nwl_dsi_enable(dsi);
/* Step 3 from DSI reset-out instructions */
- ret = reset_control_deassert(dsi->rst_esc);
+ ret = dsi->pdata->mipi_reset(dsi, false);
if (ret < 0) {
- DRM_DEV_ERROR(dev, "Failed to deassert ESC: %d\n", ret);
- goto runtime_put;
- }
- ret = reset_control_deassert(dsi->rst_byte);
- if (ret < 0) {
- DRM_DEV_ERROR(dev, "Failed to deassert BYTE: %d\n", ret);
+ DRM_DEV_ERROR(dsi->dev, "Failed to deassert DSI: %d\n", ret);
goto runtime_put;
}
+ /*
+ * We need to force call enable for the panel here, in order to
+ * make the panel initialization execute before our call to
+ * bridge_enable, where we will enable the DPI and start streaming
+ * pixels on the data lanes.
+ */
+ drm_bridge_chain_enable(dsi->panel_bridge);
+
return;
runtime_put:
- pm_runtime_put_sync(dev);
+ pm_runtime_put_sync(dsi->dev);
}
static void
@@ -905,7 +1365,7 @@ nwl_dsi_bridge_atomic_enable(struct drm_bridge *bridge,
int ret;
/* Step 5 from DSI reset-out instructions */
- ret = reset_control_deassert(dsi->rst_dpi);
+ ret = dsi->pdata->dpi_reset(dsi, false);
if (ret < 0)
DRM_DEV_ERROR(dsi->dev, "Failed to deassert DPI: %d\n", ret);
}
@@ -916,6 +1376,7 @@ static int nwl_dsi_bridge_attach(struct drm_bridge *bridge,
struct nwl_dsi *dsi = bridge_to_dsi(bridge);
struct drm_bridge *panel_bridge;
struct drm_panel *panel;
+ struct clk *phy_parent;
int ret;
ret = drm_of_find_panel_or_bridge(dsi->dev->of_node, 1, 0, &panel,
@@ -924,7 +1385,8 @@ static int nwl_dsi_bridge_attach(struct drm_bridge *bridge,
return ret;
if (panel) {
- panel_bridge = drm_panel_bridge_add(panel);
+ panel_bridge = drm_panel_bridge_add_typed(panel,
+ DRM_MODE_CONNECTOR_DSI);
if (IS_ERR(panel_bridge))
return PTR_ERR(panel_bridge);
}
@@ -933,6 +1395,27 @@ static int nwl_dsi_bridge_attach(struct drm_bridge *bridge,
if (!dsi->panel_bridge)
return -EPROBE_DEFER;
+ phy_parent = devm_clk_get(dsi->dev, "phy_parent");
+ if (!IS_ERR_OR_NULL(phy_parent)) {
+ ret = clk_set_parent(dsi->phy_ref_clk, phy_parent);
+ ret |= clk_set_parent(dsi->tx_esc_clk, phy_parent);
+ ret |= clk_set_parent(dsi->rx_esc_clk, phy_parent);
+
+ if (ret) {
+ dev_err(dsi->dev,
+ "Error re-parenting phy/tx/rx clocks: %d",
+ ret);
+
+ return ret;
+ }
+
+ if (dsi->pdata->tx_clk_rate)
+ clk_set_rate(dsi->tx_esc_clk, dsi->pdata->tx_clk_rate);
+
+ if (dsi->pdata->rx_clk_rate)
+ clk_set_rate(dsi->rx_esc_clk, dsi->pdata->rx_clk_rate);
+ }
+
return drm_bridge_attach(bridge->encoder, dsi->panel_bridge, bridge,
flags);
}
@@ -982,8 +1465,9 @@ static const struct drm_bridge_funcs nwl_dsi_bridge_funcs = {
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
.atomic_reset = drm_atomic_helper_bridge_reset,
.atomic_check = nwl_dsi_bridge_atomic_check,
+ .atomic_pre_enable = nwl_dsi_bridge_atomic_pre_enable,
.atomic_enable = nwl_dsi_bridge_atomic_enable,
- .atomic_disable = nwl_dsi_bridge_atomic_disable,
+ .atomic_post_disable = nwl_dsi_bridge_atomic_post_disable,
.atomic_get_input_bus_fmts = nwl_bridge_atomic_get_input_bus_fmts,
.mode_set = nwl_dsi_bridge_mode_set,
.mode_valid = nwl_dsi_bridge_mode_valid,
@@ -991,12 +1475,22 @@ static const struct drm_bridge_funcs nwl_dsi_bridge_funcs = {
.detach = nwl_dsi_bridge_detach,
};
+static void nwl_dsi_encoder_destroy(struct drm_encoder *encoder)
+{
+ drm_encoder_cleanup(encoder);
+}
+
+static const struct drm_encoder_funcs nwl_dsi_encoder_funcs = {
+ .destroy = nwl_dsi_encoder_destroy,
+};
+
static int nwl_dsi_parse_dt(struct nwl_dsi *dsi)
{
struct platform_device *pdev = to_platform_device(dsi->dev);
+ struct device_node *np = dsi->dev->of_node;
struct clk *clk;
void __iomem *base;
- int ret;
+ int ret, id;
dsi->phy = devm_phy_get(dsi->dev, "dphy");
if (IS_ERR(dsi->phy)) {
@@ -1006,23 +1500,64 @@ static int nwl_dsi_parse_dt(struct nwl_dsi *dsi)
return ret;
}
- clk = devm_clk_get(dsi->dev, "lcdif");
- if (IS_ERR(clk)) {
- ret = PTR_ERR(clk);
- DRM_DEV_ERROR(dsi->dev, "Failed to get lcdif clock: %d\n",
- ret);
- return ret;
+ id = of_alias_get_id(np, "mipi_dsi");
+ if (id > 0) {
+ if (id > dsi->pdata->max_instances - 1) {
+ dev_err(dsi->dev,
+ "Too many instances! (cur: %d, max: %d)\n",
+ id, dsi->pdata->max_instances);
+ return -ENODEV;
+ }
+ dsi->instance = id;
}
- dsi->lcdif_clk = clk;
- clk = devm_clk_get(dsi->dev, "core");
- if (IS_ERR(clk)) {
- ret = PTR_ERR(clk);
- DRM_DEV_ERROR(dsi->dev, "Failed to get core clock: %d\n",
- ret);
- return ret;
+ if (dsi->pdata->clks & NWL_DSI_LCDIF_CLK) {
+ clk = devm_clk_get(dsi->dev, "lcdif");
+ if (IS_ERR(clk)) {
+ ret = PTR_ERR(clk);
+ DRM_DEV_ERROR(dsi->dev,
+ "Failed to get lcdif clock: %d\n",
+ ret);
+ return ret;
+ }
+ dsi->lcdif_clk = clk;
+ }
+
+ if (dsi->pdata->clks & NWL_DSI_CORE_CLK) {
+ clk = devm_clk_get(dsi->dev, "core");
+ if (IS_ERR(clk)) {
+ ret = PTR_ERR(clk);
+ DRM_DEV_ERROR(dsi->dev,
+ "Failed to get core clock: %d\n",
+ ret);
+ return ret;
+ }
+ dsi->core_clk = clk;
+ }
+
+ if (dsi->pdata->clks & NWL_DSI_BYPASS_CLK) {
+ clk = devm_clk_get(dsi->dev, "bypass");
+ if (IS_ERR(clk)) {
+ ret = PTR_ERR(clk);
+ DRM_DEV_ERROR(dsi->dev,
+ "Failed to get bypass clock: %d\n",
+ ret);
+ return ret;
+ }
+ dsi->bypass_clk = clk;
+ }
+
+ if (dsi->pdata->clks & NWL_DSI_PIXEL_CLK) {
+ clk = devm_clk_get(dsi->dev, "pixel");
+ if (IS_ERR(clk)) {
+ ret = PTR_ERR(clk);
+ DRM_DEV_ERROR(dsi->dev,
+ "Failed to get pixel clock: %d\n",
+ ret);
+ return ret;
+ }
+ dsi->pixel_clk = clk;
}
- dsi->core_clk = clk;
clk = devm_clk_get(dsi->dev, "phy_ref");
if (IS_ERR(clk)) {
@@ -1051,12 +1586,21 @@ static int nwl_dsi_parse_dt(struct nwl_dsi *dsi)
}
dsi->tx_esc_clk = clk;
- dsi->mux = devm_mux_control_get(dsi->dev, NULL);
- if (IS_ERR(dsi->mux)) {
- ret = PTR_ERR(dsi->mux);
- if (ret != -EPROBE_DEFER)
- DRM_DEV_ERROR(dsi->dev, "Failed to get mux: %d\n", ret);
- return ret;
+ /* The video_pll clock is optional */
+ clk = devm_clk_get(dsi->dev, "video_pll");
+ if (!IS_ERR(clk))
+ dsi->pll_clk = clk;
+
+
+ if (dsi->pdata->mux_present) {
+ dsi->mux = devm_mux_control_get(dsi->dev, NULL);
+ if (IS_ERR(dsi->mux)) {
+ ret = PTR_ERR(dsi->mux);
+ if (ret != -EPROBE_DEFER)
+ DRM_DEV_ERROR(dsi->dev,
+ "Failed to get mux: %d\n", ret);
+ return ret;
+ }
}
base = devm_platform_ioremap_resource(pdev, 0);
@@ -1072,6 +1616,18 @@ static int nwl_dsi_parse_dt(struct nwl_dsi *dsi)
return ret;
}
+ /* For these three regs, we need a mapping to MIPI-DSI CSR */
+ if (dsi->pdata->reg_tx_ulps || dsi->pdata->reg_pxl2dpi ||
+ dsi->pdata->reg_cm) {
+ dsi->csr = syscon_regmap_lookup_by_phandle(np, "csr");
+ if (IS_ERR(dsi->csr)) {
+ ret = PTR_ERR(dsi->csr);
+ dev_err(dsi->dev,
+ "Failed to get CSR regmap: %d\n", ret);
+ return ret;
+ }
+ }
+
dsi->irq = platform_get_irq(pdev, 0);
if (dsi->irq < 0) {
DRM_DEV_ERROR(dsi->dev, "Failed to get device IRQ: %d\n",
@@ -1079,60 +1635,100 @@ static int nwl_dsi_parse_dt(struct nwl_dsi *dsi)
return dsi->irq;
}
- dsi->rst_pclk = devm_reset_control_get_exclusive(dsi->dev, "pclk");
+ dsi->rst_pclk = devm_reset_control_get_optional_exclusive(dsi->dev,
+ "pclk");
if (IS_ERR(dsi->rst_pclk)) {
DRM_DEV_ERROR(dsi->dev, "Failed to get pclk reset: %ld\n",
PTR_ERR(dsi->rst_pclk));
return PTR_ERR(dsi->rst_pclk);
}
- dsi->rst_byte = devm_reset_control_get_exclusive(dsi->dev, "byte");
+ dsi->rst_byte = devm_reset_control_get_optional_exclusive(dsi->dev,
+ "byte");
if (IS_ERR(dsi->rst_byte)) {
DRM_DEV_ERROR(dsi->dev, "Failed to get byte reset: %ld\n",
PTR_ERR(dsi->rst_byte));
return PTR_ERR(dsi->rst_byte);
}
- dsi->rst_esc = devm_reset_control_get_exclusive(dsi->dev, "esc");
+ dsi->rst_esc = devm_reset_control_get_optional_exclusive(dsi->dev,
+ "esc");
if (IS_ERR(dsi->rst_esc)) {
DRM_DEV_ERROR(dsi->dev, "Failed to get esc reset: %ld\n",
PTR_ERR(dsi->rst_esc));
return PTR_ERR(dsi->rst_esc);
}
- dsi->rst_dpi = devm_reset_control_get_exclusive(dsi->dev, "dpi");
+ dsi->rst_dpi = devm_reset_control_get_optional_exclusive(dsi->dev,
+ "dpi");
if (IS_ERR(dsi->rst_dpi)) {
DRM_DEV_ERROR(dsi->dev, "Failed to get dpi reset: %ld\n",
PTR_ERR(dsi->rst_dpi));
return PTR_ERR(dsi->rst_dpi);
}
+
+ of_property_read_u32(np, "fsl,clock-drop-level", &dsi->clk_drop_lvl);
+
+ INIT_LIST_HEAD(&dsi->valid_modes);
+
return 0;
}
static int nwl_dsi_select_input(struct nwl_dsi *dsi)
{
struct device_node *remote;
- u32 use_dcss = 1;
+ u32 use_dcss_or_epdc = 1;
int ret;
- remote = of_graph_get_remote_node(dsi->dev->of_node, 0,
- NWL_DSI_ENDPOINT_LCDIF);
- if (remote) {
- use_dcss = 0;
- } else {
+ /* If there is no mux, nothing to do here */
+ if (!dsi->pdata->mux_present)
+ return 0;
+
+ if (dsi->pdata->use_lcdif_or_dcss) {
remote = of_graph_get_remote_node(dsi->dev->of_node, 0,
- NWL_DSI_ENDPOINT_DCSS);
- if (!remote) {
- DRM_DEV_ERROR(dsi->dev,
- "No valid input endpoint found\n");
- return -EINVAL;
+ NWL_DSI_ENDPOINT_LCDIF);
+ if (remote) {
+ use_dcss_or_epdc = 0;
+ } else {
+ remote = of_graph_get_remote_node(dsi->dev->of_node, 0,
+ NWL_DSI_ENDPOINT_DCSS);
+ if (!remote) {
+ DRM_DEV_ERROR(dsi->dev,
+ "No valid input endpoint found\n");
+ return -EINVAL;
+ }
}
+
+ DRM_DEV_INFO(dsi->dev, "Using %s as input source\n",
+ (use_dcss_or_epdc) ? "DCSS" : "LCDIF");
+ } else if (dsi->pdata->use_dcnano_or_epdc) {
+ remote = of_graph_get_remote_node(dsi->dev->of_node, 0,
+ NWL_DSI_ENDPOINT_DCNANO);
+ if (remote) {
+ use_dcss_or_epdc = 0;
+ } else {
+ remote = of_graph_get_remote_node(dsi->dev->of_node, 0,
+ NWL_DSI_ENDPOINT_EPDC);
+ if (!remote) {
+ DRM_DEV_ERROR(dsi->dev,
+ "No valid input endpoint found\n");
+ return -EINVAL;
+ }
+ }
+
+ DRM_DEV_INFO(dsi->dev, "Using %s as input source\n",
+ (use_dcss_or_epdc) ? "EPDC" : "DCNANO");
+ } else {
+ DRM_DEV_ERROR(dsi->dev, "No valid input endpoint found\n");
+ return -EINVAL;
}
- DRM_DEV_INFO(dsi->dev, "Using %s as input source\n",
- (use_dcss) ? "DCSS" : "LCDIF");
- ret = mux_control_try_select(dsi->mux, use_dcss);
+ ret = mux_control_try_select(dsi->mux, use_dcss_or_epdc);
if (ret < 0)
DRM_DEV_ERROR(dsi->dev, "Failed to select input: %d\n", ret);
of_node_put(remote);
+
+ if (of_device_is_compatible(dsi->dev->of_node, "fsl,imx8mq-nwl-dsi"))
+ dsi->use_dcss = use_dcss_or_epdc;
+
return ret;
}
@@ -1140,6 +1736,10 @@ static int nwl_dsi_deselect_input(struct nwl_dsi *dsi)
{
int ret;
+ /* If there is no mux, nothing to do here */
+ if (!dsi->pdata->mux_present)
+ return 0;
+
ret = mux_control_deselect(dsi->mux);
if (ret < 0)
DRM_DEV_ERROR(dsi->dev, "Failed to deselect input: %d\n", ret);
@@ -1147,12 +1747,213 @@ static int nwl_dsi_deselect_input(struct nwl_dsi *dsi)
return ret;
}
+static int imx8_common_dsi_pclk_reset(struct nwl_dsi *dsi, bool reset)
+{
+ int ret = 0;
+
+ if (dsi->rst_pclk) {
+ if (reset)
+ ret = reset_control_assert(dsi->rst_pclk);
+ else
+ ret = reset_control_deassert(dsi->rst_pclk);
+ }
+
+ return ret;
+
+}
+
+static int imx8_common_dsi_mipi_reset(struct nwl_dsi *dsi, bool reset)
+{
+ int ret = 0;
+
+ if (dsi->rst_esc) {
+ if (reset)
+ ret = reset_control_assert(dsi->rst_esc);
+ else
+ ret = reset_control_deassert(dsi->rst_esc);
+ }
+
+ if (dsi->rst_byte) {
+ if (reset)
+ ret = reset_control_assert(dsi->rst_byte);
+ else
+ ret = reset_control_deassert(dsi->rst_byte);
+ }
+
+ return ret;
+
+}
+
+static int imx8_common_dsi_dpi_reset(struct nwl_dsi *dsi, bool reset)
+{
+ int ret = 0;
+
+ if (dsi->rst_dpi) {
+ if (reset)
+ ret = reset_control_assert(dsi->rst_dpi);
+ else
+ ret = reset_control_deassert(dsi->rst_dpi);
+ }
+
+ return ret;
+
+}
+
+static int imx8q_dsi_pclk_reset(struct nwl_dsi *dsi, bool reset)
+{
+ struct imx_sc_ipc *handle;
+ u32 mipi_id, dc_id;
+ u8 ctrl;
+ bool shared_phy = dsi->pdata->shared_phy;
+ int ret = 0;
+
+ ret = imx_scu_get_handle(&handle);
+ if (ret) {
+ DRM_DEV_ERROR(dsi->dev,
+ "Failed to get scu ipc handle (%d)\n", ret);
+ return ret;
+ }
+
+ mipi_id = (dsi->instance)?MIPI_ID(1):MIPI_ID(0);
+ dc_id = (!shared_phy && dsi->instance)?DC_ID(1):DC_ID(0);
+ DRM_DEV_DEBUG_DRIVER(dsi->dev,
+ "Power %s PCLK MIPI:%u DC:%u\n",
+ (reset)?"OFF":"ON", mipi_id, dc_id);
+
+ if (shared_phy) {
+ ret |= imx_sc_misc_set_control(handle,
+ mipi_id, IMX_SC_C_MODE, reset);
+ ret |= imx_sc_misc_set_control(handle,
+ mipi_id, IMX_SC_C_DUAL_MODE, reset);
+ ret |= imx_sc_misc_set_control(handle,
+ mipi_id, IMX_SC_C_PXL_LINK_SEL, reset);
+ }
+
+ ctrl = (shared_phy && dsi->instance)?PXL_VLD(2):PXL_VLD(1);
+ ret |= imx_sc_misc_set_control(handle, dc_id, ctrl, !reset);
+
+ ctrl = (shared_phy && dsi->instance)?SYNC_CTRL(1):SYNC_CTRL(0);
+ ret |= imx_sc_misc_set_control(handle, dc_id, ctrl, !reset);
+
+ return ret;
+}
+
+static int imx8q_dsi_mipi_reset(struct nwl_dsi *dsi, bool reset)
+{
+ struct imx_sc_ipc *handle;
+ u32 mipi_id;
+ int ret = 0;
+
+ ret = imx_scu_get_handle(&handle);
+ if (ret) {
+ DRM_DEV_ERROR(dsi->dev,
+ "Failed to get scu ipc handle (%d)\n", ret);
+ return ret;
+ }
+
+ mipi_id = (dsi->instance)?MIPI_ID(1):MIPI_ID(0);
+ DRM_DEV_DEBUG_DRIVER(dsi->dev,
+ "Power %s HOST MIPI:%u\n",
+ (reset)?"OFF":"ON", mipi_id);
+
+ ret |= imx_sc_misc_set_control(handle, mipi_id,
+ IMX_SC_C_PHY_RESET, !reset);
+ ret |= imx_sc_misc_set_control(handle, mipi_id,
+ IMX_SC_C_MIPI_RESET, !reset);
+
+ return ret;
+}
+
+static int imx8q_dsi_dpi_reset(struct nwl_dsi *dsi, bool reset)
+{
+ struct imx_sc_ipc *handle;
+ u32 mipi_id;
+ int ret = 0;
+
+ ret = imx_scu_get_handle(&handle);
+ if (ret) {
+ DRM_DEV_ERROR(dsi->dev,
+ "Failed to get scu ipc handle (%d)\n", ret);
+ return ret;
+ }
+
+ mipi_id = (dsi->instance)?MIPI_ID(1):MIPI_ID(0);
+ DRM_DEV_DEBUG_DRIVER(dsi->dev,
+ "Power %s DPI MIPI:%u\n",
+ (reset)?"OFF":"ON", mipi_id);
+
+ regmap_write(dsi->csr, dsi->pdata->reg_tx_ulps, 0);
+ regmap_write(dsi->csr, dsi->pdata->reg_pxl2dpi, NWL_DSI_DPI_24_BIT);
+
+ ret |= imx_sc_misc_set_control(handle, mipi_id,
+ IMX_SC_C_DPI_RESET, !reset);
+
+ return ret;
+}
+
static const struct drm_bridge_timings nwl_dsi_timings = {
.input_bus_flags = DRM_BUS_FLAG_DE_LOW,
};
+static const struct nwl_dsi_platform_data imx8mq_dev = {
+ .pclk_reset = &imx8_common_dsi_pclk_reset,
+ .mipi_reset = &imx8_common_dsi_mipi_reset,
+ .dpi_reset = &imx8_common_dsi_dpi_reset,
+ .clks = NWL_DSI_CORE_CLK | NWL_DSI_LCDIF_CLK,
+ .mux_present = true,
+ .bit_hs_tx_timeout = BIT(29),
+ .bit_bta_timeout = BIT(31),
+ .use_lcdif_or_dcss = true,
+};
+
+static const struct nwl_dsi_platform_data imx8qm_dev = {
+ .pclk_reset = &imx8q_dsi_pclk_reset,
+ .mipi_reset = &imx8q_dsi_mipi_reset,
+ .dpi_reset = &imx8q_dsi_dpi_reset,
+ .clks = NWL_DSI_BYPASS_CLK | NWL_DSI_PIXEL_CLK,
+ .reg_tx_ulps = 0x00,
+ .reg_pxl2dpi = 0x04,
+ .max_instances = 2,
+ .tx_clk_rate = 18000000,
+ .rx_clk_rate = 72000000,
+ .shared_phy = false,
+ .bit_hs_tx_timeout = BIT(29),
+ .bit_bta_timeout = BIT(31),
+};
+
+static const struct nwl_dsi_platform_data imx8qx_dev = {
+ .pclk_reset = &imx8q_dsi_pclk_reset,
+ .mipi_reset = &imx8q_dsi_mipi_reset,
+ .dpi_reset = &imx8q_dsi_dpi_reset,
+ .clks = NWL_DSI_BYPASS_CLK | NWL_DSI_PIXEL_CLK,
+ .reg_tx_ulps = 0x30,
+ .reg_pxl2dpi = 0x40,
+ .max_instances = 2,
+ .tx_clk_rate = 18000000,
+ .rx_clk_rate = 72000000,
+ .shared_phy = true,
+ .bit_hs_tx_timeout = BIT(29),
+ .bit_bta_timeout = BIT(31),
+};
+
+static const struct nwl_dsi_platform_data imx8ulp_dev = {
+ .pclk_reset = &imx8_common_dsi_pclk_reset,
+ .mipi_reset = &imx8_common_dsi_mipi_reset,
+ .dpi_reset = &imx8_common_dsi_dpi_reset,
+ .clks = NWL_DSI_CORE_CLK,
+ .reg_cm = 0x8,
+ .mux_present = true,
+ .bit_hs_tx_timeout = BIT(31),
+ .bit_bta_timeout = BIT(29),
+ .use_dcnano_or_epdc = true,
+ .rx_clk_quirk = true,
+};
+
static const struct of_device_id nwl_dsi_dt_ids[] = {
- { .compatible = "fsl,imx8mq-nwl-dsi", },
+ { .compatible = "fsl,imx8mq-nwl-dsi", .data = &imx8mq_dev, },
+ { .compatible = "fsl,imx8qm-nwl-dsi", .data = &imx8qm_dev, },
+ { .compatible = "fsl,imx8qx-nwl-dsi", .data = &imx8qx_dev, },
+ { .compatible = "fsl,imx8ulp-nwl-dsi", .data = &imx8ulp_dev, },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, nwl_dsi_dt_ids);
@@ -1163,18 +1964,110 @@ static const struct soc_device_attribute nwl_dsi_quirks_match[] = {
{ /* sentinel. */ },
};
+static int nwl_dsi_encoder_atomic_check(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc_state);
+
+ imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB101010_1X30;
+
+ return 0;
+}
+
+static const struct drm_encoder_helper_funcs nwl_dsi_encoder_helper_funcs = {
+ .atomic_check = nwl_dsi_encoder_atomic_check,
+};
+
+static int nwl_dsi_bind(struct device *dev,
+ struct device *master,
+ void *data)
+{
+ struct drm_device *drm = data;
+ uint32_t crtc_mask;
+ struct nwl_dsi *dsi = dev_get_drvdata(dev);
+ int ret = 0;
+
+ DRM_DEV_DEBUG_DRIVER(dev, "id = %s\n", (dsi->instance)?"DSI1":"DSI0");
+
+ crtc_mask = drm_of_find_possible_crtcs(drm, dev->of_node);
+ /*
+ * If we failed to find the CRTC(s) which this encoder is
+ * supposed to be connected to, it's because the CRTC has
+ * not been registered yet. Defer probing, and hope that
+ * the required CRTC is added later.
+ */
+ if (crtc_mask == 0)
+ return -EPROBE_DEFER;
+
+ dsi->encoder.possible_crtcs = crtc_mask;
+ dsi->encoder.possible_clones = 0;
+
+ drm_encoder_helper_add(&dsi->encoder,
+ &nwl_dsi_encoder_helper_funcs);
+ ret = drm_encoder_init(drm,
+ &dsi->encoder,
+ &nwl_dsi_encoder_funcs,
+ DRM_MODE_ENCODER_DSI,
+ NULL);
+ if (ret) {
+ DRM_DEV_ERROR(dev, "failed to init DSI encoder (%d)\n", ret);
+ return ret;
+ }
+
+ ret = drm_bridge_attach(&dsi->encoder, &dsi->bridge, NULL, 0);
+ if (ret)
+ drm_encoder_cleanup(&dsi->encoder);
+
+ /*
+ * -ENODEV is returned when there is no node connected to us. Since
+ * it might be disabled because the device is not actually connected,
+ * just cleanup and return 0.
+ */
+ if (ret == -ENODEV)
+ return 0;
+
+ return ret;
+}
+
+static void nwl_dsi_unbind(struct device *dev,
+ struct device *master,
+ void *data)
+{
+ struct nwl_dsi *dsi = dev_get_drvdata(dev);
+
+ DRM_DEV_DEBUG_DRIVER(dev, "id = %s\n", (dsi->instance)?"DSI1":"DSI0");
+
+ if (dsi->encoder.dev)
+ drm_encoder_cleanup(&dsi->encoder);
+}
+
+static const struct component_ops nwl_dsi_component_ops = {
+ .bind = nwl_dsi_bind,
+ .unbind = nwl_dsi_unbind,
+};
+
static int nwl_dsi_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
+ const struct of_device_id *of_id = of_match_device(nwl_dsi_dt_ids, dev);
const struct soc_device_attribute *attr;
struct nwl_dsi *dsi;
int ret;
+ if (!of_id || !of_id->data)
+ return -ENODEV;
+
dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL);
if (!dsi)
return -ENOMEM;
dsi->dev = dev;
+ dsi->pdata = of_id->data;
+
+ attr = soc_device_match(nwl_dsi_quirks_match);
+ if (attr)
+ dsi->quirks = (uintptr_t)attr->data;
ret = nwl_dsi_parse_dt(dsi);
if (ret)
@@ -1196,10 +2089,6 @@ static int nwl_dsi_probe(struct platform_device *pdev)
return ret;
}
- attr = soc_device_match(nwl_dsi_quirks_match);
- if (attr)
- dsi->quirks = (uintptr_t)attr->data;
-
dsi->bridge.driver_private = dsi;
dsi->bridge.funcs = &nwl_dsi_bridge_funcs;
dsi->bridge.of_node = dev->of_node;
@@ -1216,13 +2105,31 @@ static int nwl_dsi_probe(struct platform_device *pdev)
}
drm_bridge_add(&dsi->bridge);
- return 0;
+
+ if (of_property_read_bool(dev->of_node, "use-disp-ss"))
+ ret = component_add(&pdev->dev, &nwl_dsi_component_ops);
+
+ if (ret) {
+ pm_runtime_disable(dev);
+ drm_bridge_remove(&dsi->bridge);
+ mipi_dsi_host_unregister(&dsi->dsi_host);
+ }
+
+ return ret;
}
static int nwl_dsi_remove(struct platform_device *pdev)
{
struct nwl_dsi *dsi = platform_get_drvdata(pdev);
+ struct mode_config *config;
+ struct list_head *pos, *tmp;
+ list_for_each_safe(pos, tmp, &dsi->valid_modes) {
+ config = list_entry(pos, struct mode_config, list);
+ list_del(pos);
+ devm_kfree(dsi->dev, config);
+ }
+
nwl_dsi_deselect_input(dsi);
mipi_dsi_host_unregister(&dsi->dsi_host);
drm_bridge_remove(&dsi->bridge);
diff --git a/drivers/gpu/drm/bridge/nwl-dsi.h b/drivers/gpu/drm/bridge/nwl-dsi.h
index a247a8a11c7c..d34fffec00c0 100644
--- a/drivers/gpu/drm/bridge/nwl-dsi.h
+++ b/drivers/gpu/drm/bridge/nwl-dsi.h
@@ -69,9 +69,7 @@
#define NWL_DSI_RX_FIFO_UDFLW BIT(6)
#define NWL_DSI_RX_PKT_HDR_RCVD BIT(7)
#define NWL_DSI_RX_PKT_PAYLOAD_DATA_RCVD BIT(8)
-#define NWL_DSI_BTA_TIMEOUT BIT(29)
#define NWL_DSI_LP_RX_TIMEOUT BIT(30)
-#define NWL_DSI_HS_TX_TIMEOUT BIT(31)
#define NWL_DSI_IRQ_STATUS2 0x2a4
#define NWL_DSI_SINGLE_BIT_ECC_ERR BIT(0)
@@ -88,9 +86,7 @@
#define NWL_DSI_RX_FIFO_UDFLW_MASK BIT(6)
#define NWL_DSI_RX_PKT_HDR_RCVD_MASK BIT(7)
#define NWL_DSI_RX_PKT_PAYLOAD_DATA_RCVD_MASK BIT(8)
-#define NWL_DSI_BTA_TIMEOUT_MASK BIT(29)
#define NWL_DSI_LP_RX_TIMEOUT_MASK BIT(30)
-#define NWL_DSI_HS_TX_TIMEOUT_MASK BIT(31)
#define NWL_DSI_IRQ_MASK2 0x2ac
#define NWL_DSI_SINGLE_BIT_ECC_ERR_MASK BIT(0)
diff --git a/drivers/gpu/drm/bridge/nxp-seiko-43wvfig.c b/drivers/gpu/drm/bridge/nxp-seiko-43wvfig.c
new file mode 100644
index 000000000000..f09b0b6920c8
--- /dev/null
+++ b/drivers/gpu/drm/bridge/nxp-seiko-43wvfig.c
@@ -0,0 +1,265 @@
+/*
+ * DRM driver for the legacy Freescale adapter card holding
+ * Seiko RA169Z20 43WVFIG LCD panel
+ *
+ * Copyright (C) 2018 NXP
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/of_platform.h>
+
+struct seiko_adapter {
+ struct device *dev;
+ struct drm_panel *panel;
+ struct drm_bridge bridge;
+ struct drm_connector connector;
+
+ u32 bpc;
+ u32 bus_format;
+};
+
+static enum drm_connector_status seiko_adapter_connector_detect(
+ struct drm_connector *connector, bool force)
+{
+ return connector_status_connected;
+}
+
+static int seiko_adapter_connector_get_modes(struct drm_connector *connector)
+{
+ int num_modes;
+
+ struct seiko_adapter *adap = container_of(connector,
+ struct seiko_adapter,
+ connector);
+
+ num_modes = drm_panel_get_modes(adap->panel, connector);
+
+ /*
+ * The panel will populate the connector display_info properties with
+ * fixed numbers, but we need to change them according to our
+ * configuration.
+ */
+ connector->display_info.bpc = adap->bpc;
+ drm_display_info_set_bus_formats(&connector->display_info,
+ &adap->bus_format, 1);
+
+ return num_modes;
+}
+
+static const struct drm_connector_funcs seiko_adapter_connector_funcs = {
+ .detect = seiko_adapter_connector_detect,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = drm_connector_cleanup,
+ .reset = drm_atomic_helper_connector_reset,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static const struct drm_connector_helper_funcs
+ seiko_adapter_connector_helper_funcs = {
+ .get_modes = seiko_adapter_connector_get_modes,
+};
+
+static int seiko_adapter_bridge_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
+{
+ struct seiko_adapter *adap = bridge->driver_private;
+ struct device *dev = adap->dev;
+ struct drm_encoder *encoder = bridge->encoder;
+ struct drm_device *drm;
+ int ret = 0;
+
+ if (!encoder) {
+ DRM_DEV_ERROR(dev, "Parent encoder object not found\n");
+ return -ENODEV;
+ }
+
+ drm = encoder->dev;
+
+ /*
+ * Create the connector for our panel
+ */
+
+ ret = drm_connector_init(drm, &adap->connector,
+ &seiko_adapter_connector_funcs,
+ DRM_MODE_CONNECTOR_DPI);
+ if (ret) {
+ DRM_DEV_ERROR(dev, "Failed to init drm connector: %d\n", ret);
+ return ret;
+ }
+
+ drm_connector_helper_add(&adap->connector,
+ &seiko_adapter_connector_helper_funcs);
+
+ adap->connector.dpms = DRM_MODE_DPMS_OFF;
+ drm_connector_attach_encoder(&adap->connector, encoder);
+
+ return ret;
+}
+
+static void seiko_adapter_bridge_detach(struct drm_bridge *bridge)
+{
+ struct seiko_adapter *adap = bridge->driver_private;
+
+ drm_connector_cleanup(&adap->connector);
+}
+
+static void seiko_adapter_bridge_enable(struct drm_bridge *bridge)
+{
+ struct seiko_adapter *adap = bridge->driver_private;
+ struct device *dev = adap->dev;
+
+ if (drm_panel_prepare(adap->panel)) {
+ DRM_DEV_ERROR(dev, "Failed to prepare panel\n");
+ return;
+ }
+
+ if (drm_panel_enable(adap->panel)) {
+ DRM_DEV_ERROR(dev, "Failed to enable panel\n");
+ drm_panel_unprepare(adap->panel);
+ }
+}
+
+static void seiko_adapter_bridge_disable(struct drm_bridge *bridge)
+{
+ struct seiko_adapter *adap = bridge->driver_private;
+ struct device *dev = adap->dev;
+
+ if (drm_panel_disable(adap->panel)) {
+ DRM_DEV_ERROR(dev, "failed to disable panel\n");
+ return;
+ }
+
+ if (drm_panel_unprepare(adap->panel))
+ DRM_DEV_ERROR(dev, "failed to unprepare panel\n");
+}
+
+#define MAX_INPUT_FORMATS 1
+static u32 *
+seiko_adapter_atomic_get_input_bus_fmts(struct drm_bridge *bridge,
+ struct drm_bridge_state *bridge_state,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state,
+ u32 output_fmt,
+ unsigned int *num_input_fmts) {
+ struct seiko_adapter *adap = bridge->driver_private;
+ u32 *input_fmts;
+
+ *num_input_fmts = 0;
+
+ input_fmts = kcalloc(MAX_INPUT_FORMATS, sizeof(*input_fmts),
+ GFP_KERNEL);
+ if (!input_fmts)
+ return NULL;
+
+ input_fmts[0] = adap->bus_format;
+ *num_input_fmts = MAX_INPUT_FORMATS;
+
+ return input_fmts;
+}
+
+static const struct drm_bridge_funcs seiko_adapter_bridge_funcs = {
+ .enable = seiko_adapter_bridge_enable,
+ .disable = seiko_adapter_bridge_disable,
+ .attach = seiko_adapter_bridge_attach,
+ .detach = seiko_adapter_bridge_detach,
+
+ .atomic_get_input_bus_fmts = seiko_adapter_atomic_get_input_bus_fmts,
+
+ .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
+ .atomic_reset = drm_atomic_helper_bridge_reset,
+};
+
+static int seiko_adapter_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct seiko_adapter *adap;
+ u32 bus_mode;
+ int port, ret;
+
+ adap = devm_kzalloc(dev, sizeof(*adap), GFP_KERNEL);
+ if (!adap)
+ return -ENOMEM;
+
+ of_property_read_u32(dev->of_node, "bus_mode", &bus_mode);
+ if (bus_mode != 18 && bus_mode != 24) {
+ dev_err(dev, "Invalid bus_mode: %d\n", bus_mode);
+ return -EINVAL;
+ }
+
+ switch (bus_mode) {
+ case 18:
+ adap->bpc = 6;
+ adap->bus_format = MEDIA_BUS_FMT_RGB666_1X18;
+ break;
+ case 24:
+ adap->bpc = 8;
+ adap->bus_format = MEDIA_BUS_FMT_RGB888_1X24;
+ break;
+ }
+
+ for (port = 0; port < 2; port++) {
+ ret = drm_of_find_panel_or_bridge(dev->of_node,
+ port, 0,
+ &adap->panel, NULL);
+ if (!ret)
+ break;
+ }
+
+ if (ret) {
+ dev_err(dev, "No panel found: %d\n", ret);
+ return ret;
+ }
+
+ adap->dev = dev;
+ adap->bridge.driver_private = adap;
+ adap->bridge.funcs = &seiko_adapter_bridge_funcs;
+ adap->bridge.of_node = dev->of_node;
+
+ drm_bridge_add(&adap->bridge);
+
+ return 0;
+}
+
+static int seiko_adapter_remove(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static const struct of_device_id seiko_adapter_dt_ids[] = {
+ { .compatible = "nxp,seiko-43wvfig" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, seiko_adapter_dt_ids);
+
+static struct platform_driver seiko_adapter_driver = {
+ .probe = seiko_adapter_probe,
+ .remove = seiko_adapter_remove,
+ .driver = {
+ .of_match_table = seiko_adapter_dt_ids,
+ .name = "nxp-seiko-adapter",
+ },
+};
+
+module_platform_driver(seiko_adapter_driver);
+
+MODULE_AUTHOR("NXP Semiconductor");
+MODULE_DESCRIPTION("Seiko 43WVFIG adapter card driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/bridge/sec-dsim.c b/drivers/gpu/drm/bridge/sec-dsim.c
new file mode 100644
index 000000000000..1eb34e755509
--- /dev/null
+++ b/drivers/gpu/drm/bridge/sec-dsim.c
@@ -0,0 +1,2087 @@
+/*
+ * Samsung MIPI DSIM Bridge
+ *
+ * Copyright 2018-2022 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <asm/unaligned.h>
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/gcd.h>
+#include <linux/log2.h>
+#include <linux/module.h>
+#include <linux/of_graph.h>
+#include <linux/pm_runtime.h>
+#include <drm/bridge/sec_mipi_dsim.h>
+#include <drm/drm_vblank.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_panel.h>
+#include <video/videomode.h>
+#include <video/mipi_display.h>
+
+/* dsim registers */
+#define DSIM_VERSION 0x00
+#define DSIM_STATUS 0x04
+#define DSIM_RGB_STATUS 0x08
+#define DSIM_SWRST 0x0c
+#define DSIM_CLKCTRL 0x10
+#define DSIM_TIMEOUT 0x14
+#define DSIM_CONFIG 0x18
+#define DSIM_ESCMODE 0x1c
+#define DSIM_MDRESOL 0x20
+#define DSIM_MVPORCH 0x24
+#define DSIM_MHPORCH 0x28
+#define DSIM_MSYNC 0x2c
+#define DSIM_SDRESOL 0x30
+#define DSIM_INTSRC 0x34
+#define DSIM_INTMSK 0x38
+
+/* packet */
+#define DSIM_PKTHDR 0x3c
+#define DSIM_PAYLOAD 0x40
+#define DSIM_RXFIFO 0x44
+#define DSIM_FIFOTHLD 0x48
+#define DSIM_FIFOCTRL 0x4c
+#define DSIM_MEMACCHR 0x50
+#define DSIM_MULTI_PKT 0x78
+
+/* pll control */
+#define DSIM_PLLCTRL_1G 0x90
+#define DSIM_PLLCTRL 0x94
+#define DSIM_PLLCTRL1 0x98
+#define DSIM_PLLCTRL2 0x9c
+#define DSIM_PLLTMR 0xa0
+
+/* dphy */
+#define DSIM_PHYTIMING 0xb4
+#define DSIM_PHYTIMING1 0xb8
+#define DSIM_PHYTIMING2 0xbc
+
+/* reg bit manipulation */
+#define REG_MASK(e, s) (((1 << ((e) - (s) + 1)) - 1) << (s))
+#define REG_PUT(x, e, s) (((x) << (s)) & REG_MASK(e, s))
+#define REG_GET(x, e, s) (((x) & REG_MASK(e, s)) >> (s))
+
+/* register bit fields */
+#define STATUS_PLLSTABLE BIT(31)
+#define STATUS_SWRSTRLS BIT(20)
+#define STATUS_TXREADYHSCLK BIT(10)
+#define STATUS_ULPSCLK BIT(9)
+#define STATUS_STOPSTATECLK BIT(8)
+#define STATUS_GET_ULPSDAT(x) REG_GET(x, 7, 4)
+#define STATUS_GET_STOPSTATEDAT(x) REG_GET(x, 3, 0)
+
+#define RGB_STATUS_CMDMODE_INSEL BIT(31)
+#define RGB_STATUS_GET_RGBSTATE(x) REG_GET(x, 12, 0)
+
+#define CLKCTRL_TXREQUESTHSCLK BIT(31)
+#define CLKCTRL_DPHY_SEL_1G BIT(29)
+#define CLKCTRL_DPHY_SEL_1P5G (0x0 << 29)
+#define CLKCTRL_ESCCLKEN BIT(28)
+#define CLKCTRL_PLLBYPASS BIT(29)
+#define CLKCTRL_BYTECLKSRC_DPHY_PLL REG_PUT(0, 26, 25)
+#define CLKCTRL_BYTECLKEN BIT(24)
+#define CLKCTRL_SET_LANEESCCLKEN(x) REG_PUT(x, 23, 19)
+#define CLKCTRL_SET_ESCPRESCALER(x) REG_PUT(x, 15, 0)
+
+#define TIMEOUT_SET_BTAOUT(x) REG_PUT(x, 23, 16)
+#define TIMEOUT_SET_LPDRTOUT(x) REG_PUT(x, 15, 0)
+
+#define CONFIG_NON_CONTINUOUS_CLOCK_LANE BIT(31)
+#define CONFIG_CLKLANE_STOP_START BIT(30)
+#define CONFIG_MFLUSH_VS BIT(29)
+#define CONFIG_EOT_R03 BIT(28)
+#define CONFIG_SYNCINFORM BIT(27)
+#define CONFIG_BURSTMODE BIT(26)
+#define CONFIG_VIDEOMODE BIT(25)
+#define CONFIG_AUTOMODE BIT(24)
+#define CONFIG_HSEDISABLEMODE BIT(23)
+#define CONFIG_HFPDISABLEMODE BIT(22)
+#define CONFIG_HBPDISABLEMODE BIT(21)
+#define CONFIG_HSADISABLEMODE BIT(20)
+#define CONFIG_SET_MAINVC(x) REG_PUT(x, 19, 18)
+#define CONFIG_SET_SUBVC(x) REG_PUT(x, 17, 16)
+#define CONFIG_SET_MAINPIXFORMAT(x) REG_PUT(x, 14, 12)
+#define CONFIG_SET_SUBPIXFORMAT(x) REG_PUT(x, 10, 8)
+#define CONFIG_SET_NUMOFDATLANE(x) REG_PUT(x, 6, 5)
+#define CONFIG_SET_LANEEN(x) REG_PUT(x, 4, 0)
+
+#define ESCMODE_SET_STOPSTATE_CNT(X) REG_PUT(x, 31, 21)
+#define ESCMODE_FORCESTOPSTATE BIT(20)
+#define ESCMODE_FORCEBTA BIT(16)
+#define ESCMODE_CMDLPDT BIT(7)
+#define ESCMODE_TXLPDT BIT(6)
+#define ESCMODE_TXTRIGGERRST BIT(5)
+
+#define MDRESOL_MAINSTANDBY BIT(31)
+#define MDRESOL_SET_MAINVRESOL(x) REG_PUT(x, 27, 16)
+#define MDRESOL_SET_MAINHRESOL(x) REG_PUT(x, 11, 0)
+
+#define MVPORCH_SET_CMDALLOW(x) REG_PUT(x, 31, 28)
+#define MVPORCH_SET_STABLEVFP(x) REG_PUT(x, 26, 16)
+#define MVPORCH_SET_MAINVBP(x) REG_PUT(x, 10, 0)
+
+#define MHPORCH_SET_MAINHFP(x) REG_PUT(x, 31, 16)
+#define MHPORCH_SET_MAINHBP(x) REG_PUT(x, 15, 0)
+
+#define MSYNC_SET_MAINVSA(x) REG_PUT(x, 31, 22)
+#define MSYNC_SET_MAINHSA(x) REG_PUT(x, 15, 0)
+
+#define INTSRC_PLLSTABLE BIT(31)
+#define INTSRC_SWRSTRELEASE BIT(30)
+#define INTSRC_SFRPLFIFOEMPTY BIT(29)
+#define INTSRC_SFRPHFIFOEMPTY BIT(28)
+#define INTSRC_FRAMEDONE BIT(24)
+#define INTSRC_LPDRTOUT BIT(21)
+#define INTSRC_TATOUT BIT(20)
+#define INTSRC_RXDATDONE BIT(18)
+#define INTSRC_RXTE BIT(17)
+#define INTSRC_RXACK BIT(16)
+#define INTSRC_MASK (INTSRC_PLLSTABLE | \
+ INTSRC_SWRSTRELEASE | \
+ INTSRC_SFRPLFIFOEMPTY | \
+ INTSRC_SFRPHFIFOEMPTY | \
+ INTSRC_FRAMEDONE | \
+ INTSRC_LPDRTOUT | \
+ INTSRC_TATOUT | \
+ INTSRC_RXDATDONE | \
+ INTSRC_RXTE | \
+ INTSRC_RXACK)
+
+#define INTMSK_MSKPLLSTABLE BIT(31)
+#define INTMSK_MSKSWRELEASE BIT(30)
+#define INTMSK_MSKSFRPLFIFOEMPTY BIT(29)
+#define INTMSK_MSKSFRPHFIFOEMPTY BIT(28)
+#define INTMSK_MSKFRAMEDONE BIT(24)
+#define INTMSK_MSKLPDRTOUT BIT(21)
+#define INTMSK_MSKTATOUT BIT(20)
+#define INTMSK_MSKRXDATDONE BIT(18)
+#define INTMSK_MSKRXTE BIT(17)
+#define INTMSK_MSKRXACK BIT(16)
+
+#define PKTHDR_SET_DATA1(x) REG_PUT(x, 23, 16)
+#define PKTHDR_GET_DATA1(x) REG_GET(x, 23, 16)
+#define PKTHDR_SET_DATA0(x) REG_PUT(x, 15, 8)
+#define PKTHDR_GET_DATA0(x) REG_GET(x, 15, 8)
+#define PKTHDR_GET_WC(x) REG_GET(x, 23, 8)
+#define PKTHDR_SET_DI(x) REG_PUT(x, 7, 0)
+#define PKTHDR_GET_DI(x) REG_GET(x, 7, 0)
+#define PKTHDR_SET_DT(x) REG_PUT(x, 5, 0)
+#define PKTHDR_GET_DT(x) REG_GET(x, 5, 0)
+#define PKTHDR_SET_VC(x) REG_PUT(x, 7, 6)
+#define PKTHDR_GET_VC(x) REG_GET(x, 7, 6)
+
+#define FIFOCTRL_FULLRX BIT(25)
+#define FIFOCTRL_EMPTYRX BIT(24)
+#define FIFOCTRL_FULLHSFR BIT(23)
+#define FIFOCTRL_EMPTYHSFR BIT(22)
+#define FIFOCTRL_FULLLSFR BIT(21)
+#define FIFOCTRL_EMPTYLSFR BIT(20)
+#define FIFOCTRL_FULLHMAIN BIT(11)
+#define FIFOCTRL_EMPTYHMAIN BIT(10)
+#define FIFOCTRL_FULLLMAIN BIT(9)
+#define FIFOCTRL_EMPTYLMAIN BIT(8)
+#define FIFOCTRL_NINITRX BIT(4)
+#define FIFOCTRL_NINITSFR BIT(3)
+#define FIFOCTRL_NINITI80 BIT(2)
+#define FIFOCTRL_NINITSUB BIT(1)
+#define FIFOCTRL_NINITMAIN BIT(0)
+
+#define PLLCTRL_DPDNSWAP_CLK BIT(25)
+#define PLLCTRL_DPDNSWAP_DAT BIT(24)
+#define PLLCTRL_PLLEN BIT(23)
+#define PLLCTRL_SET_PMS(x) REG_PUT(x, 19, 1)
+ #define PLLCTRL_SET_P(x) REG_PUT(x, 18, 13)
+ #define PLLCTRL_SET_M(x) REG_PUT(x, 12, 3)
+ #define PLLCTRL_SET_S(x) REG_PUT(x, 2, 0)
+
+#define PHYTIMING_SET_M_TLPXCTL(x) REG_PUT(x, 15, 8)
+#define PHYTIMING_SET_M_THSEXITCTL(x) REG_PUT(x, 7, 0)
+
+#define PHYTIMING1_SET_M_TCLKPRPRCTL(x) REG_PUT(x, 31, 24)
+#define PHYTIMING1_SET_M_TCLKZEROCTL(x) REG_PUT(x, 23, 16)
+#define PHYTIMING1_SET_M_TCLKPOSTCTL(x) REG_PUT(x, 15, 8)
+#define PHYTIMING1_SET_M_TCLKTRAILCTL(x) REG_PUT(x, 7, 0)
+
+#define PHYTIMING2_SET_M_THSPRPRCTL(x) REG_PUT(x, 23, 16)
+#define PHYTIMING2_SET_M_THSZEROCTL(x) REG_PUT(x, 15, 8)
+#define PHYTIMING2_SET_M_THSTRAILCTL(x) REG_PUT(x, 7, 0)
+
+#define dsim_read(dsim, reg) readl(dsim->base + reg)
+#define dsim_write(dsim, val, reg) writel(val, dsim->base + reg)
+
+#define MAX_MAIN_HRESOL 2047
+#define MAX_MAIN_VRESOL 2047
+#define MAX_SUB_HRESOL 1024
+#define MAX_SUB_VRESOL 1024
+
+/* in KHZ */
+#define MAX_ESC_CLK_FREQ 20000
+
+/* dsim all irqs index */
+#define PLLSTABLE 1
+#define SWRSTRELEASE 2
+#define SFRPLFIFOEMPTY 3
+#define SFRPHFIFOEMPTY 4
+#define SYNCOVERRIDE 5
+#define BUSTURNOVER 6
+#define FRAMEDONE 7
+#define LPDRTOUT 8
+#define TATOUT 9
+#define RXDATDONE 10
+#define RXTE 11
+#define RXACK 12
+#define ERRRXECC 13
+#define ERRRXCRC 14
+#define ERRESC3 15
+#define ERRESC2 16
+#define ERRESC1 17
+#define ERRESC0 18
+#define ERRSYNC3 19
+#define ERRSYNC2 20
+#define ERRSYNC1 21
+#define ERRSYNC0 22
+#define ERRCONTROL3 23
+#define ERRCONTROL2 24
+#define ERRCONTROL1 25
+#define ERRCONTROL0 26
+
+#define MIPI_FIFO_TIMEOUT msecs_to_jiffies(250)
+
+#define MIPI_HFP_PKT_OVERHEAD 6
+#define MIPI_HBP_PKT_OVERHEAD 6
+#define MIPI_HSA_PKT_OVERHEAD 6
+
+#define to_sec_mipi_dsim(dsi) container_of(dsi, struct sec_mipi_dsim, dsi_host)
+#define conn_to_sec_mipi_dsim(conn) \
+ container_of(conn, struct sec_mipi_dsim, connector)
+
+/* used for CEA standard modes */
+struct dsim_hblank_par {
+ char *name; /* drm display mode name */
+ int vrefresh;
+ int hfp_wc;
+ int hbp_wc;
+ int hsa_wc;
+ int lanes;
+};
+
+struct dsim_pll_pms {
+ uint32_t bit_clk; /* kHz */
+ uint32_t p;
+ uint32_t m;
+ uint32_t s;
+ uint32_t k;
+};
+
+struct sec_mipi_dsim {
+ struct mipi_dsi_host dsi_host;
+ struct drm_connector connector;
+ struct drm_encoder *encoder;
+ struct drm_bridge *bridge;
+ struct drm_bridge *next;
+ struct drm_panel *panel;
+ struct device *dev;
+
+ void __iomem *base;
+
+ /* kHz clocks */
+ uint32_t pix_clk;
+ uint32_t bit_clk;
+ uint32_t pref_clk; /* phy ref clock rate in KHz */
+
+ unsigned int lanes;
+ unsigned int channel; /* virtual channel */
+ enum mipi_dsi_pixel_format format;
+ unsigned long mode_flags;
+ const struct dsim_hblank_par *hpar;
+ unsigned int pms;
+ unsigned int p;
+ unsigned int m;
+ unsigned int s;
+ unsigned long long lp_data_rate;
+ unsigned long long hs_data_rate;
+ struct videomode vmode;
+ bool enabled;
+
+ struct completion pll_stable;
+ struct completion ph_tx_done;
+ struct completion pl_tx_done;
+ struct completion rx_done;
+ const struct sec_mipi_dsim_plat_data *pdata;
+};
+
+#define DSIM_HBLANK_PARAM(nm, vf, hfp, hbp, hsa, num) \
+ .name = (nm), \
+ .vrefresh = (vf), \
+ .hfp_wc = (hfp), \
+ .hbp_wc = (hbp), \
+ .hsa_wc = (hsa), \
+ .lanes = (num)
+
+#define DSIM_PLL_PMS(c, pp, mm, ss) \
+ .bit_clk = (c), \
+ .p = (pp), \
+ .m = (mm), \
+ .s = (ss)
+
+static const struct dsim_hblank_par hblank_4lanes[] = {
+ /* { 88, 148, 44 } */
+ { DSIM_HBLANK_PARAM("1920x1080", 60, 60, 105, 27, 4), },
+ /* { 528, 148, 44 } */
+ { DSIM_HBLANK_PARAM("1920x1080", 50, 390, 105, 27, 4), },
+ /* { 88, 148, 44 } */
+ { DSIM_HBLANK_PARAM("1920x1080", 30, 60, 105, 27, 4), },
+ /* { 110, 220, 40 } */
+ { DSIM_HBLANK_PARAM("1280x720" , 60, 78, 159, 24, 4), },
+ /* { 440, 220, 40 } */
+ { DSIM_HBLANK_PARAM("1280x720" , 50, 324, 159, 24, 4), },
+ /* { 16, 60, 62 } */
+ { DSIM_HBLANK_PARAM("720x480" , 60, 6, 39, 40, 4), },
+ /* { 12, 68, 64 } */
+ { DSIM_HBLANK_PARAM("720x576" , 50, 3, 45, 42, 4), },
+ /* { 16, 48, 96 } */
+ { DSIM_HBLANK_PARAM("640x480" , 60, 6, 30, 66, 4), },
+};
+
+static const struct dsim_hblank_par hblank_2lanes[] = {
+ /* { 88, 148, 44 } */
+ { DSIM_HBLANK_PARAM("1920x1080", 30, 114, 210, 60, 2), },
+ /* { 110, 220, 40 } */
+ { DSIM_HBLANK_PARAM("1280x720" , 60, 159, 320, 40, 2), },
+ /* { 440, 220, 40 } */
+ { DSIM_HBLANK_PARAM("1280x720" , 50, 654, 320, 40, 2), },
+ /* { 16, 60, 62 } */
+ { DSIM_HBLANK_PARAM("720x480" , 60, 16, 66, 88, 2), },
+ /* { 12, 68, 64 } */
+ { DSIM_HBLANK_PARAM("720x576" , 50, 12, 96, 72, 2), },
+ /* { 16, 48, 96 } */
+ { DSIM_HBLANK_PARAM("640x480" , 60, 18, 66, 138, 2), },
+};
+
+static const struct dsim_hblank_par *sec_mipi_dsim_get_hblank_par(const char *name,
+ int vrefresh,
+ int lanes)
+{
+ int i, size;
+ const struct dsim_hblank_par *hpar, *hblank;
+
+ if (unlikely(!name))
+ return NULL;
+
+ switch (lanes) {
+ case 2:
+ hblank = hblank_2lanes;
+ size = ARRAY_SIZE(hblank_2lanes);
+ break;
+ case 4:
+ hblank = hblank_4lanes;
+ size = ARRAY_SIZE(hblank_4lanes);
+ break;
+ default:
+ pr_err("No hblank data for mode %s with %d lanes\n",
+ name, lanes);
+ return NULL;
+ }
+
+ for (i = 0; i < size; i++) {
+ hpar = &hblank[i];
+
+ if (!strcmp(name, hpar->name)) {
+ if (vrefresh != hpar->vrefresh)
+ continue;
+
+ /* found */
+ return hpar;
+ }
+ }
+
+ return NULL;
+}
+
+static int sec_mipi_dsim_set_pref_rate(struct sec_mipi_dsim *dsim)
+{
+ int ret;
+ uint32_t rate;
+ const struct sec_mipi_dsim_plat_data *pdata = dsim->pdata;
+ const struct sec_mipi_dsim_pll *dpll = pdata->dphy_pll;
+ const struct sec_mipi_dsim_range *fin_range = &dpll->fin;
+
+ ret = pdata->determine_pll_ref_rate(&rate, fin_range->min, fin_range->max);
+ if (ret)
+ return ret;
+
+ dsim->pref_clk = rate;
+
+ return 0;
+}
+
+static void sec_mipi_dsim_irq_init(struct sec_mipi_dsim *dsim);
+
+/* For now, dsim only support one device attached */
+static int sec_mipi_dsim_host_attach(struct mipi_dsi_host *host,
+ struct mipi_dsi_device *dsi)
+{
+ struct sec_mipi_dsim *dsim = to_sec_mipi_dsim(host);
+ const struct sec_mipi_dsim_plat_data *pdata = dsim->pdata;
+ struct device *dev = dsim->dev;
+ struct drm_panel *panel;
+
+ if (!dsi->lanes || dsi->lanes > pdata->max_data_lanes) {
+ dev_err(dev, "invalid data lanes number\n");
+ return -EINVAL;
+ }
+
+ if (dsim->channel)
+ return -EINVAL;
+
+ if ((dsi->mode_flags & MIPI_DSI_MODE_VIDEO) &&
+ !((dsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST) ||
+ (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE))) {
+ dev_err(dev, "unsupported dsi mode\n");
+ return -EINVAL;
+ }
+
+ if (dsi->format != MIPI_DSI_FMT_RGB888 &&
+ dsi->format != MIPI_DSI_FMT_RGB565 &&
+ dsi->format != MIPI_DSI_FMT_RGB666 &&
+ dsi->format != MIPI_DSI_FMT_RGB666_PACKED) {
+ dev_err(dev, "unsupported pixel format: %#x\n", dsi->format);
+ return -EINVAL;
+ }
+
+ if (!dsim->next) {
+ /* 'dsi' must be panel device */
+ panel = of_drm_find_panel(dsi->dev.of_node);
+
+ if (!panel) {
+ dev_err(dev, "refuse unknown dsi device attach\n");
+ WARN_ON(!panel);
+ return -ENODEV;
+ }
+
+ /* Don't support multiple panels */
+ if (dsim->panel && panel && dsim->panel != panel) {
+ dev_err(dev, "don't support multiple panels\n");
+ return -EBUSY;
+ }
+
+ dsim->panel = panel;
+ }
+
+ /* TODO: DSIM 3 lanes has some display issue, so
+ * avoid 3 lanes enable, and force data lanes to
+ * be 2.
+ */
+ if (dsi->lanes == 3)
+ dsi->lanes = 2;
+
+ dsim->lanes = dsi->lanes;
+ dsim->channel = dsi->channel;
+ dsim->format = dsi->format;
+ dsim->mode_flags = dsi->mode_flags;
+
+ /* TODO: support later */
+#if 0
+ if (dsim->connector.dev)
+ drm_helper_hpd_irq_event(dsim->connector.dev);
+#endif
+
+ return 0;
+}
+
+static int sec_mipi_dsim_host_detach(struct mipi_dsi_host *host,
+ struct mipi_dsi_device *dsi)
+{
+ struct sec_mipi_dsim *dsim = to_sec_mipi_dsim(host);
+
+ if (WARN_ON(!dsim->next && !dsim->panel))
+ return -ENODEV;
+
+ /* clear the saved dsi parameters */
+ dsim->lanes = 0;
+ dsim->channel = 0;
+ dsim->format = 0;
+ dsim->mode_flags = 0;
+
+ /* detached panel should be NULL */
+ dsim->panel = NULL;
+
+ return 0;
+}
+
+static void sec_mipi_dsim_config_cmd_lpm(struct sec_mipi_dsim *dsim,
+ bool enable)
+{
+ uint32_t escmode;
+
+ escmode = dsim_read(dsim, DSIM_ESCMODE);
+
+ if (enable)
+ escmode |= ESCMODE_CMDLPDT;
+ else
+ escmode &= ~ESCMODE_CMDLPDT;
+
+ dsim_write(dsim, escmode, DSIM_ESCMODE);
+}
+
+static void sec_mipi_dsim_write_pl_to_sfr_fifo(struct sec_mipi_dsim *dsim,
+ const void *payload,
+ size_t length)
+{
+ uint32_t pl_data;
+
+ if (!length)
+ return;
+
+ while (length >= 4) {
+ pl_data = get_unaligned_le32(payload);
+ dsim_write(dsim, pl_data, DSIM_PAYLOAD);
+ payload += 4;
+ length -= 4;
+ }
+
+ pl_data = 0;
+ switch (length) {
+ case 3:
+ pl_data |= ((u8 *)payload)[2] << 16;
+ fallthrough;
+ case 2:
+ pl_data |= ((u8 *)payload)[1] << 8;
+ fallthrough;
+ case 1:
+ pl_data |= ((u8 *)payload)[0];
+ dsim_write(dsim, pl_data, DSIM_PAYLOAD);
+ break;
+ }
+}
+
+static void sec_mipi_dsim_write_ph_to_sfr_fifo(struct sec_mipi_dsim *dsim,
+ void *header,
+ bool use_lpm)
+{
+ uint32_t pkthdr;
+
+ pkthdr = PKTHDR_SET_DATA1(((u8 *)header)[2]) | /* WC MSB */
+ PKTHDR_SET_DATA0(((u8 *)header)[1]) | /* WC LSB */
+ PKTHDR_SET_DI(((u8 *)header)[0]); /* Data ID */
+
+ dsim_write(dsim, pkthdr, DSIM_PKTHDR);
+}
+
+static int sec_mipi_dsim_read_pl_from_sfr_fifo(struct sec_mipi_dsim *dsim,
+ void *payload,
+ size_t length)
+{
+ uint8_t data_type;
+ uint16_t word_count = 0;
+ uint32_t fifoctrl, ph, pl;
+
+ fifoctrl = dsim_read(dsim, DSIM_FIFOCTRL);
+
+ if (WARN_ON(fifoctrl & FIFOCTRL_EMPTYRX))
+ return -EINVAL;
+
+ ph = dsim_read(dsim, DSIM_RXFIFO);
+ data_type = PKTHDR_GET_DT(ph);
+ switch (data_type) {
+ case MIPI_DSI_RX_ACKNOWLEDGE_AND_ERROR_REPORT:
+ dev_err(dsim->dev, "peripheral report error: (0-7)%x, (8-15)%x\n",
+ PKTHDR_GET_DATA0(ph), PKTHDR_GET_DATA1(ph));
+ return -EPROTO;
+ case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_2BYTE:
+ case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_2BYTE:
+ if (!WARN_ON(length < 2)) {
+ ((u8 *)payload)[1] = PKTHDR_GET_DATA1(ph);
+ word_count++;
+ }
+ fallthrough;
+ case MIPI_DSI_RX_DCS_SHORT_READ_RESPONSE_1BYTE:
+ case MIPI_DSI_RX_GENERIC_SHORT_READ_RESPONSE_1BYTE:
+ ((u8 *)payload)[0] = PKTHDR_GET_DATA0(ph);
+ word_count++;
+ length = word_count;
+ break;
+ case MIPI_DSI_RX_DCS_LONG_READ_RESPONSE:
+ case MIPI_DSI_RX_GENERIC_LONG_READ_RESPONSE:
+ word_count = PKTHDR_GET_WC(ph);
+ if (word_count > length) {
+ dev_err(dsim->dev, "invalid receive buffer length\n");
+ return -EINVAL;
+ }
+
+ length = word_count;
+
+ while (word_count >= 4) {
+ pl = dsim_read(dsim, DSIM_RXFIFO);
+ ((u8 *)payload)[0] = pl & 0xff;
+ ((u8 *)payload)[1] = (pl >> 8) & 0xff;
+ ((u8 *)payload)[2] = (pl >> 16) & 0xff;
+ ((u8 *)payload)[3] = (pl >> 24) & 0xff;
+ payload += 4;
+ word_count -= 4;
+ }
+
+ if (word_count > 0) {
+ pl = dsim_read(dsim, DSIM_RXFIFO);
+
+ switch (word_count) {
+ case 3:
+ ((u8 *)payload)[2] = (pl >> 16) & 0xff;
+ fallthrough;
+ case 2:
+ ((u8 *)payload)[1] = (pl >> 8) & 0xff;
+ fallthrough;
+ case 1:
+ ((u8 *)payload)[0] = pl & 0xff;
+ break;
+ }
+ }
+
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return length;
+}
+
+static ssize_t sec_mipi_dsim_host_transfer(struct mipi_dsi_host *host,
+ const struct mipi_dsi_msg *msg)
+{
+ int ret;
+ ssize_t bytes = 0;
+ bool use_lpm;
+ struct mipi_dsi_packet packet;
+ struct sec_mipi_dsim *dsim = to_sec_mipi_dsim(host);
+
+ if ((msg->rx_buf && !msg->rx_len) || (msg->rx_len && !msg->rx_buf))
+ return -EINVAL;
+
+ ret = mipi_dsi_create_packet(&packet, msg);
+ if (ret) {
+ dev_err(dsim->dev, "failed to create dsi packet: %d\n", ret);
+ return ret;
+ }
+
+ /* need to read data from peripheral */
+ if (unlikely(msg->rx_buf))
+ reinit_completion(&dsim->rx_done);
+
+ /* config LPM for CMD TX */
+ use_lpm = msg->flags & MIPI_DSI_MSG_USE_LPM ? true : false;
+ sec_mipi_dsim_config_cmd_lpm(dsim, use_lpm);
+
+ if (packet.payload_length) { /* Long Packet case */
+ reinit_completion(&dsim->pl_tx_done);
+
+ /* write packet payload */
+ sec_mipi_dsim_write_pl_to_sfr_fifo(dsim,
+ packet.payload,
+ packet.payload_length);
+
+ /* write packet header */
+ sec_mipi_dsim_write_ph_to_sfr_fifo(dsim,
+ packet.header,
+ use_lpm);
+
+ ret = wait_for_completion_timeout(&dsim->ph_tx_done,
+ MIPI_FIFO_TIMEOUT);
+ if (!ret) {
+ dev_err(dsim->dev, "wait payload tx done time out\n");
+ return -EBUSY;
+ }
+ } else {
+ reinit_completion(&dsim->ph_tx_done);
+
+ /* write packet header */
+ sec_mipi_dsim_write_ph_to_sfr_fifo(dsim,
+ packet.header,
+ use_lpm);
+
+ ret = wait_for_completion_timeout(&dsim->ph_tx_done,
+ MIPI_FIFO_TIMEOUT);
+ if (!ret) {
+ dev_err(dsim->dev, "wait pkthdr tx done time out\n");
+ return -EBUSY;
+ }
+ }
+
+ /* read packet payload */
+ if (unlikely(msg->rx_buf)) {
+ ret = wait_for_completion_timeout(&dsim->rx_done,
+ MIPI_FIFO_TIMEOUT);
+ if (!ret) {
+ dev_err(dsim->dev, "wait rx done time out\n");
+ return -EBUSY;
+ }
+
+ ret = sec_mipi_dsim_read_pl_from_sfr_fifo(dsim,
+ msg->rx_buf,
+ msg->rx_len);
+ if (ret < 0)
+ return ret;
+
+ bytes = msg->rx_len;
+ } else {
+ bytes = packet.size;
+ }
+
+ return bytes;
+}
+
+static const struct mipi_dsi_host_ops sec_mipi_dsim_host_ops = {
+ .attach = sec_mipi_dsim_host_attach,
+ .detach = sec_mipi_dsim_host_detach,
+ .transfer = sec_mipi_dsim_host_transfer,
+};
+
+static int sec_mipi_dsim_bridge_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
+{
+ int ret;
+ bool attach_bridge = false;
+ struct sec_mipi_dsim *dsim = bridge->driver_private;
+ struct device *dev = dsim->dev;
+ struct device_node *np = dev->of_node;
+ struct device_node *endpoint, *remote = NULL;
+ struct drm_bridge *next = ERR_PTR(-ENODEV);
+ struct drm_encoder *encoder = dsim->encoder;
+
+ /* TODO: All bridges and planes should have already been added */
+
+ /* A panel has been found, ignor other dsi devices */
+ if (dsim->panel)
+ return 0;
+
+ /* find next bridge */
+ endpoint = of_graph_get_next_endpoint(np, NULL);
+ /* At least one endpoint should be existed */
+ if (!endpoint)
+ return -ENODEV;
+
+ while (endpoint) {
+ /* check the endpoint can attach bridge or not */
+ attach_bridge = of_property_read_bool(endpoint, "attach-bridge");
+ if (!attach_bridge) {
+ endpoint = of_graph_get_next_endpoint(np, endpoint);
+ continue;
+ }
+
+ remote = of_graph_get_remote_port_parent(endpoint);
+
+ if (!remote || !of_device_is_available(remote)) {
+ of_node_put(remote);
+ endpoint = of_graph_get_next_endpoint(np, endpoint);
+ continue;
+ }
+
+ next = of_drm_find_bridge(remote);
+ if (next) {
+ /* Found */
+ of_node_put(endpoint);
+ break;
+ }
+
+ endpoint = of_graph_get_next_endpoint(np, endpoint);
+ }
+
+ /* No workable bridge exists */
+ if (IS_ERR(next))
+ return PTR_ERR(next);
+
+ /* For the panel driver loading is after dsim bridge,
+ * defer bridge binding to wait for panel driver ready.
+ * The disadvantage of probe defer is endless probing
+ * in some cases.
+ */
+ if (!next)
+ return -EPROBE_DEFER;
+
+ /* duplicate bridges or next bridge exists */
+ WARN_ON(bridge == next || drm_bridge_get_next_bridge(bridge) || dsim->next);
+
+ dsim->next = next;
+ next->encoder = encoder;
+ ret = drm_bridge_attach(encoder, next, bridge, flags);
+ if (ret) {
+ dev_err(dev, "Unable to attach bridge %s: %d\n",
+ remote->name, ret);
+ dsim->next = NULL;
+ return ret;
+ }
+
+ return 0;
+}
+
+static int sec_mipi_dsim_config_pll(struct sec_mipi_dsim *dsim)
+{
+ int ret;
+ uint32_t pllctrl = 0, status, data_lanes_en, stop;
+
+ dsim_write(dsim, 0x8000, DSIM_PLLTMR);
+
+ /* TODO: config dp/dn swap if requires */
+
+ pllctrl |= PLLCTRL_SET_PMS(dsim->pms) | PLLCTRL_PLLEN;
+ dsim_write(dsim, pllctrl, DSIM_PLLCTRL);
+
+ ret = wait_for_completion_timeout(&dsim->pll_stable, HZ / 10);
+ if (!ret) {
+ dev_err(dsim->dev, "wait for pll stable time out\n");
+ return -EBUSY;
+ }
+
+ /* wait for clk & data lanes to go to stop state */
+ mdelay(1);
+
+ data_lanes_en = (0x1 << dsim->lanes) - 1;
+ status = dsim_read(dsim, DSIM_STATUS);
+ if (!(status & STATUS_STOPSTATECLK)) {
+ dev_err(dsim->dev, "clock is not in stop state\n");
+ return -EBUSY;
+ }
+
+ stop = STATUS_GET_STOPSTATEDAT(status);
+ if ((stop & data_lanes_en) != data_lanes_en) {
+ dev_err(dsim->dev,
+ "one or more data lanes is not in stop state\n");
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+static void sec_mipi_dsim_set_main_mode(struct sec_mipi_dsim *dsim)
+{
+ uint32_t bpp, hfp_wc, hbp_wc, hsa_wc, wc;
+ uint32_t mdresol = 0, mvporch = 0, mhporch = 0, msync = 0;
+ struct videomode *vmode = &dsim->vmode;
+
+ mdresol |= MDRESOL_SET_MAINVRESOL(vmode->vactive) |
+ MDRESOL_SET_MAINHRESOL(vmode->hactive);
+ dsim_write(dsim, mdresol, DSIM_MDRESOL);
+
+ mvporch |= MVPORCH_SET_MAINVBP(vmode->vback_porch) |
+ MVPORCH_SET_STABLEVFP(vmode->vfront_porch) |
+ MVPORCH_SET_CMDALLOW(0x0);
+ dsim_write(dsim, mvporch, DSIM_MVPORCH);
+
+ bpp = mipi_dsi_pixel_format_to_bpp(dsim->format);
+
+ /* calculate hfp & hbp word counts */
+ if (!dsim->hpar) {
+ wc = DIV_ROUND_UP(vmode->hfront_porch * (bpp >> 3),
+ dsim->lanes);
+ hfp_wc = wc > MIPI_HFP_PKT_OVERHEAD ?
+ wc - MIPI_HFP_PKT_OVERHEAD : vmode->hfront_porch;
+ wc = DIV_ROUND_UP(vmode->hback_porch * (bpp >> 3),
+ dsim->lanes);
+ hbp_wc = wc > MIPI_HBP_PKT_OVERHEAD ?
+ wc - MIPI_HBP_PKT_OVERHEAD : vmode->hback_porch;
+ } else {
+ hfp_wc = dsim->hpar->hfp_wc;
+ hbp_wc = dsim->hpar->hbp_wc;
+ }
+
+ mhporch |= MHPORCH_SET_MAINHFP(hfp_wc) |
+ MHPORCH_SET_MAINHBP(hbp_wc);
+
+ dsim_write(dsim, mhporch, DSIM_MHPORCH);
+
+ /* calculate hsa word counts */
+ if (!dsim->hpar) {
+ wc = DIV_ROUND_UP(vmode->hsync_len * (bpp >> 3),
+ dsim->lanes);
+ hsa_wc = wc > MIPI_HSA_PKT_OVERHEAD ?
+ wc - MIPI_HSA_PKT_OVERHEAD : vmode->hsync_len;
+ } else
+ hsa_wc = dsim->hpar->hsa_wc;
+
+ msync |= MSYNC_SET_MAINVSA(vmode->vsync_len) |
+ MSYNC_SET_MAINHSA(hsa_wc);
+
+ dsim_write(dsim, msync, DSIM_MSYNC);
+}
+
+static void sec_mipi_dsim_config_dpi(struct sec_mipi_dsim *dsim)
+{
+ uint32_t config = 0, rgb_status = 0, data_lanes_en;
+
+ if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO)
+ rgb_status &= ~RGB_STATUS_CMDMODE_INSEL;
+ else
+ rgb_status |= RGB_STATUS_CMDMODE_INSEL;
+
+ dsim_write(dsim, rgb_status, DSIM_RGB_STATUS);
+
+ if (dsim->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) {
+ config |= CONFIG_NON_CONTINUOUS_CLOCK_LANE;
+ if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO)
+ config |= CONFIG_CLKLANE_STOP_START;
+ }
+
+ if (dsim->mode_flags & MIPI_DSI_MODE_VSYNC_FLUSH)
+ config |= CONFIG_MFLUSH_VS;
+
+ /* disable EoT packets in HS mode */
+ if (dsim->mode_flags & MIPI_DSI_MODE_NO_EOT_PACKET)
+ config |= CONFIG_EOT_R03;
+
+ if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO) {
+ config |= CONFIG_VIDEOMODE;
+
+ if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_BURST)
+ config |= CONFIG_BURSTMODE;
+
+ else if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)
+ config |= CONFIG_SYNCINFORM;
+
+ if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_AUTO_VERT)
+ config |= CONFIG_AUTOMODE;
+
+ if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_HSE)
+ config |= CONFIG_HSEDISABLEMODE;
+
+ if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_NO_HFP)
+ config |= CONFIG_HFPDISABLEMODE;
+
+ if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_NO_HBP)
+ config |= CONFIG_HBPDISABLEMODE;
+
+ if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_NO_HSA)
+ config |= CONFIG_HSADISABLEMODE;
+ }
+
+ config |= CONFIG_SET_MAINVC(dsim->channel);
+
+ switch (dsim->format) {
+ case MIPI_DSI_FMT_RGB565:
+ config |= dsim->mode_flags & MIPI_DSI_MODE_VIDEO ?
+ CONFIG_SET_MAINPIXFORMAT(0x4) :
+ CONFIG_SET_MAINPIXFORMAT(0x3);
+ break;
+ case MIPI_DSI_FMT_RGB666_PACKED:
+ if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO)
+ config |= CONFIG_SET_MAINPIXFORMAT(0x5);
+ break;
+ case MIPI_DSI_FMT_RGB666:
+ config |= CONFIG_SET_MAINPIXFORMAT(0x6);
+ break;
+ case MIPI_DSI_FMT_RGB888:
+ config |= CONFIG_SET_MAINPIXFORMAT(0x7);
+ break;
+ default:
+ config |= CONFIG_SET_MAINPIXFORMAT(0x7);
+ break;
+ }
+
+ /* config data lanes number and enable lanes */
+ data_lanes_en = (0x1 << dsim->lanes) - 1;
+ config |= CONFIG_SET_NUMOFDATLANE(dsim->lanes - 1);
+ config |= CONFIG_SET_LANEEN(0x1 | data_lanes_en << 1);
+
+ dsim_write(dsim, config, DSIM_CONFIG);
+}
+
+static void sec_mipi_dsim_config_dphy(struct sec_mipi_dsim *dsim)
+{
+ struct sec_mipi_dsim_dphy_timing key = { 0 };
+ const struct sec_mipi_dsim_dphy_timing *match = NULL;
+ const struct sec_mipi_dsim_plat_data *pdata = dsim->pdata;
+ uint32_t phytiming = 0, phytiming1 = 0, phytiming2 = 0, timeout = 0;
+ uint32_t hactive, vactive;
+ struct videomode *vmode = &dsim->vmode;
+ struct drm_display_mode mode;
+
+ key.bit_clk = DIV_ROUND_CLOSEST_ULL(dsim->bit_clk, 1000);
+
+ /* '1280x720@60Hz' mode with 2 data lanes
+ * requires special fine tuning for DPHY
+ * TIMING config according to the tests.
+ */
+ if (dsim->lanes == 2) {
+ hactive = vmode->hactive;
+ vactive = vmode->vactive;
+
+ if (hactive == 1280 && vactive == 720) {
+ memset(&mode, 0x0, sizeof(mode));
+ drm_display_mode_from_videomode(vmode, &mode);
+
+ if (drm_mode_vrefresh(&mode) == 60)
+ key.bit_clk >>= 1;
+ }
+ }
+
+ match = bsearch(&key, pdata->dphy_timing, pdata->num_dphy_timing,
+ sizeof(struct sec_mipi_dsim_dphy_timing),
+ pdata->dphy_timing_cmp);
+ if (WARN_ON(!match))
+ return;
+
+ phytiming |= PHYTIMING_SET_M_TLPXCTL(match->lpx) |
+ PHYTIMING_SET_M_THSEXITCTL(match->hs_exit);
+ dsim_write(dsim, phytiming, DSIM_PHYTIMING);
+
+ phytiming1 |= PHYTIMING1_SET_M_TCLKPRPRCTL(match->clk_prepare) |
+ PHYTIMING1_SET_M_TCLKZEROCTL(match->clk_zero) |
+ PHYTIMING1_SET_M_TCLKPOSTCTL(match->clk_post) |
+ PHYTIMING1_SET_M_TCLKTRAILCTL(match->clk_trail);
+ dsim_write(dsim, phytiming1, DSIM_PHYTIMING1);
+
+ phytiming2 |= PHYTIMING2_SET_M_THSPRPRCTL(match->hs_prepare) |
+ PHYTIMING2_SET_M_THSZEROCTL(match->hs_zero) |
+ PHYTIMING2_SET_M_THSTRAILCTL(match->hs_trail);
+ dsim_write(dsim, phytiming2, DSIM_PHYTIMING2);
+
+ timeout |= TIMEOUT_SET_BTAOUT(0xff) |
+ TIMEOUT_SET_LPDRTOUT(0xff);
+ dsim_write(dsim, timeout, DSIM_TIMEOUT);
+}
+
+static void sec_mipi_dsim_init_fifo_pointers(struct sec_mipi_dsim *dsim)
+{
+ uint32_t fifoctrl, fifo_ptrs;
+
+ fifoctrl = dsim_read(dsim, DSIM_FIFOCTRL);
+
+ fifo_ptrs = FIFOCTRL_NINITRX |
+ FIFOCTRL_NINITSFR |
+ FIFOCTRL_NINITI80 |
+ FIFOCTRL_NINITSUB |
+ FIFOCTRL_NINITMAIN;
+
+ fifoctrl &= ~fifo_ptrs;
+ dsim_write(dsim, fifoctrl, DSIM_FIFOCTRL);
+ udelay(500);
+
+ fifoctrl |= fifo_ptrs;
+ dsim_write(dsim, fifoctrl, DSIM_FIFOCTRL);
+ udelay(500);
+}
+
+static void sec_mipi_dsim_config_clkctrl(struct sec_mipi_dsim *dsim)
+{
+ uint32_t clkctrl = 0, data_lanes_en;
+ uint32_t byte_clk, esc_prescaler;
+
+ clkctrl |= CLKCTRL_TXREQUESTHSCLK;
+
+ /* using 1.5Gbps PHY */
+ clkctrl |= CLKCTRL_DPHY_SEL_1P5G;
+
+ clkctrl |= CLKCTRL_ESCCLKEN;
+
+ clkctrl &= ~CLKCTRL_PLLBYPASS;
+
+ clkctrl |= CLKCTRL_BYTECLKSRC_DPHY_PLL;
+
+ clkctrl |= CLKCTRL_BYTECLKEN;
+
+ data_lanes_en = (0x1 << dsim->lanes) - 1;
+ clkctrl |= CLKCTRL_SET_LANEESCCLKEN(0x1 | data_lanes_en << 1);
+
+ /* calculate esc prescaler from byte clock:
+ * EscClk = ByteClk / EscPrescaler;
+ */
+ byte_clk = dsim->bit_clk >> 3;
+ esc_prescaler = DIV_ROUND_UP(byte_clk, MAX_ESC_CLK_FREQ);
+ clkctrl |= CLKCTRL_SET_ESCPRESCALER(esc_prescaler);
+
+ dsim_write(dsim, clkctrl, DSIM_CLKCTRL);
+}
+
+static void sec_mipi_dsim_set_standby(struct sec_mipi_dsim *dsim,
+ bool standby)
+{
+ uint32_t mdresol = 0;
+
+ mdresol = dsim_read(dsim, DSIM_MDRESOL);
+
+ if (standby)
+ mdresol |= MDRESOL_MAINSTANDBY;
+ else
+ mdresol &= ~MDRESOL_MAINSTANDBY;
+
+ dsim_write(dsim, mdresol, DSIM_MDRESOL);
+}
+
+struct dsim_pll_pms *sec_mipi_dsim_calc_pmsk(struct sec_mipi_dsim *dsim)
+{
+ uint32_t p, m, s;
+ uint32_t best_p = 0, best_m = 0, best_s = 0;
+ uint32_t fin, fout;
+ uint32_t s_pow_2, raw_s;
+ uint64_t mfin, pfvco, pfout, psfout;
+ uint32_t delta, best_delta = ~0U;
+ struct dsim_pll_pms *pll_pms;
+ struct device *dev = dsim->dev;
+ const struct sec_mipi_dsim_plat_data *pdata = dsim->pdata;
+ struct sec_mipi_dsim_pll dpll = *pdata->dphy_pll;
+ struct sec_mipi_dsim_range *prange = &dpll.p;
+ struct sec_mipi_dsim_range *mrange = &dpll.m;
+ struct sec_mipi_dsim_range *srange = &dpll.s;
+ struct sec_mipi_dsim_range *krange = &dpll.k;
+ struct sec_mipi_dsim_range *fvco_range = &dpll.fvco;
+ struct sec_mipi_dsim_range *fpref_range = &dpll.fpref;
+ struct sec_mipi_dsim_range pr_new = *prange;
+ struct sec_mipi_dsim_range sr_new = *srange;
+
+ pll_pms = devm_kzalloc(dev, sizeof(*pll_pms), GFP_KERNEL);
+ if (!pll_pms) {
+ dev_err(dev, "Unable to allocate 'pll_pms'\n");
+ return ERR_PTR(-ENOMEM);
+ }
+
+ fout = dsim->bit_clk;
+ fin = dsim->pref_clk;
+
+ /* TODO: ignore 'k' for PMS calculation,
+ * only use 'p', 'm' and 's' to generate
+ * the requested PLL output clock.
+ */
+ krange->min = 0;
+ krange->max = 0;
+
+ /* narrow 'p' range via 'Fpref' limitation:
+ * Fpref : [2MHz ~ 30MHz] (Fpref = Fin / p)
+ */
+ prange->min = max(prange->min, DIV_ROUND_UP(fin, fpref_range->max));
+ prange->max = min(prange->max, fin / fpref_range->min);
+
+ /* narrow 'm' range via 'Fvco' limitation:
+ * Fvco: [1050MHz ~ 2100MHz] (Fvco = ((m + k / 65536) * Fin) / p)
+ * So, m = Fvco * p / Fin and Fvco > Fin;
+ */
+ pfvco = (uint64_t)fvco_range->min * prange->min;
+ mrange->min = max_t(uint32_t, mrange->min,
+ DIV_ROUND_UP_ULL(pfvco, fin));
+ pfvco = (uint64_t)fvco_range->max * prange->max;
+ mrange->max = min_t(uint32_t, mrange->max,
+ DIV_ROUND_UP_ULL(pfvco, fin));
+
+ dev_dbg(dev, "p: min = %u, max = %u, "
+ "m: min = %u, max = %u, "
+ "s: min = %u, max = %u\n",
+ prange->min, prange->max, mrange->min,
+ mrange->max, srange->min, srange->max);
+
+ /* first determine 'm', then can determine 'p', last determine 's' */
+ for (m = mrange->min; m <= mrange->max; m++) {
+ /* p = m * Fin / Fvco */
+ mfin = (uint64_t)m * fin;
+ pr_new.min = max_t(uint32_t, prange->min,
+ DIV_ROUND_UP_ULL(mfin, fvco_range->max));
+ pr_new.max = min_t(uint32_t, prange->max,
+ (mfin / fvco_range->min));
+
+ if (pr_new.max < pr_new.min || pr_new.min < prange->min)
+ continue;
+
+ for (p = pr_new.min; p <= pr_new.max; p++) {
+ /* s = order_pow_of_two((m * Fin) / (p * Fout)) */
+ pfout = (uint64_t)p * fout;
+ raw_s = DIV_ROUND_CLOSEST_ULL(mfin, pfout);
+
+ s_pow_2 = rounddown_pow_of_two(raw_s);
+ sr_new.min = max_t(uint32_t, srange->min,
+ order_base_2(s_pow_2));
+
+ s_pow_2 = roundup_pow_of_two(DIV_ROUND_CLOSEST_ULL(mfin, pfout));
+ sr_new.max = min_t(uint32_t, srange->max,
+ order_base_2(s_pow_2));
+
+ if (sr_new.max < sr_new.min || sr_new.min < srange->min)
+ continue;
+
+ for (s = sr_new.min; s <= sr_new.max; s++) {
+ /* fout = m * Fin / (p * 2^s) */
+ psfout = pfout * (1 << s);
+ delta = abs(psfout - mfin);
+ if (delta < best_delta) {
+ best_p = p;
+ best_m = m;
+ best_s = s;
+ best_delta = delta;
+ }
+ }
+ }
+ }
+
+ if (best_delta == ~0U) {
+ devm_kfree(dev, pll_pms);
+ return ERR_PTR(-EINVAL);
+ }
+
+ pll_pms->p = best_p;
+ pll_pms->m = best_m;
+ pll_pms->s = best_s;
+
+ dev_dbg(dev, "fout = %u, fin = %u, m = %u, "
+ "p = %u, s = %u, best_delta = %u\n",
+ fout, fin, pll_pms->m, pll_pms->p, pll_pms->s, best_delta);
+
+ return pll_pms;
+}
+
+int sec_mipi_dsim_check_pll_out(void *driver_private,
+ const struct drm_display_mode *mode)
+{
+ int bpp;
+ uint32_t pix_clk, bit_clk;
+ struct sec_mipi_dsim *dsim = driver_private;
+ const struct sec_mipi_dsim_plat_data *pdata = dsim->pdata;
+ const struct dsim_hblank_par *hpar;
+ const struct dsim_pll_pms *pmsk;
+
+ bpp = mipi_dsi_pixel_format_to_bpp(dsim->format);
+ if (bpp < 0)
+ return -EINVAL;
+
+ pix_clk = mode->clock;
+ bit_clk = DIV_ROUND_UP(pix_clk * bpp, dsim->lanes);
+
+ if (bit_clk * 1000 > pdata->max_data_rate) {
+ dev_err(dsim->dev,
+ "reuest bit clk freq exceeds lane's maximum value\n");
+ return -EINVAL;
+ }
+
+ dsim->pix_clk = pix_clk;
+ dsim->bit_clk = bit_clk;
+ dsim->hpar = NULL;
+
+ pmsk = sec_mipi_dsim_calc_pmsk(dsim);
+ if (IS_ERR(pmsk)) {
+ dev_err(dsim->dev,
+ "failed to get pmsk for: fin = %u, fout = %u\n",
+ dsim->pref_clk, dsim->bit_clk);
+ return -EINVAL;
+ }
+
+ dsim->pms = PLLCTRL_SET_P(pmsk->p) |
+ PLLCTRL_SET_M(pmsk->m) |
+ PLLCTRL_SET_S(pmsk->s);
+
+ /* free 'dsim_pll_pms' structure data which is
+ * allocated in 'sec_mipi_dsim_calc_pmsk()'.
+ */
+ devm_kfree(dsim->dev, (void *)pmsk);
+
+ if (dsim->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) {
+ hpar = sec_mipi_dsim_get_hblank_par(mode->name,
+ drm_mode_vrefresh(mode),
+ dsim->lanes);
+ dsim->hpar = hpar;
+ if (!hpar)
+ dev_dbg(dsim->dev, "no pre-exist hpar can be used\n");
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(sec_mipi_dsim_check_pll_out);
+
+static
+struct drm_crtc *sec_mipi_dsim_get_new_crtc(struct sec_mipi_dsim *dsim,
+ struct drm_atomic_state *state)
+{
+ struct drm_encoder *encoder = dsim->encoder;
+ struct drm_connector *connector;
+ struct drm_connector_state *conn_state;
+
+ connector = drm_atomic_get_new_connector_for_encoder(state, encoder);
+ if (!connector)
+ return NULL;
+
+ conn_state = drm_atomic_get_new_connector_state(state, connector);
+ if (!conn_state)
+ return NULL;
+
+ return conn_state->crtc;
+}
+
+static void
+sec_mipi_dsim_bridge_atomic_enable(struct drm_bridge *bridge,
+ struct drm_bridge_state *old_bridge_state)
+{
+ int ret;
+ struct sec_mipi_dsim *dsim = bridge->driver_private;
+ struct drm_atomic_state *old_state = old_bridge_state->base.state;
+ struct drm_crtc *crtc;
+ struct drm_crtc_state *old_crtc_state;
+
+ /* At this moment, the dsim bridge's preceding encoder has
+ * already been enabled. So the dsim can be configed here
+ */
+
+ crtc = sec_mipi_dsim_get_new_crtc(dsim, old_state);
+ if (!crtc) {
+ dev_err(dsim->dev, "bridge is enabling without CRTC\n");
+ return;
+ }
+
+ old_crtc_state = drm_atomic_get_old_crtc_state(old_state, crtc);
+ /* Don't do enablement operation if we're coming back from PSR. */
+ if (old_crtc_state && old_crtc_state->self_refresh_active &&
+ dsim->enabled)
+ return;
+
+ if (dsim->enabled)
+ return;
+
+ /* config main display mode */
+ sec_mipi_dsim_set_main_mode(dsim);
+
+ /* config dsim dpi */
+ sec_mipi_dsim_config_dpi(dsim);
+
+ /* config dsim pll */
+ ret = sec_mipi_dsim_config_pll(dsim);
+ if (ret) {
+ dev_err(dsim->dev, "dsim pll config failed: %d\n", ret);
+ return;
+ }
+
+ /* config dphy timings */
+ sec_mipi_dsim_config_dphy(dsim);
+
+ /* initialize FIFO pointers */
+ sec_mipi_dsim_init_fifo_pointers(dsim);
+
+ /* prepare panel if exists */
+ if (dsim->panel) {
+ ret = drm_panel_prepare(dsim->panel);
+ if (unlikely(ret)) {
+ dev_err(dsim->dev, "panel prepare failed: %d\n", ret);
+ return;
+ }
+ }
+
+ /* config esc clock, byte clock and etc */
+ sec_mipi_dsim_config_clkctrl(dsim);
+
+ /* enable panel if exists */
+ if (dsim->panel) {
+ ret = drm_panel_enable(dsim->panel);
+ if (unlikely(ret)) {
+ dev_err(dsim->dev, "panel enable failed: %d\n", ret);
+ goto panel_unprepare;
+ }
+ }
+
+ /* enable data transfer of dsim */
+ sec_mipi_dsim_set_standby(dsim, true);
+
+ dsim->enabled = true;
+
+ return;
+
+panel_unprepare:
+ ret = drm_panel_unprepare(dsim->panel);
+ if (unlikely(ret))
+ dev_err(dsim->dev, "panel unprepare failed: %d\n", ret);
+}
+
+static void sec_mipi_dsim_disable_clkctrl(struct sec_mipi_dsim *dsim)
+{
+ uint32_t clkctrl;
+
+ clkctrl = dsim_read(dsim, DSIM_CLKCTRL);
+
+ clkctrl &= ~CLKCTRL_TXREQUESTHSCLK;
+
+ clkctrl &= ~CLKCTRL_ESCCLKEN;
+
+ clkctrl &= ~CLKCTRL_BYTECLKEN;
+
+ dsim_write(dsim, clkctrl, DSIM_CLKCTRL);
+}
+
+static void sec_mipi_dsim_disable_pll(struct sec_mipi_dsim *dsim)
+{
+ uint32_t pllctrl;
+
+ pllctrl = dsim_read(dsim, DSIM_PLLCTRL);
+
+ pllctrl &= ~PLLCTRL_PLLEN;
+
+ dsim_write(dsim, pllctrl, DSIM_PLLCTRL);
+}
+
+static void
+sec_mipi_dsim_bridge_atomic_disable(struct drm_bridge *bridge,
+ struct drm_bridge_state *old_bridge_state)
+{
+ int ret;
+ struct sec_mipi_dsim *dsim = bridge->driver_private;
+ struct drm_atomic_state *old_state = old_bridge_state->base.state;
+ struct drm_crtc *crtc;
+ struct drm_crtc_state *new_crtc_state;
+
+ crtc = sec_mipi_dsim_get_new_crtc(dsim, old_state);
+ /* No CRTC means we're doing a full shutdown. */
+ if (!crtc)
+ goto disable;
+
+ new_crtc_state = drm_atomic_get_new_crtc_state(old_state, crtc);
+ /* Don't do disablement operation if we're entering PSR. */
+ if (!new_crtc_state || new_crtc_state->self_refresh_active)
+ return;
+
+disable:
+ if (!dsim->enabled)
+ return;
+
+ /* disable panel if exists */
+ if (dsim->panel) {
+ ret = drm_panel_disable(dsim->panel);
+ if (unlikely(ret))
+ dev_err(dsim->dev, "panel disable failed: %d\n", ret);
+ }
+
+ /* disable data transfer of dsim */
+ sec_mipi_dsim_set_standby(dsim, false);
+
+ /* disable esc clock & byte clock */
+ sec_mipi_dsim_disable_clkctrl(dsim);
+
+ /* disable dsim pll */
+ sec_mipi_dsim_disable_pll(dsim);
+
+ /* unprepare panel if exists */
+ if (dsim->panel) {
+ ret = drm_panel_unprepare(dsim->panel);
+ if (unlikely(ret))
+ dev_err(dsim->dev, "panel unprepare failed: %d\n", ret);
+ }
+
+ dsim->enabled = false;
+}
+
+static void sec_mipi_dsim_bridge_mode_set(struct drm_bridge *bridge,
+ const struct drm_display_mode *mode,
+ const struct drm_display_mode *adjusted_mode)
+{
+ struct sec_mipi_dsim *dsim = bridge->driver_private;
+
+ /* This hook is called when the display pipe is completely
+ * off. And since the pm runtime is implemented, the dsim
+ * hardware cannot be accessed at this moment. So move all
+ * the mode_set config to ->enable() hook.
+ * And this hook is called only when 'mode_changed' is true,
+ * so it is called not every time atomic commit.
+ */
+
+ drm_display_mode_to_videomode(adjusted_mode, &dsim->vmode);
+}
+
+static u32 *sec_mipi_dsim_atomic_get_input_bus_fmts(struct drm_bridge *bridge,
+ struct drm_bridge_state *bridge_state,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state,
+ u32 output_fmt,
+ unsigned int *num_input_fmts)
+{
+ u32 *input_fmts;
+ struct sec_mipi_dsim *dsim = bridge->driver_private;
+
+ /* use dsi format to determine output bus format
+ * if it is passed with MEDIA_BUS_FMT_FIXED
+ */
+ if (output_fmt == MEDIA_BUS_FMT_FIXED) {
+ switch (dsim->format) {
+ case MIPI_DSI_FMT_RGB888:
+ output_fmt = MEDIA_BUS_FMT_RGB888_1X24;
+ break;
+ case MIPI_DSI_FMT_RGB666:
+ output_fmt = MEDIA_BUS_FMT_RGB666_1X24_CPADHI;
+ break;
+ case MIPI_DSI_FMT_RGB666_PACKED:
+ output_fmt = MEDIA_BUS_FMT_RGB666_1X18;
+ break;
+ case MIPI_DSI_FMT_RGB565:
+ output_fmt = MEDIA_BUS_FMT_RGB565_1X16;
+ break;
+ default:
+ return NULL;
+ }
+ } else {
+ /* check if the output format matches with
+ * DSI device requested format
+ */
+ switch (dsim->format) {
+ case MIPI_DSI_FMT_RGB888:
+ if (output_fmt != MEDIA_BUS_FMT_RGB888_1X24)
+ return NULL;
+ break;
+ case MIPI_DSI_FMT_RGB666:
+ if (output_fmt != MEDIA_BUS_FMT_RGB666_1X24_CPADHI)
+ return NULL;
+ break;
+ case MIPI_DSI_FMT_RGB666_PACKED:
+ if (output_fmt != MEDIA_BUS_FMT_RGB666_1X18)
+ return NULL;
+ break;
+ case MIPI_DSI_FMT_RGB565:
+ if (output_fmt != MEDIA_BUS_FMT_RGB565_1X16)
+ return NULL;
+ break;
+ default:
+ return NULL;
+ }
+ }
+
+ /* Since dsim cannot do any color conversion, so the
+ * bus format output by mipi dsi should just be
+ * propagated to the bus format recieved by mipi dsi
+ * directly.
+ */
+ input_fmts = drm_atomic_helper_bridge_propagate_bus_fmt(bridge,
+ bridge_state,
+ crtc_state,
+ conn_state,
+ output_fmt,
+ num_input_fmts);
+
+ return input_fmts;
+}
+
+static int sec_mipi_dsim_bridge_atomic_check(struct drm_bridge *bridge,
+ struct drm_bridge_state *bridge_state,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode;
+ struct drm_bus_cfg *input_bus_cfg = &bridge_state->input_bus_cfg;
+ struct sec_mipi_dsim *dsim = bridge->driver_private;
+
+ /*
+ * If the DSI mode is not video mode, it implies
+ * that the connector is self refresh aware.
+ */
+ if (!(dsim->mode_flags & MIPI_DSI_MODE_VIDEO))
+ conn_state->self_refresh_aware = true;
+
+ /* seems unnecessary to check the input bus format
+ * again, since during the negotiation process, it
+ * has already been checked
+ */
+ if (bridge_state->output_bus_cfg.format == MEDIA_BUS_FMT_FIXED)
+ bridge_state->output_bus_cfg.format = bridge_state->input_bus_cfg.format;
+
+ /* adjust Hsync and Vsync polarities for drm display mode,
+ * since DSIM can only accept active high Hsync and Vsync
+ * signals
+ */
+ if (adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC) {
+ adjusted_mode->flags &= ~DRM_MODE_FLAG_NHSYNC;
+ adjusted_mode->flags |= DRM_MODE_FLAG_PHSYNC;
+ }
+
+ if (adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC) {
+ adjusted_mode->flags &= ~DRM_MODE_FLAG_NVSYNC;
+ adjusted_mode->flags |= DRM_MODE_FLAG_PVSYNC;
+ }
+
+ /* adjust input DE and pixel data sample polarities for
+ * input bus_flags, since DSIM can only accept active
+ * high DE and sample pixel data at clock postive edge
+ */
+ if (input_bus_cfg->flags & DRM_BUS_FLAG_DE_LOW ||
+ !(input_bus_cfg->flags & DRM_BUS_FLAG_DE_HIGH)) {
+ input_bus_cfg->flags &= ~DRM_BUS_FLAG_DE_LOW;
+ input_bus_cfg->flags |= DRM_BUS_FLAG_DE_HIGH;
+ }
+
+ if (input_bus_cfg->flags & DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE ||
+ !(input_bus_cfg->flags & DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE)) {
+ input_bus_cfg->flags &= ~DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE;
+ input_bus_cfg->flags |= DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE;
+ }
+
+ /* workaround for CEA standard mode "1280x720@60" "1920x1080p24"
+ * display on 4 data lanes with Non-burst with sync
+ * pulse DSI mode, since use the standard horizontal
+ * timings cannot display correctly. And this code
+ * cannot be put into the dsim Bridge's mode_fixup,
+ * since the DSI device lane number change always
+ * happens after that.
+ */
+ if (!strcmp(adjusted_mode->name, "1280x720") &&
+ drm_mode_vrefresh(adjusted_mode) == 60 &&
+ dsim->lanes == 4 &&
+ dsim->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) {
+ adjusted_mode->hsync_start += 2;
+ adjusted_mode->hsync_end += 2;
+ adjusted_mode->htotal += 2;
+ }
+
+ if (!strcmp(adjusted_mode->name, "1920x1080") &&
+ drm_mode_vrefresh(adjusted_mode) == 24 &&
+ dsim->lanes == 4 &&
+ dsim->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) {
+ adjusted_mode->hsync_start += 2;
+ adjusted_mode->hsync_end += 2;
+ adjusted_mode->htotal += 2;
+ }
+
+ return 0;
+}
+
+static const struct drm_bridge_funcs sec_mipi_dsim_bridge_funcs = {
+ .atomic_check = sec_mipi_dsim_bridge_atomic_check,
+ .atomic_reset = drm_atomic_helper_bridge_reset,
+ .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
+ .atomic_get_input_bus_fmts = sec_mipi_dsim_atomic_get_input_bus_fmts,
+ .attach = sec_mipi_dsim_bridge_attach,
+ .atomic_enable = sec_mipi_dsim_bridge_atomic_enable,
+ .atomic_disable = sec_mipi_dsim_bridge_atomic_disable,
+ .mode_set = sec_mipi_dsim_bridge_mode_set,
+};
+
+void sec_mipi_dsim_suspend(struct device *dev)
+{
+ /* TODO: add dsim reset */
+}
+EXPORT_SYMBOL(sec_mipi_dsim_suspend);
+
+void sec_mipi_dsim_resume(struct device *dev)
+{
+ struct sec_mipi_dsim *dsim = dev_get_drvdata(dev);
+
+ sec_mipi_dsim_irq_init(dsim);
+
+ /* TODO: add dsim de-reset */
+}
+EXPORT_SYMBOL(sec_mipi_dsim_resume);
+
+static void __maybe_unused sec_mipi_dsim_irq_mask(struct sec_mipi_dsim *dsim,
+ int irq_idx)
+{
+ uint32_t intmsk;
+
+ intmsk = dsim_read(dsim, DSIM_INTMSK);
+
+ switch (irq_idx) {
+ case PLLSTABLE:
+ intmsk |= INTMSK_MSKPLLSTABLE;
+ break;
+ case SWRSTRELEASE:
+ intmsk |= INTMSK_MSKSWRELEASE;
+ break;
+ case SFRPLFIFOEMPTY:
+ intmsk |= INTMSK_MSKSFRPLFIFOEMPTY;
+ break;
+ case SFRPHFIFOEMPTY:
+ intmsk |= INTMSK_MSKSFRPHFIFOEMPTY;
+ break;
+ case FRAMEDONE:
+ intmsk |= INTMSK_MSKFRAMEDONE;
+ break;
+ case LPDRTOUT:
+ intmsk |= INTMSK_MSKLPDRTOUT;
+ break;
+ case TATOUT:
+ intmsk |= INTMSK_MSKTATOUT;
+ break;
+ case RXDATDONE:
+ intmsk |= INTMSK_MSKRXDATDONE;
+ break;
+ case RXTE:
+ intmsk |= INTMSK_MSKRXTE;
+ break;
+ case RXACK:
+ intmsk |= INTMSK_MSKRXACK;
+ break;
+ default:
+ /* unsupported irq */
+ return;
+ }
+
+ writel(intmsk, dsim->base + DSIM_INTMSK);
+}
+
+static void sec_mipi_dsim_irq_unmask(struct sec_mipi_dsim *dsim,
+ int irq_idx)
+{
+ uint32_t intmsk;
+
+ intmsk = dsim_read(dsim, DSIM_INTMSK);
+
+ switch (irq_idx) {
+ case PLLSTABLE:
+ intmsk &= ~INTMSK_MSKPLLSTABLE;
+ break;
+ case SWRSTRELEASE:
+ intmsk &= ~INTMSK_MSKSWRELEASE;
+ break;
+ case SFRPLFIFOEMPTY:
+ intmsk &= ~INTMSK_MSKSFRPLFIFOEMPTY;
+ break;
+ case SFRPHFIFOEMPTY:
+ intmsk &= ~INTMSK_MSKSFRPHFIFOEMPTY;
+ break;
+ case FRAMEDONE:
+ intmsk &= ~INTMSK_MSKFRAMEDONE;
+ break;
+ case LPDRTOUT:
+ intmsk &= ~INTMSK_MSKLPDRTOUT;
+ break;
+ case TATOUT:
+ intmsk &= ~INTMSK_MSKTATOUT;
+ break;
+ case RXDATDONE:
+ intmsk &= ~INTMSK_MSKRXDATDONE;
+ break;
+ case RXTE:
+ intmsk &= ~INTMSK_MSKRXTE;
+ break;
+ case RXACK:
+ intmsk &= ~INTMSK_MSKRXACK;
+ break;
+ default:
+ /* unsupported irq */
+ return;
+ }
+
+ dsim_write(dsim, intmsk, DSIM_INTMSK);
+}
+
+/* write 1 clear irq */
+static void sec_mipi_dsim_irq_clear(struct sec_mipi_dsim *dsim,
+ int irq_idx)
+{
+ uint32_t intsrc = 0;
+
+ switch (irq_idx) {
+ case PLLSTABLE:
+ intsrc |= INTSRC_PLLSTABLE;
+ break;
+ case SWRSTRELEASE:
+ intsrc |= INTSRC_SWRSTRELEASE;
+ break;
+ case SFRPLFIFOEMPTY:
+ intsrc |= INTSRC_SFRPLFIFOEMPTY;
+ break;
+ case SFRPHFIFOEMPTY:
+ intsrc |= INTSRC_SFRPHFIFOEMPTY;
+ break;
+ case FRAMEDONE:
+ intsrc |= INTSRC_FRAMEDONE;
+ break;
+ case LPDRTOUT:
+ intsrc |= INTSRC_LPDRTOUT;
+ break;
+ case TATOUT:
+ intsrc |= INTSRC_TATOUT;
+ break;
+ case RXDATDONE:
+ intsrc |= INTSRC_RXDATDONE;
+ break;
+ case RXTE:
+ intsrc |= INTSRC_RXTE;
+ break;
+ case RXACK:
+ intsrc |= INTSRC_RXACK;
+ break;
+ default:
+ /* unsupported irq */
+ return;
+ }
+
+ dsim_write(dsim, intsrc, DSIM_INTSRC);
+}
+
+static void sec_mipi_dsim_irq_init(struct sec_mipi_dsim *dsim)
+{
+ sec_mipi_dsim_irq_unmask(dsim, PLLSTABLE);
+ sec_mipi_dsim_irq_unmask(dsim, SWRSTRELEASE);
+
+ if (dsim->panel) {
+ sec_mipi_dsim_irq_unmask(dsim, SFRPLFIFOEMPTY);
+ sec_mipi_dsim_irq_unmask(dsim, SFRPHFIFOEMPTY);
+ sec_mipi_dsim_irq_unmask(dsim, LPDRTOUT);
+ sec_mipi_dsim_irq_unmask(dsim, TATOUT);
+ sec_mipi_dsim_irq_unmask(dsim, RXDATDONE);
+ sec_mipi_dsim_irq_unmask(dsim, RXTE);
+ sec_mipi_dsim_irq_unmask(dsim, RXACK);
+ }
+}
+
+static irqreturn_t sec_mipi_dsim_irq_handler(int irq, void *data)
+{
+ uint32_t intsrc, status;
+ struct sec_mipi_dsim *dsim = data;
+
+ intsrc = dsim_read(dsim, DSIM_INTSRC);
+ status = dsim_read(dsim, DSIM_STATUS);
+
+ if (WARN_ON(!intsrc)) {
+ dev_err(dsim->dev, "interrupt is not from dsim\n");
+ return IRQ_NONE;
+ }
+
+ if (WARN_ON(!(intsrc & INTSRC_MASK))) {
+ dev_warn(dsim->dev, "unenable irq happens: %#x\n", intsrc);
+ /* just clear irqs */
+ dsim_write(dsim, intsrc, DSIM_INTSRC);
+ return IRQ_NONE;
+ }
+
+ if (intsrc & INTSRC_PLLSTABLE) {
+ WARN_ON(!(status & STATUS_PLLSTABLE));
+ sec_mipi_dsim_irq_clear(dsim, PLLSTABLE);
+ complete(&dsim->pll_stable);
+ }
+
+ if (intsrc & INTSRC_SWRSTRELEASE)
+ sec_mipi_dsim_irq_clear(dsim, SWRSTRELEASE);
+
+ if (intsrc & INTSRC_SFRPLFIFOEMPTY) {
+ sec_mipi_dsim_irq_clear(dsim, SFRPLFIFOEMPTY);
+ complete(&dsim->pl_tx_done);
+ }
+
+ if (intsrc & INTSRC_SFRPHFIFOEMPTY) {
+ sec_mipi_dsim_irq_clear(dsim, SFRPHFIFOEMPTY);
+ complete(&dsim->ph_tx_done);
+ }
+
+ if (WARN_ON(intsrc & INTSRC_LPDRTOUT)) {
+ sec_mipi_dsim_irq_clear(dsim, LPDRTOUT);
+ dev_warn(dsim->dev, "LP RX timeout\n");
+ }
+
+ if (WARN_ON(intsrc & INTSRC_TATOUT)) {
+ sec_mipi_dsim_irq_clear(dsim, TATOUT);
+ dev_warn(dsim->dev, "Turns around Acknowledge timeout\n");
+ }
+
+ if (intsrc & INTSRC_RXDATDONE) {
+ sec_mipi_dsim_irq_clear(dsim, RXDATDONE);
+ complete(&dsim->rx_done);
+ }
+
+ if (intsrc & INTSRC_RXTE) {
+ sec_mipi_dsim_irq_clear(dsim, RXTE);
+ dev_dbg(dsim->dev, "TE Rx trigger received\n");
+ }
+
+ if (intsrc & INTSRC_RXACK) {
+ sec_mipi_dsim_irq_clear(dsim, RXACK);
+ dev_dbg(dsim->dev, "ACK Rx trigger received\n");
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int sec_mipi_dsim_connector_get_modes(struct drm_connector *connector)
+{
+ struct sec_mipi_dsim *dsim = conn_to_sec_mipi_dsim(connector);
+
+ if (WARN_ON(!dsim->panel))
+ return -ENODEV;
+
+ return drm_panel_get_modes(dsim->panel, connector);
+}
+
+static const struct drm_connector_helper_funcs
+ sec_mipi_dsim_connector_helper_funcs = {
+ .get_modes = sec_mipi_dsim_connector_get_modes,
+};
+
+static enum drm_connector_status
+ sec_mipi_dsim_connector_detect(struct drm_connector *connector,
+ bool force)
+{
+ /* TODO: add support later */
+
+ return connector_status_connected;
+}
+
+static const struct drm_connector_funcs sec_mipi_dsim_connector_funcs = {
+ .detect = sec_mipi_dsim_connector_detect,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = drm_connector_cleanup,
+ .reset = drm_atomic_helper_connector_reset,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+int sec_mipi_dsim_bind(struct device *dev, struct device *master, void *data,
+ struct drm_encoder *encoder, void __iomem *base,
+ int irq, const struct sec_mipi_dsim_plat_data *pdata)
+{
+ int ret, version;
+ struct drm_device *drm_dev = data;
+ struct drm_bridge *bridge;
+ struct drm_connector *connector;
+ struct sec_mipi_dsim *dsim;
+ struct device_node *node = NULL;
+
+ dev_dbg(dev, "sec-dsim bridge bind begin\n");
+
+ dsim = devm_kzalloc(dev, sizeof(*dsim), GFP_KERNEL);
+ if (!dsim) {
+ dev_err(dev, "Unable to allocate 'dsim'\n");
+ return -ENOMEM;
+ }
+
+ dsim->dev = dev;
+ dsim->base = base;
+ dsim->pdata = pdata;
+ dsim->encoder = encoder;
+
+ dsim->dsi_host.ops = &sec_mipi_dsim_host_ops;
+ dsim->dsi_host.dev = dev;
+
+ dev_set_drvdata(dev, dsim);
+
+ pm_runtime_get_sync(dev);
+ version = dsim_read(dsim, DSIM_VERSION);
+ WARN_ON(version != pdata->version);
+ pm_runtime_put_sync(dev);
+
+ dev_info(dev, "version number is %#x\n", version);
+
+ /* set suitable rate for phy ref clock */
+ ret = sec_mipi_dsim_set_pref_rate(dsim);
+ if (ret) {
+ dev_err(dev, "failed to set pll ref clock rate\n");
+ return ret;
+ }
+
+ ret = devm_request_irq(dev, irq, sec_mipi_dsim_irq_handler,
+ 0, dev_name(dev), dsim);
+ if (ret) {
+ dev_err(dev, "failed to request dsim irq: %d\n", ret);
+ return ret;
+ }
+
+ init_completion(&dsim->pll_stable);
+ init_completion(&dsim->ph_tx_done);
+ init_completion(&dsim->pl_tx_done);
+ init_completion(&dsim->rx_done);
+
+ /* Initialize and attach sec dsim bridge */
+ bridge = devm_kzalloc(dev, sizeof(*bridge), GFP_KERNEL);
+ if (!bridge) {
+ dev_err(dev, "Unable to allocate 'bridge'\n");
+ return -ENOMEM;
+ }
+
+ /* mipi dsi host needs to be registered before bridge attach, since:
+ * 1. Have Panel
+ * The 'mipi_dsi_host_register' will allocate a mipi_dsi_device
+ * if the dsi host node has a panel child node in DTB. And dsi
+ * host ->attach() will be called in panel's probe().
+ *
+ * 2. Have Bridge
+ * The dsi host ->attach() will be called through the below
+ * 'drm_bridge_attach()' which will attach next bridge in a
+ * chain.
+ */
+ ret = mipi_dsi_host_register(&dsim->dsi_host);
+ if (ret) {
+ dev_err(dev, "Unable to register mipi dsi host: %d\n", ret);
+ return ret;
+ }
+
+ dsim->bridge = bridge;
+ bridge->driver_private = dsim;
+ bridge->funcs = &sec_mipi_dsim_bridge_funcs;
+ bridge->of_node = dev->of_node;
+ bridge->encoder = encoder;
+
+ /* attach sec dsim bridge and its next bridge if exists */
+ ret = drm_bridge_attach(encoder, bridge, NULL, 0);
+ if (ret) {
+ dev_err(dev, "Failed to attach bridge: %s\n", dev_name(dev));
+
+ /* no bridge exists, so defer probe to wait
+ * panel driver loading
+ */
+ if (ret != -EPROBE_DEFER) {
+ for_each_available_child_of_node(dev->of_node, node) {
+ /* skip nodes without reg property */
+ if (!of_find_property(node, "reg", NULL))
+ continue;
+
+ /* error codes only ENODEV or EPROBE_DEFER */
+ dsim->panel = of_drm_find_panel(node);
+ if (!IS_ERR(dsim->panel))
+ goto panel;
+
+ ret = PTR_ERR(dsim->panel);
+ }
+ }
+
+ mipi_dsi_host_unregister(&dsim->dsi_host);
+ return ret;
+ }
+
+panel:
+ if (dsim->panel) {
+ /* A panel has been attached */
+ connector = &dsim->connector;
+
+ drm_connector_helper_add(connector,
+ &sec_mipi_dsim_connector_helper_funcs);
+ ret = drm_connector_init(drm_dev, connector,
+ &sec_mipi_dsim_connector_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+ if (ret)
+ goto host_unregister;
+
+ /* TODO */
+ connector->dpms = DRM_MODE_DPMS_OFF;
+
+ ret = drm_connector_attach_encoder(connector, encoder);
+ if (ret)
+ goto cleanup_connector;
+ }
+
+ dev_dbg(dev, "sec-dsim bridge bind end\n");
+
+ return 0;
+
+cleanup_connector:
+ drm_connector_cleanup(connector);
+host_unregister:
+ mipi_dsi_host_unregister(&dsim->dsi_host);
+ return ret;
+}
+EXPORT_SYMBOL(sec_mipi_dsim_bind);
+
+void sec_mipi_dsim_unbind(struct device *dev, struct device *master, void *data)
+{
+ struct sec_mipi_dsim *dsim = dev_get_drvdata(dev);
+
+ if (dsim->panel)
+ drm_connector_cleanup(&dsim->connector);
+
+ mipi_dsi_host_unregister(&dsim->dsi_host);
+}
+EXPORT_SYMBOL(sec_mipi_dsim_unbind);
+
+MODULE_DESCRIPTION("Samsung MIPI DSI Host Controller bridge driver");
+MODULE_AUTHOR("Fancy Fang <chen.fang@nxp.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/bridge/synopsys/Kconfig b/drivers/gpu/drm/bridge/synopsys/Kconfig
index 21a1be3ced0f..e398b58c996d 100644
--- a/drivers/gpu/drm/bridge/synopsys/Kconfig
+++ b/drivers/gpu/drm/bridge/synopsys/Kconfig
@@ -25,6 +25,17 @@ config DRM_DW_HDMI_I2S_AUDIO
Support the I2S Audio interface which is part of the Synopsys
Designware HDMI block.
+config DRM_DW_HDMI_GP_AUDIO
+ tristate "Synopsys Designware GP Audio interface"
+ depends on DRM_DW_HDMI && SND
+ select SND_PCM
+ select SND_PCM_ELD
+ select SND_PCM_IEC958
+ help
+ Support the GP Audio interface which is part of the Synopsys
+ Designware HDMI block. This is used in conjunction with
+ the i.MX865 HDMI driver.
+
config DRM_DW_HDMI_CEC
tristate "Synopsis Designware CEC interface"
depends on DRM_DW_HDMI
diff --git a/drivers/gpu/drm/bridge/synopsys/Makefile b/drivers/gpu/drm/bridge/synopsys/Makefile
index 91d746ad5de1..ce715562e9e5 100644
--- a/drivers/gpu/drm/bridge/synopsys/Makefile
+++ b/drivers/gpu/drm/bridge/synopsys/Makefile
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o
obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o
+obj-$(CONFIG_DRM_DW_HDMI_GP_AUDIO) += dw-hdmi-gp-audio.o
obj-$(CONFIG_DRM_DW_HDMI_I2S_AUDIO) += dw-hdmi-i2s-audio.o
obj-$(CONFIG_DRM_DW_HDMI_CEC) += dw-hdmi-cec.o
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c
index 70ab4fbdc23e..061fe6f47077 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-cec.c
@@ -18,6 +18,8 @@
#include "dw-hdmi-cec.h"
+static u8 cec_saved_regs[5];
+
enum {
HDMI_IH_CEC_STAT0 = 0x0106,
HDMI_IH_MUTE_CEC_STAT0 = 0x0186,
@@ -308,11 +310,44 @@ static int dw_hdmi_cec_remove(struct platform_device *pdev)
return 0;
}
+static int __maybe_unused dw_hdmi_cec_resume(struct device *dev)
+{
+ struct dw_hdmi_cec *cec = dev_get_drvdata(dev);
+
+ /* Restore logical address and interrupt status/mask register */
+ dw_hdmi_write(cec, cec_saved_regs[0], HDMI_CEC_ADDR_L);
+ dw_hdmi_write(cec, cec_saved_regs[1], HDMI_CEC_ADDR_H);
+ dw_hdmi_write(cec, cec_saved_regs[2], HDMI_CEC_POLARITY);
+ dw_hdmi_write(cec, cec_saved_regs[3], HDMI_CEC_MASK);
+ dw_hdmi_write(cec, cec_saved_regs[4], HDMI_IH_MUTE_CEC_STAT0);
+
+ return 0;
+}
+
+static int __maybe_unused dw_hdmi_cec_suspend(struct device *dev)
+{
+ struct dw_hdmi_cec *cec = dev_get_drvdata(dev);
+
+ /* store logical address and interrupt status/mask register */
+ cec_saved_regs[0] = dw_hdmi_read(cec, HDMI_CEC_ADDR_L);
+ cec_saved_regs[1] = dw_hdmi_read(cec, HDMI_CEC_ADDR_H);
+ cec_saved_regs[2] = dw_hdmi_read(cec, HDMI_CEC_POLARITY);
+ cec_saved_regs[3] = dw_hdmi_read(cec, HDMI_CEC_MASK);
+ cec_saved_regs[4] = dw_hdmi_read(cec, HDMI_IH_MUTE_CEC_STAT0);
+
+ return 0;
+}
+
+static const struct dev_pm_ops dw_hdmi_cec_pm = {
+ SET_SYSTEM_SLEEP_PM_OPS(dw_hdmi_cec_suspend, dw_hdmi_cec_resume)
+};
+
static struct platform_driver dw_hdmi_cec_driver = {
.probe = dw_hdmi_cec_probe,
.remove = dw_hdmi_cec_remove,
.driver = {
.name = "dw-hdmi-cec",
+ .pm = &dw_hdmi_cec_pm,
},
};
module_platform_driver(dw_hdmi_cec_driver);
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-gp-audio.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-gp-audio.c
new file mode 100644
index 000000000000..43624ea6a937
--- /dev/null
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-gp-audio.c
@@ -0,0 +1,201 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright 2020 NXP
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
+#include <drm/bridge/dw_hdmi.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_connector.h>
+
+#include <sound/hdmi-codec.h>
+#include <sound/asoundef.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_drm_eld.h>
+#include <sound/pcm_iec958.h>
+#include <sound/dmaengine_pcm.h>
+
+#include "dw-hdmi-audio.h"
+
+#define DRIVER_NAME "dw-hdmi-gp-audio"
+#define DRV_NAME "hdmi-gp-audio"
+
+struct snd_dw_hdmi {
+ struct dw_hdmi_audio_data data;
+ struct platform_device *audio_pdev;
+ unsigned int pos;
+};
+
+struct dw_hdmi_channel_conf {
+ u8 conf1;
+ u8 ca;
+};
+
+/*
+ * The default mapping of ALSA channels to HDMI channels and speaker
+ * allocation bits. Note that we can't do channel remapping here -
+ * channels must be in the same order.
+ *
+ * Mappings for alsa-lib pcm/surround*.conf files:
+ *
+ * Front Sur4.0 Sur4.1 Sur5.0 Sur5.1 Sur7.1
+ * Channels 2 4 6 6 6 8
+ *
+ * Our mapping from ALSA channel to CEA686D speaker name and HDMI channel:
+ *
+ * Number of ALSA channels
+ * ALSA Channel 2 3 4 5 6 7 8
+ * 0 FL:0 = = = = = =
+ * 1 FR:1 = = = = = =
+ * 2 FC:3 RL:4 LFE:2 = = =
+ * 3 RR:5 RL:4 FC:3 = =
+ * 4 RR:5 RL:4 = =
+ * 5 RR:5 = =
+ * 6 RC:6 =
+ * 7 RLC/FRC RLC/FRC
+ */
+static struct dw_hdmi_channel_conf default_hdmi_channel_config[7] = {
+ { 0x03, 0x00 }, /* FL,FR */
+ { 0x0b, 0x02 }, /* FL,FR,FC */
+ { 0x33, 0x08 }, /* FL,FR,RL,RR */
+ { 0x37, 0x09 }, /* FL,FR,LFE,RL,RR */
+ { 0x3f, 0x0b }, /* FL,FR,LFE,FC,RL,RR */
+ { 0x7f, 0x0f }, /* FL,FR,LFE,FC,RL,RR,RC */
+ { 0xff, 0x13 }, /* FL,FR,LFE,FC,RL,RR,[FR]RC,[FR]LC */
+};
+
+static int audio_hw_params(struct device *dev, void *data,
+ struct hdmi_codec_daifmt *daifmt,
+ struct hdmi_codec_params *params)
+{
+ struct snd_dw_hdmi *dw = dev_get_drvdata(dev);
+ int ret = 0;
+ u8 ca;
+
+ dw_hdmi_set_sample_rate(dw->data.hdmi, params->sample_rate);
+
+ ca = default_hdmi_channel_config[params->channels - 2].ca;
+
+ dw_hdmi_set_channel_count(dw->data.hdmi, params->channels);
+ dw_hdmi_set_channel_allocation(dw->data.hdmi, ca);
+
+ dw_hdmi_set_sample_non_pcm(dw->data.hdmi,
+ params->iec.status[0] & IEC958_AES0_NONAUDIO);
+ dw_hdmi_set_sample_width(dw->data.hdmi, params->sample_width);
+
+ return ret;
+}
+
+static void audio_shutdown(struct device *dev, void *data)
+{
+}
+
+static int audio_mute_stream(struct device *dev, void *data,
+ bool enable, int direction)
+{
+ struct snd_dw_hdmi *dw = dev_get_drvdata(dev);
+ int ret = 0;
+
+ if (!enable)
+ dw_hdmi_audio_enable(dw->data.hdmi);
+ else
+ dw_hdmi_audio_disable(dw->data.hdmi);
+
+ return ret;
+}
+
+static int audio_get_eld(struct device *dev, void *data,
+ u8 *buf, size_t len)
+{
+ struct dw_hdmi_audio_data *audio = data;
+ u8 *eld;
+
+ eld = audio->get_eld(audio->hdmi);
+ if (eld)
+ memcpy(buf, eld, min_t(size_t, MAX_ELD_BYTES, len));
+ else
+ /* Pass en empty ELD if connector not available */
+ memset(buf, 0, len);
+
+ return 0;
+}
+
+static int audio_hook_plugged_cb(struct device *dev, void *data,
+ hdmi_codec_plugged_cb fn,
+ struct device *codec_dev)
+{
+ struct snd_dw_hdmi *dw = dev_get_drvdata(dev);
+
+ return dw_hdmi_set_plugged_cb(dw->data.hdmi, fn, codec_dev);
+}
+
+static const struct hdmi_codec_ops audio_codec_ops = {
+ .hw_params = audio_hw_params,
+ .audio_shutdown = audio_shutdown,
+ .mute_stream = audio_mute_stream,
+ .get_eld = audio_get_eld,
+ .hook_plugged_cb = audio_hook_plugged_cb,
+};
+
+static int snd_dw_hdmi_probe(struct platform_device *pdev)
+{
+ const struct dw_hdmi_audio_data *data = pdev->dev.platform_data;
+ struct snd_dw_hdmi *dw;
+
+ struct hdmi_codec_pdata codec_data = {
+ .i2s = 1,
+ .spdif = 0,
+ .ops = &audio_codec_ops,
+ .max_i2s_channels = 8,
+ .data = (void *)data,
+ };
+
+ dw = devm_kzalloc(&pdev->dev, sizeof(*dw), GFP_KERNEL);
+ if (!dw)
+ return -ENOMEM;
+
+ dw->data = *data;
+
+ platform_set_drvdata(pdev, dw);
+
+ dw->audio_pdev = platform_device_register_data(&pdev->dev,
+ HDMI_CODEC_DRV_NAME, 1,
+ &codec_data,
+ sizeof(codec_data));
+
+ return PTR_ERR_OR_ZERO(dw->audio_pdev);
+}
+
+static int snd_dw_hdmi_remove(struct platform_device *pdev)
+{
+ struct snd_dw_hdmi *dw = platform_get_drvdata(pdev);
+
+ platform_device_unregister(dw->audio_pdev);
+
+ return 0;
+}
+
+static struct platform_driver snd_dw_hdmi_driver = {
+ .probe = snd_dw_hdmi_probe,
+ .remove = snd_dw_hdmi_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ },
+};
+
+module_platform_driver(snd_dw_hdmi_driver);
+
+MODULE_AUTHOR("Shengjiu Wang <shengjiu.wang@nxp.com>");
+MODULE_DESCRIPTION("Synopsis Designware HDMI GPA ALSA interface");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" DRIVER_NAME);
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
index 8bb403bc712a..de48ecddbb39 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.c
@@ -191,7 +191,10 @@ struct dw_hdmi {
spinlock_t audio_lock;
struct mutex audio_mutex;
+ unsigned int sample_non_pcm;
+ unsigned int sample_width;
unsigned int sample_rate;
+ unsigned int channels;
unsigned int audio_cts;
unsigned int audio_n;
bool audio_enable;
@@ -589,6 +592,8 @@ static unsigned int hdmi_compute_n(unsigned int freq, unsigned long pixel_clk)
n = 4096;
else if (pixel_clk == 74176000 || pixel_clk == 148352000)
n = 11648;
+ else if (pixel_clk == 297000000)
+ n = 3072;
else
n = 4096;
n *= mult;
@@ -601,6 +606,8 @@ static unsigned int hdmi_compute_n(unsigned int freq, unsigned long pixel_clk)
n = 17836;
else if (pixel_clk == 148352000)
n = 8918;
+ else if (pixel_clk == 297000000)
+ n = 4704;
else
n = 6272;
n *= mult;
@@ -615,6 +622,8 @@ static unsigned int hdmi_compute_n(unsigned int freq, unsigned long pixel_clk)
n = 11648;
else if (pixel_clk == 148352000)
n = 5824;
+ else if (pixel_clk == 297000000)
+ n = 5120;
else
n = 6144;
n *= mult;
@@ -660,7 +669,7 @@ static void hdmi_set_clk_regenerator(struct dw_hdmi *hdmi,
config3 = hdmi_readb(hdmi, HDMI_CONFIG3_ID);
/* Only compute CTS when using internal AHB audio */
- if (config3 & HDMI_CONFIG3_AHBAUDDMA) {
+ if ((config3 & HDMI_CONFIG3_AHBAUDDMA) || (config3 & HDMI_CONFIG3_GPAUD)) {
/*
* Compute the CTS value from the N value. Note that CTS and N
* can be up to 20 bits in total, so we need 64-bit math. Also
@@ -702,6 +711,22 @@ static void hdmi_clk_regenerator_update_pixel_clock(struct dw_hdmi *hdmi)
mutex_unlock(&hdmi->audio_mutex);
}
+void dw_hdmi_set_sample_width(struct dw_hdmi *hdmi, unsigned int width)
+{
+ mutex_lock(&hdmi->audio_mutex);
+ hdmi->sample_width = width;
+ mutex_unlock(&hdmi->audio_mutex);
+}
+EXPORT_SYMBOL_GPL(dw_hdmi_set_sample_width);
+
+void dw_hdmi_set_sample_non_pcm(struct dw_hdmi *hdmi, unsigned int non_pcm)
+{
+ mutex_lock(&hdmi->audio_mutex);
+ hdmi->sample_non_pcm = non_pcm;
+ mutex_unlock(&hdmi->audio_mutex);
+}
+EXPORT_SYMBOL_GPL(dw_hdmi_set_sample_non_pcm);
+
void dw_hdmi_set_sample_rate(struct dw_hdmi *hdmi, unsigned int rate)
{
mutex_lock(&hdmi->audio_mutex);
@@ -717,6 +742,7 @@ void dw_hdmi_set_channel_count(struct dw_hdmi *hdmi, unsigned int cnt)
u8 layout;
mutex_lock(&hdmi->audio_mutex);
+ hdmi->channels = cnt;
/*
* For >2 channel PCM audio, we need to select layout 1
@@ -757,6 +783,87 @@ static void hdmi_enable_audio_clk(struct dw_hdmi *hdmi, bool enable)
hdmi_writeb(hdmi, hdmi->mc_clkdis, HDMI_MC_CLKDIS);
}
+static void dw_hdmi_gp_audio_enable(struct dw_hdmi *hdmi)
+{
+ int sample_freq = 0x2, org_sample_freq = 0xD;
+ int ch_mask = BIT(hdmi->channels) - 1;
+
+ switch (hdmi->sample_rate) {
+ case 32000:
+ sample_freq = 0x03;
+ org_sample_freq = 0x0C;
+ break;
+ case 44100:
+ sample_freq = 0x00;
+ org_sample_freq = 0x0F;
+ break;
+ case 48000:
+ sample_freq = 0x02;
+ org_sample_freq = 0x0D;
+ break;
+ case 88200:
+ sample_freq = 0x08;
+ org_sample_freq = 0x07;
+ break;
+ case 96000:
+ sample_freq = 0x0A;
+ org_sample_freq = 0x05;
+ break;
+ case 176400:
+ sample_freq = 0x0C;
+ org_sample_freq = 0x03;
+ break;
+ case 192000:
+ sample_freq = 0x0E;
+ org_sample_freq = 0x01;
+ break;
+ default:
+ break;
+ }
+
+ hdmi_set_cts_n(hdmi, hdmi->audio_cts, hdmi->audio_n);
+ hdmi_enable_audio_clk(hdmi, true);
+
+ hdmi_writeb(hdmi, 0x1, HDMI_FC_AUDSCHNLS0);
+ hdmi_writeb(hdmi, hdmi->channels, HDMI_FC_AUDSCHNLS2);
+ hdmi_writeb(hdmi, 0x22, HDMI_FC_AUDSCHNLS3);
+ hdmi_writeb(hdmi, 0x22, HDMI_FC_AUDSCHNLS4);
+ hdmi_writeb(hdmi, 0x11, HDMI_FC_AUDSCHNLS5);
+ hdmi_writeb(hdmi, 0x11, HDMI_FC_AUDSCHNLS6);
+ hdmi_writeb(hdmi, (0x3 << 4) | sample_freq, HDMI_FC_AUDSCHNLS7);
+ hdmi_writeb(hdmi, (org_sample_freq << 4) | 0xb, HDMI_FC_AUDSCHNLS8);
+
+ hdmi_writeb(hdmi, ch_mask, HDMI_GP_CONF1);
+ hdmi_writeb(hdmi, 0x02, HDMI_GP_CONF2);
+ hdmi_writeb(hdmi, 0x01, HDMI_GP_CONF0);
+
+ hdmi_modb(hdmi, 0x3, 0x3, HDMI_FC_DATAUTO3);
+
+ /* hbr */
+ if (hdmi->sample_rate == 192000 && hdmi->channels == 8 &&
+ hdmi->sample_width == 32 && hdmi->sample_non_pcm) {
+ hdmi_modb(hdmi, 0x01, 0x01, HDMI_GP_CONF2);
+ }
+
+ if (hdmi->phy.ops->enable_audio)
+ hdmi->phy.ops->enable_audio(hdmi, hdmi->phy.data,
+ hdmi->channels,
+ hdmi->sample_width,
+ hdmi->sample_rate,
+ hdmi->sample_non_pcm);
+}
+
+static void dw_hdmi_gp_audio_disable(struct dw_hdmi *hdmi)
+{
+ hdmi_set_cts_n(hdmi, hdmi->audio_cts, 0);
+
+ hdmi_modb(hdmi, 0, 0x3, HDMI_FC_DATAUTO3);
+ if (hdmi->phy.ops->disable_audio)
+ hdmi->phy.ops->disable_audio(hdmi, hdmi->phy.data);
+
+ hdmi_enable_audio_clk(hdmi, false);
+}
+
static u8 *hdmi_audio_get_eld(struct dw_hdmi *hdmi)
{
if (!hdmi->curr_conn)
@@ -1160,6 +1267,14 @@ static void hdmi_video_packetize(struct dw_hdmi *hdmi)
HDMI_VP_PR_CD_DESIRED_PR_FACTOR_MASK);
hdmi_writeb(hdmi, val, HDMI_VP_PR_CD);
+ val = hdmi_readb(hdmi, HDMI_FC_DATAUTO3);
+ if (color_depth == 4)
+ /* disable Auto GCP when bpp 24 */
+ val &= ~0x4;
+ else
+ val |= 0x4;
+ hdmi_writeb(hdmi, val, HDMI_FC_DATAUTO3);
+
hdmi_modb(hdmi, HDMI_VP_STUFF_PR_STUFFING_STUFFING_MODE,
HDMI_VP_STUFF_PR_STUFFING_MASK, HDMI_VP_STUFF);
@@ -1359,11 +1474,19 @@ static void dw_hdmi_phy_sel_interface_control(struct dw_hdmi *hdmi, u8 enable)
void dw_hdmi_phy_reset(struct dw_hdmi *hdmi)
{
+ /* PHY reset. The reset signal is active high on Gen1 PHYs. */
+ hdmi_writeb(hdmi, 0, HDMI_MC_PHYRSTZ);
+ hdmi_writeb(hdmi, HDMI_MC_PHYRSTZ_PHYRSTZ, HDMI_MC_PHYRSTZ);
+}
+EXPORT_SYMBOL_GPL(dw_hdmi_phy_reset);
+
+void dw_hdmi_phy_gen2_reset(struct dw_hdmi *hdmi)
+{
/* PHY reset. The reset signal is active high on Gen2 PHYs. */
hdmi_writeb(hdmi, HDMI_MC_PHYRSTZ_PHYRSTZ, HDMI_MC_PHYRSTZ);
hdmi_writeb(hdmi, 0, HDMI_MC_PHYRSTZ);
}
-EXPORT_SYMBOL_GPL(dw_hdmi_phy_reset);
+EXPORT_SYMBOL_GPL(dw_hdmi_phy_gen2_reset);
void dw_hdmi_phy_i2c_set_addr(struct dw_hdmi *hdmi, u8 address)
{
@@ -1517,7 +1640,7 @@ static int hdmi_phy_configure(struct dw_hdmi *hdmi,
if (phy->has_svsret)
dw_hdmi_phy_enable_svsret(hdmi, 1);
- dw_hdmi_phy_reset(hdmi);
+ dw_hdmi_phy_gen2_reset(hdmi);
hdmi_writeb(hdmi, HDMI_MC_HEACPHY_RST_ASSERT, HDMI_MC_HEACPHY_RST);
@@ -2087,9 +2210,9 @@ static void dw_hdmi_clear_overflow(struct dw_hdmi *hdmi)
*
* The number of iterations matters and depends on the HDMI TX revision
* (and possibly on the platform). So far i.MX6Q (v1.30a), i.MX6DL
- * (v1.31a) and multiple Allwinner SoCs (v1.32a) have been identified
- * as needing the workaround, with 4 iterations for v1.30a and 1
- * iteration for others.
+ * (v1.31a), iMX865(v2.13a) and multiple Allwinner SoCs (v1.32a)
+ * have been identified as needing the workaround,
+ * with 4 iterations for v1.30a and 1 iteration for others.
* The Amlogic Meson GX SoCs (v2.01a) have been identified as needing
* the workaround with a single iteration.
* The Rockchip RK3288 SoC (v2.00a) and RK3328/RK3399 SoCs (v2.11a) have
@@ -2106,6 +2229,7 @@ static void dw_hdmi_clear_overflow(struct dw_hdmi *hdmi)
case 0x201a:
case 0x211a:
case 0x212a:
+ case 0x213a:
count = 1;
break;
default:
@@ -2863,6 +2987,14 @@ static void dw_hdmi_bridge_atomic_enable(struct drm_bridge *bridge,
hdmi->curr_conn = connector;
dw_hdmi_update_power(hdmi);
dw_hdmi_update_phy_mask(hdmi);
+
+ /* Workaround for update hdmi audio eld data when cable plugin.
+ * plugged_cb is called in bridge_detect to update eld, but eld
+ * data is not updated until get_edid. Call handle_plugged_change
+ * in bridge_atomic_enable to make sure eld data is update after
+ * edid data are read */
+ handle_plugged_change(hdmi,
+ hdmi->last_connector_result == connector_status_connected);
mutex_unlock(&hdmi->mutex);
}
@@ -3245,6 +3377,7 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev,
hdmi->plat_data = plat_data;
hdmi->dev = dev;
hdmi->sample_rate = 48000;
+ hdmi->channels = 2;
hdmi->disabled = true;
hdmi->rxsense = true;
hdmi->phy_mask = (u8)~(HDMI_PHY_HPD | HDMI_PHY_RX_SENSE);
@@ -3467,6 +3600,24 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev,
pdevinfo.size_data = sizeof(audio);
pdevinfo.dma_mask = DMA_BIT_MASK(32);
hdmi->audio = platform_device_register_full(&pdevinfo);
+ } else if (iores && config3 & HDMI_CONFIG3_GPAUD) {
+ struct dw_hdmi_audio_data audio;
+
+ audio.phys = iores->start;
+ audio.base = hdmi->regs;
+ audio.irq = irq;
+ audio.hdmi = hdmi;
+ audio.get_eld = hdmi_audio_get_eld;
+
+ hdmi->enable_audio = dw_hdmi_gp_audio_enable;
+ hdmi->disable_audio = dw_hdmi_gp_audio_disable;
+
+ pdevinfo.name = "dw-hdmi-gp-audio";
+ pdevinfo.id = PLATFORM_DEVID_NONE;
+ pdevinfo.data = &audio;
+ pdevinfo.size_data = sizeof(audio);
+ pdevinfo.dma_mask = DMA_BIT_MASK(32);
+ hdmi->audio = platform_device_register_full(&pdevinfo);
}
if (!plat_data->disable_cec && (config0 & HDMI_CONFIG0_CEC)) {
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.h b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.h
index 1999db05bc3b..99aa1c03343b 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi.h
+++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi.h
@@ -158,8 +158,17 @@
#define HDMI_FC_SPDDEVICEINF 0x1062
#define HDMI_FC_AUDSCONF 0x1063
#define HDMI_FC_AUDSSTAT 0x1064
-#define HDMI_FC_AUDSCHNLS7 0x106e
-#define HDMI_FC_AUDSCHNLS8 0x106f
+#define HDMI_FC_AUDSV 0x1065
+#define HDMI_FC_AUDSU 0x1066
+#define HDMI_FC_AUDSCHNLS0 0x1067
+#define HDMI_FC_AUDSCHNLS1 0x1068
+#define HDMI_FC_AUDSCHNLS2 0x1069
+#define HDMI_FC_AUDSCHNLS3 0x106A
+#define HDMI_FC_AUDSCHNLS4 0x106B
+#define HDMI_FC_AUDSCHNLS5 0x106C
+#define HDMI_FC_AUDSCHNLS6 0x106D
+#define HDMI_FC_AUDSCHNLS7 0x106E
+#define HDMI_FC_AUDSCHNLS8 0x106F
#define HDMI_FC_DATACH0FILL 0x1070
#define HDMI_FC_DATACH1FILL 0x1071
#define HDMI_FC_DATACH2FILL 0x1072
diff --git a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c
index 56c3fd08c6a0..e642c15587ac 100644
--- a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c
+++ b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c
@@ -12,6 +12,7 @@
#include <linux/component.h>
#include <linux/debugfs.h>
#include <linux/iopoll.h>
+#include <linux/math64.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/pm_runtime.h>
@@ -386,8 +387,6 @@ static int dw_mipi_dsi_host_detach(struct mipi_dsi_host *host,
drm_of_panel_bridge_remove(host->dev->of_node, 1, 0);
- drm_bridge_remove(&dsi->bridge);
-
return 0;
}
@@ -654,7 +653,7 @@ static void dw_mipi_dsi_init(struct dw_mipi_dsi *dsi)
* timeout clock division should be computed with the
* high speed transmission counter timeout and byte lane...
*/
- dsi_write(dsi, DSI_CLKMGR_CFG, TO_CLK_DIVISION(10) |
+ dsi_write(dsi, DSI_CLKMGR_CFG, TO_CLK_DIVISION(0) |
TX_ESC_CLK_DIVISION(esc_clk_division));
}
@@ -733,8 +732,15 @@ static u32 dw_mipi_dsi_get_hcomponent_lbcc(struct dw_mipi_dsi *dsi,
u32 hcomponent)
{
u32 frac, lbcc;
+ int bpp;
+
+ bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
+ if (bpp < 0) {
+ dev_err(dsi->dev, "failed to get bpp\n");
+ return 0;
+ }
- lbcc = hcomponent * dsi->lane_mbps * MSEC_PER_SEC / 8;
+ lbcc = div_u64((u64)hcomponent * mode->clock * bpp, dsi->lanes * 8);
frac = lbcc % mode->clock;
lbcc = lbcc / mode->clock;
@@ -1216,6 +1222,8 @@ __dw_mipi_dsi_probe(struct platform_device *pdev,
static void __dw_mipi_dsi_remove(struct dw_mipi_dsi *dsi)
{
+ drm_bridge_remove(&dsi->bridge);
+
mipi_dsi_host_unregister(&dsi->dsi_host);
pm_runtime_disable(dsi->dev);
diff --git a/drivers/gpu/drm/drm_of.c b/drivers/gpu/drm/drm_of.c
index 37c34146eea8..ac56279bfb71 100644
--- a/drivers/gpu/drm/drm_of.c
+++ b/drivers/gpu/drm/drm_of.c
@@ -100,8 +100,10 @@ void drm_of_component_match_add(struct device *master,
EXPORT_SYMBOL_GPL(drm_of_component_match_add);
/**
- * drm_of_component_probe - Generic probe function for a component based master
+ * drm_of_component_probe_with_match - Generic probe function with match
+ * entries for a component based master
* @dev: master device containing the OF node
+ * @match: component match pointer provided to store matches
* @compare_of: compare function used for matching components
* @m_ops: component master ops to be used
*
@@ -112,12 +114,12 @@ EXPORT_SYMBOL_GPL(drm_of_component_match_add);
*
* Returns zero if successful, or one of the standard error codes if it fails.
*/
-int drm_of_component_probe(struct device *dev,
+int drm_of_component_probe_with_match(struct device *dev,
+ struct component_match *match,
int (*compare_of)(struct device *, void *),
const struct component_master_ops *m_ops)
{
struct device_node *ep, *port, *remote;
- struct component_match *match = NULL;
int i;
if (!dev->of_node)
@@ -183,6 +185,29 @@ int drm_of_component_probe(struct device *dev,
return component_master_add_with_match(dev, m_ops, match);
}
+EXPORT_SYMBOL(drm_of_component_probe_with_match);
+
+/**
+ * drm_of_component_probe - Generic probe function for a component based master
+ * @dev: master device containing the OF node
+ * @compare_of: compare function used for matching components
+ * @master_ops: component master ops to be used
+ *
+ * Parse the platform device OF node and bind all the components associated
+ * with the master. Interface ports are added before the encoders in order to
+ * satisfy their .bind requirements
+ * See Documentation/devicetree/bindings/graph.txt for the bindings.
+ *
+ * Returns zero if successful, or one of the standard error codes if it fails.
+ */
+int drm_of_component_probe(struct device *dev,
+ int (*compare_of)(struct device *, void *),
+ const struct component_master_ops *m_ops)
+{
+ struct component_match *match = NULL;
+
+ return drm_of_component_probe_with_match(dev, match, compare_of, m_ops);
+}
EXPORT_SYMBOL(drm_of_component_probe);
/*
diff --git a/drivers/gpu/drm/imx/Kconfig b/drivers/gpu/drm/imx/Kconfig
index b5fa0e45a839..6580101a7729 100644
--- a/drivers/gpu/drm/imx/Kconfig
+++ b/drivers/gpu/drm/imx/Kconfig
@@ -6,10 +6,15 @@ config DRM_IMX
select DRM_GEM_CMA_HELPER
select DRM_KMS_CMA_HELPER
depends on DRM && (ARCH_MXC || ARCH_MULTIPLATFORM || COMPILE_TEST)
- depends on IMX_IPUV3_CORE
+ depends on IMX_IPUV3_CORE || IMX_DPU_CORE || IMX_LCDIF_CORE
help
enable i.MX graphics support
+config DRM_IMX_LCDIF_MUX_DISPLAY
+ tristate "Support for LCDIF mux displays"
+ select DRM_PANEL_BRIDGE
+ depends on DRM_IMX && OF && MFD_SYSCON
+
config DRM_IMX_PARALLEL_DISPLAY
tristate "Support for parallel displays"
select DRM_PANEL
@@ -34,11 +39,86 @@ config DRM_IMX_LDB
Choose this to enable the internal LVDS Display Bridge (LDB)
found on i.MX53 and i.MX6 processors.
+config DRM_IMX8QM_LDB
+ tristate "Support for i.MX8qm LVDS displays"
+ depends on DRM_IMX && DRM_FSL_IMX_LVDS_BRIDGE
+ depends on PHY_MIXEL_LVDS
+ help
+ Choose this to enable the internal LVDS Display Bridge (LDB)
+ found on i.MX8qm processors.
+
+config DRM_IMX8QXP_LDB
+ tristate "Support for i.MX8qxp LVDS displays"
+ depends on DRM_IMX && DRM_FSL_IMX_LVDS_BRIDGE
+ depends on PHY_MIXEL_LVDS_COMBO
+ help
+ Choose this to enable the internal LVDS Display Bridge (LDB)
+ found on i.MX8qxp processors.
+
+config DRM_IMX8MP_LDB
+ tristate "Support for i.MX8mp LVDS displays"
+ depends on DRM_IMX && DRM_FSL_IMX_LVDS_BRIDGE
+ depends on PHY_FSL_IMX8MP_LVDS
+ help
+ Choose this to enable the internal LVDS Display Bridge (LDB)
+ found on i.MX8mp processors.
+
+config DRM_IMX93_LDB
+ tristate "Support for i.MX93 LVDS displays"
+ depends on DRM_IMX && DRM_FSL_IMX_LVDS_BRIDGE
+ depends on PHY_FSL_IMX8MP_LVDS
+ help
+ Choose this to enable the internal LVDS Display Bridge (LDB)
+ found on i.MX93 processors.
+
+config DRM_IMX93_PARALLEL_DISPLAY_FORMAT
+ tristate "Support for i.MX93 parallel display format"
+ select DRM_PANEL_BRIDGE
+ depends on DRM_IMX && OF && MFD_SYSCON
+ help
+ Choose this to enable the internal parallel display format
+ configuration found on i.MX93 processors.
+
+config DRM_IMX_IPUV3
+ tristate
+ depends on DRM_IMX
+ depends on IMX_IPUV3_CORE
+ default y if DRM_IMX=y
+ default m if DRM_IMX=m
+
+config IMX8MP_HDMI_PAVI
+ tristate "NXP i.MX8MP HDMI Audio Video (PVI/PAI)"
+ help
+ Choose this if you want to use HDMI PAI/PVI on i.MX8MP.
+
+config DRM_IMX_DW_MIPI_DSI
+ tristate "Freescale i.MX DRM Synopsys DesignWare MIPI DSI"
+ select DRM_DW_MIPI_DSI
+ depends on DRM_IMX
+ help
+ Choose this if you want to use Synopsys DesignWare MIPI DSI on i.MX93.
+
config DRM_IMX_HDMI
tristate "Freescale i.MX DRM HDMI"
select DRM_DW_HDMI
+ select IMX8MP_HDMI_PAVI
depends on DRM_IMX && OF
help
- Choose this if you want to use HDMI on i.MX6.
+ Choose this if you want to use HDMI on i.MX6/i.MX8.
+
+config DRM_IMX_SEC_DSIM
+ tristate "Support for Samsung MIPI DSIM displays"
+ depends on DRM_IMX
+ select MFD_SYSCON
+ select DRM_SEC_MIPI_DSIM
+ help
+ Choose this to enable the internal SEC MIPI DSIM controller
+ found on i.MX platform.
+source "drivers/gpu/drm/imx/dcnano/Kconfig"
+source "drivers/gpu/drm/imx/dpu/Kconfig"
source "drivers/gpu/drm/imx/dcss/Kconfig"
+source "drivers/gpu/drm/imx/mhdp/Kconfig"
+source "drivers/gpu/drm/imx/ipuv3/Kconfig"
+source "drivers/gpu/drm/imx/lcdif/Kconfig"
+source "drivers/gpu/drm/imx/lcdifv3/Kconfig"
diff --git a/drivers/gpu/drm/imx/Makefile b/drivers/gpu/drm/imx/Makefile
index b644deffe948..e35d08773f65 100644
--- a/drivers/gpu/drm/imx/Makefile
+++ b/drivers/gpu/drm/imx/Makefile
@@ -1,12 +1,27 @@
# SPDX-License-Identifier: GPL-2.0
-imxdrm-objs := imx-drm-core.o ipuv3-crtc.o ipuv3-plane.o
+imxdrm-objs := imx-drm-core.o
obj-$(CONFIG_DRM_IMX) += imxdrm.o
+obj-$(CONFIG_DRM_IMX_LCDIF_MUX_DISPLAY) += lcdif-mux-display.o
obj-$(CONFIG_DRM_IMX_PARALLEL_DISPLAY) += parallel-display.o
obj-$(CONFIG_DRM_IMX_TVE) += imx-tve.o
obj-$(CONFIG_DRM_IMX_LDB) += imx-ldb.o
+obj-$(CONFIG_DRM_IMX8QM_LDB) += imx8qm-ldb.o
+obj-$(CONFIG_DRM_IMX8QXP_LDB) += imx8qxp-ldb.o
+obj-$(CONFIG_DRM_IMX8MP_LDB) += imx8mp-ldb.o
+obj-$(CONFIG_DRM_IMX93_LDB) += imx93-ldb.o
+obj-$(CONFIG_DRM_IMX93_PARALLEL_DISPLAY_FORMAT) += imx93-parallel-disp-fmt.o
+obj-$(CONFIG_DRM_IMX_DCNANO) += dcnano/
+obj-$(CONFIG_DRM_IMX_DPU) += dpu/
+obj-$(CONFIG_DRM_IMX_DW_MIPI_DSI) += dw_mipi_dsi-imx.o
+obj-$(CONFIG_DRM_IMX_IPUV3) += ipuv3/
obj-$(CONFIG_DRM_IMX_HDMI) += dw_hdmi-imx.o
+obj-$(CONFIG_DRM_IMX_SEC_DSIM) += sec_mipi_dsim-imx.o
+obj-$(CONFIG_IMX8MP_HDMI_PAVI) += imx8mp-hdmi-pavi.o
obj-$(CONFIG_DRM_IMX_DCSS) += dcss/
+obj-$(CONFIG_DRM_IMX_CDNS_MHDP) += mhdp/
+obj-$(CONFIG_DRM_IMX_LCDIF) += lcdif/
+obj-$(CONFIG_DRM_IMX_LCDIFV3) += lcdifv3/
diff --git a/drivers/gpu/drm/imx/dcnano/Kconfig b/drivers/gpu/drm/imx/dcnano/Kconfig
new file mode 100644
index 000000000000..ec3ba3ad7b00
--- /dev/null
+++ b/drivers/gpu/drm/imx/dcnano/Kconfig
@@ -0,0 +1,10 @@
+config DRM_IMX_DCNANO
+ tristate "DRM support for NXP i.MX DCNANO Graphics"
+ select DRM_KMS_HELPER
+ select VIDEOMODE_HELPERS
+ select DRM_GEM_CMA_HELPER
+ select DRM_KMS_CMA_HELPER
+ depends on DRM && OF && ARCH_MXC
+ depends on COMMON_CLK
+ help
+ enable NXP i.MX DCNANO graphics support
diff --git a/drivers/gpu/drm/imx/dcnano/Makefile b/drivers/gpu/drm/imx/dcnano/Makefile
new file mode 100644
index 000000000000..b08d6712861f
--- /dev/null
+++ b/drivers/gpu/drm/imx/dcnano/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+
+imx-dcnano-drm-objs := dcnano-crtc.o dcnano-drv.o dcnano-kms.o dcnano-plane.o
+
+obj-$(CONFIG_DRM_IMX_DCNANO) += imx-dcnano-drm.o
diff --git a/drivers/gpu/drm/imx/dcnano/dcnano-crtc.c b/drivers/gpu/drm/imx/dcnano/dcnano-crtc.c
new file mode 100644
index 000000000000..bc256c2d861e
--- /dev/null
+++ b/drivers/gpu/drm/imx/dcnano/dcnano-crtc.c
@@ -0,0 +1,488 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/*
+ * Copyright 2020,2021 NXP
+ */
+
+#include <linux/clk.h>
+#include <linux/irq.h>
+#include <linux/pm_runtime.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_atomic_state_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_print.h>
+#include <drm/drm_vblank.h>
+
+#include "dcnano-drv.h"
+#include "dcnano-reg.h"
+
+#define DCNANO_CRTC_PLL_MIN_RATE 271500000
+#define DCNANO_CRTC_PLL_MAX_RATE 792000000
+
+#define DCNANO_CRTC_PLL_MIN_DIV 1
+#define DCNANO_CRTC_PLL_MAX_DIV 64
+
+#define dcnano_crtc_dbg(crtc, fmt, ...) \
+ drm_dbg_kms((crtc)->dev, "[CRTC:%d:%s] " fmt, \
+ (crtc)->base.id, (crtc)->name, ##__VA_ARGS__)
+
+#define dcnano_crtc_err(crtc, fmt, ...) \
+ drm_err((crtc)->dev, "[CRTC:%d:%s] " fmt, \
+ (crtc)->base.id, (crtc)->name, ##__VA_ARGS__)
+
+static inline struct dcnano_dev *crtc_to_dcnano_dev(struct drm_crtc *crtc)
+{
+ return to_dcnano_dev(crtc->dev);
+}
+
+static void dcnano_crtc_mode_set_nofb_dpi(struct drm_crtc *crtc)
+{
+ struct dcnano_dev *dcnano = crtc_to_dcnano_dev(crtc);
+ struct drm_display_mode *adj = &crtc->state->adjusted_mode;
+ u32 val;
+
+ /* select output bus */
+ dcnano_write(dcnano, DCNANO_DBICONFIG, DBICFG_BUS_OUTPUT_SEL_DPI);
+
+ /* set bus format */
+ dcnano_write(dcnano, DCNANO_DPICONFIG, DPICFG_DATA_FORMAT_D24);
+
+ /* horizontal timing */
+ val = HDISPLAY_END(adj->crtc_hdisplay) |
+ HDISPLAY_TOTAL(adj->crtc_htotal);
+ dcnano_write(dcnano, DCNANO_HDISPLAY, val);
+
+ val = HSYNC_START(adj->crtc_hsync_start) |
+ HSYNC_END(adj->crtc_hsync_end) | HSYNC_PULSE_ENABLE;
+ if (adj->flags & DRM_MODE_FLAG_PHSYNC)
+ val |= HSYNC_POL_POSITIVE;
+ else
+ val |= HSYNC_POL_NEGATIVE;
+ dcnano_write(dcnano, DCNANO_HSYNC, val);
+
+ /* vertical timing */
+ val = VDISPLAY_END(adj->crtc_vdisplay) |
+ VDISPLAY_TOTAL(adj->crtc_vtotal);
+ dcnano_write(dcnano, DCNANO_VDISPLAY, val);
+
+ val = VSYNC_START(adj->crtc_vsync_start) |
+ VSYNC_END(adj->crtc_vsync_end) | VSYNC_PULSE_ENABLE;
+ if (adj->flags & DRM_MODE_FLAG_PVSYNC)
+ val |= VSYNC_POL_POSITIVE;
+ else
+ val |= VSYNC_POL_NEGATIVE;
+ dcnano_write(dcnano, DCNANO_VSYNC, val);
+
+ /* panel configuration */
+ val = PANELCFG_DE_ENABLE | PANELCFG_DE_POL_POSITIVE |
+ PANELCFG_DATA_ENABLE | PANELCFG_DATA_POL_POSITIVE |
+ PANELCFG_CLOCK_ENABLE | PANELCFG_CLOCK_POL_POSITIVE |
+ PANELCFG_SEQUENCING_SOFTWARE;
+ dcnano_write(dcnano, DCNANO_PANELCONFIG, val);
+}
+
+static bool dcnano_crtc_pll_clock_rate_is_valid(unsigned long pll_clk_rate)
+{
+ return pll_clk_rate >= DCNANO_CRTC_PLL_MIN_RATE &&
+ pll_clk_rate <= DCNANO_CRTC_PLL_MAX_RATE;
+}
+
+static unsigned long
+dcnano_crtc_find_pll_clock_rate(struct drm_crtc *crtc,
+ const struct drm_display_mode *mode)
+{
+ struct dcnano_dev *dcnano = crtc_to_dcnano_dev(crtc);
+ unsigned long pll_clk_rate, rounded_pll_clk_rate;
+ int i;
+
+ for (i = DCNANO_CRTC_PLL_MIN_DIV; i <= DCNANO_CRTC_PLL_MAX_DIV; i++) {
+ pll_clk_rate = mode->clock * 1000 * i;
+
+ if (!dcnano_crtc_pll_clock_rate_is_valid(pll_clk_rate))
+ continue;
+
+ rounded_pll_clk_rate = clk_round_rate(dcnano->pll_clk,
+ pll_clk_rate);
+ if (rounded_pll_clk_rate != pll_clk_rate) {
+ dcnano_crtc_dbg(crtc,
+ "rounded pll clock rate %lu, expected %lu\n",
+ rounded_pll_clk_rate, pll_clk_rate);
+ continue;
+ }
+
+ dcnano_crtc_dbg(crtc, "find pll clock rate %lu with div %d\n",
+ pll_clk_rate, i);
+
+ return pll_clk_rate;
+ }
+
+ dcnano_crtc_dbg(crtc, "failed to find pll clock rate\n");
+
+ return 0;
+}
+
+static void dcnano_crtc_set_pixel_clock(struct drm_crtc *crtc)
+{
+ struct dcnano_dev *dcnano = crtc_to_dcnano_dev(crtc);
+ struct drm_display_mode *adj = &crtc->state->adjusted_mode;
+ struct clk *parent;
+ unsigned long pixel_clk_rate = adj->crtc_clock * 1000;
+ unsigned long pll_clk_rate;
+ int ret;
+
+ parent = clk_get_parent(dcnano->pixel_clk);
+ if (!parent) {
+ dcnano_crtc_err(crtc, "%s: no pixel clock's parent\n", __func__);
+ } else if (IS_ERR(parent)) {
+ ret = PTR_ERR(parent);
+ dcnano_crtc_err(crtc,
+ "%s: failed to get pixel clock's parent: %d\n",
+ __func__, ret);
+ return;
+ }
+
+ pll_clk_rate = dcnano_crtc_find_pll_clock_rate(crtc, adj);
+ if (pll_clk_rate == 0)
+ dcnano_crtc_err(crtc, "%s: failed to find pll clock rate\n",
+ __func__);
+
+ ret = clk_set_rate(dcnano->pll_clk, pll_clk_rate);
+ if (ret)
+ dcnano_crtc_err(crtc, "%s: failed to set pll clock rate: %d\n",
+ __func__, ret);
+
+ /* FIXME: The rate of pixel clock's parent is pixel clock rate. */
+ ret = clk_set_rate(parent, pixel_clk_rate);
+ if (ret)
+ dcnano_crtc_err(crtc,
+ "%s: failed to set pixel clock's parent rate: %d\n",
+ __func__, ret);
+
+ ret = clk_set_rate(dcnano->pixel_clk, pixel_clk_rate);
+ if (ret)
+ dcnano_crtc_err(crtc, "%s: failed to set pixel clock rate: %d\n",
+ __func__, ret);
+
+ ret = clk_prepare_enable(dcnano->pixel_clk);
+ if (ret)
+ dcnano_crtc_err(crtc, "%s: failed to enable pixel clock: %d\n",
+ __func__, ret);
+
+ dcnano_crtc_dbg(crtc, "%s: get pll clock rate: %lu\n",
+ __func__, clk_get_rate(dcnano->pll_clk));
+
+ dcnano_crtc_dbg(crtc, "%s: get rate of pixel clock's parent: %lu\n",
+ __func__, clk_get_rate(parent));
+
+ dcnano_crtc_dbg(crtc, "%s: get pixel clock rate %lu\n",
+ __func__, clk_get_rate(dcnano->pixel_clk));
+}
+
+static enum drm_mode_status
+dcnano_crtc_mode_valid(struct drm_crtc *crtc,
+ const struct drm_display_mode *mode)
+{
+ dcnano_crtc_dbg(crtc, "validating mode " DRM_MODE_FMT "\n",
+ DRM_MODE_ARG(mode));
+
+ if (dcnano_crtc_find_pll_clock_rate(crtc, mode) == 0)
+ return MODE_NOCLOCK;
+
+ return MODE_OK;
+}
+
+static void dcnano_crtc_queue_state_event(struct drm_crtc *crtc)
+{
+ struct dcnano_dev *dcnano = crtc_to_dcnano_dev(crtc);
+
+ spin_lock_irq(&crtc->dev->event_lock);
+ if (crtc->state->event) {
+ WARN_ON(drm_crtc_vblank_get(crtc));
+ WARN_ON(dcnano->event);
+ dcnano->event = crtc->state->event;
+ crtc->state->event = NULL;
+ }
+ spin_unlock_irq(&crtc->dev->event_lock);
+}
+
+static int dcnano_crtc_atomic_check(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+{
+ struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state,
+ crtc);
+ bool has_primary = crtc_state->plane_mask &
+ drm_plane_mask(crtc->primary);
+
+ if (crtc_state->active && !has_primary)
+ return -EINVAL;
+
+ if (crtc_state->active_changed && crtc_state->active)
+ crtc_state->mode_changed = true;
+
+ return 0;
+}
+
+static void dcnano_crtc_atomic_flush(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+{
+ struct drm_crtc_state *old_crtc_state = drm_atomic_get_old_crtc_state(state,
+ crtc);
+
+ if (!crtc->state->active && !old_crtc_state->active)
+ return;
+
+ if (!drm_atomic_crtc_needs_modeset(crtc->state))
+ dcnano_crtc_queue_state_event(crtc);
+}
+
+static void dcnano_crtc_atomic_enable(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+{
+ struct drm_device *drm = crtc->dev;
+ struct dcnano_dev *dcnano = crtc_to_dcnano_dev(crtc);
+ struct drm_plane *plane;
+ struct drm_plane_state *new_plane_state;
+ struct drm_display_mode *adj = &crtc->state->adjusted_mode;
+ int i;
+ u32 primary_fb_fmt = 0;
+ u32 val;
+
+ dcnano_crtc_dbg(crtc, "mode " DRM_MODE_FMT "\n", DRM_MODE_ARG(adj));
+
+ dcnano_crtc_set_pixel_clock(crtc);
+
+ /* enable power when we start to set mode for CRTC */
+ pm_runtime_get_sync(drm->dev);
+
+ if (dcnano->port == DCNANO_DPI_PORT)
+ dcnano_crtc_mode_set_nofb_dpi(crtc);
+
+ drm_crtc_vblank_on(crtc);
+
+ for_each_new_plane_in_state(state, plane, new_plane_state, i) {
+ if (!new_plane_state->fb)
+ continue;
+
+ if (plane->type != DRM_PLANE_TYPE_PRIMARY)
+ continue;
+
+ switch (new_plane_state->fb->format->format) {
+ case DRM_FORMAT_RGB565:
+ primary_fb_fmt = FBCFG_FORMAT_R5G6B5;
+ break;
+ case DRM_FORMAT_XRGB8888:
+ primary_fb_fmt = FBCFG_FORMAT_R8G8B8;
+ break;
+ }
+ }
+
+ val = FBCFG_OUTPUT_ENABLE | primary_fb_fmt;
+
+ /* enable DPI timing and start a DPI transfer, if needed */
+ if (dcnano->port == DCNANO_DPI_PORT)
+ val |= FBCFG_RESET_ENABLE;
+
+ dcnano_write(dcnano, DCNANO_FRAMEBUFFERCONFIG, val);
+
+ dcnano_crtc_queue_state_event(crtc);
+}
+
+static void dcnano_crtc_atomic_disable(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+{
+ struct drm_device *drm = crtc->dev;
+ struct dcnano_dev *dcnano = crtc_to_dcnano_dev(crtc);
+
+ /* simply write '0' to the framebuffer and timing control register */
+ dcnano_write(dcnano, DCNANO_FRAMEBUFFERCONFIG, 0);
+
+ drm_crtc_vblank_off(crtc);
+
+ /* disable power when CRTC is disabled */
+ pm_runtime_put_sync(drm->dev);
+
+ clk_disable_unprepare(dcnano->pixel_clk);
+
+ spin_lock_irq(&crtc->dev->event_lock);
+ if (crtc->state->event && !crtc->state->active) {
+ drm_crtc_send_vblank_event(crtc, crtc->state->event);
+ crtc->state->event = NULL;
+ }
+ spin_unlock_irq(&crtc->dev->event_lock);
+}
+
+static bool
+dcnano_crtc_get_scanout_position(struct drm_crtc *crtc,
+ bool in_vblank_irq,
+ int *vpos, int *hpos,
+ ktime_t *stime, ktime_t *etime,
+ const struct drm_display_mode *mode)
+{
+ struct dcnano_dev *dcnano = crtc_to_dcnano_dev(crtc);
+ int hdisplay = mode->crtc_hdisplay;
+ int htotal = mode->crtc_htotal;
+ int vdisplay = mode->crtc_vdisplay;
+ int vtotal = mode->crtc_vtotal;
+ int x, y;
+ u32 val;
+ bool reliable;
+
+ if (stime)
+ *stime = ktime_get();
+
+ val = dcnano_read(dcnano, DCNANO_DISPLAYCURRENTLOCATION);
+
+ x = CURRENTLOCATION_X_GET(val);
+ y = CURRENTLOCATION_Y_GET(val);
+
+ if (x < hdisplay)
+ *hpos = x + 1; /* active scanout area - positive */
+ else
+ *hpos = x - (htotal - 1); /* inside vblank - negative */
+
+ if (y < vdisplay)
+ *vpos = y + 1; /* active scanout area - positive */
+ else
+ *vpos = y - (vtotal - 1); /* inside vblank - negative */
+
+ reliable = true;
+
+ if (etime)
+ *etime = ktime_get();
+
+ return reliable;
+}
+
+static const struct drm_crtc_helper_funcs dcnano_crtc_helper_funcs = {
+ .mode_valid = dcnano_crtc_mode_valid,
+ .atomic_check = dcnano_crtc_atomic_check,
+ .atomic_flush = dcnano_crtc_atomic_flush,
+ .atomic_enable = dcnano_crtc_atomic_enable,
+ .atomic_disable = dcnano_crtc_atomic_disable,
+ .get_scanout_position = dcnano_crtc_get_scanout_position,
+};
+
+static u32 dcnano_crtc_get_vblank_counter(struct drm_crtc *crtc)
+{
+ struct dcnano_dev *dcnano = crtc_to_dcnano_dev(crtc);
+
+ dcnano_write(dcnano, DCNANO_DEBUGCOUNTERSELECT, TOTAL_FRAME_CNT);
+ return dcnano_read(dcnano, DCNANO_DEBUGCOUNTERVALUE);
+}
+
+static int dcnano_crtc_enable_vblank(struct drm_crtc *crtc)
+{
+ struct dcnano_dev *dcnano = crtc_to_dcnano_dev(crtc);
+
+ dcnano_write(dcnano, DCNANO_DISPLAYINTRENABLE, DISPLAYINTR_DISP0);
+
+ return 0;
+}
+
+static void dcnano_crtc_disable_vblank(struct drm_crtc *crtc)
+{
+ struct dcnano_dev *dcnano = crtc_to_dcnano_dev(crtc);
+
+ dcnano_write(dcnano, DCNANO_DISPLAYINTRENABLE, 0);
+}
+
+static const struct drm_crtc_funcs dcnano_crtc_funcs = {
+ .reset = drm_atomic_helper_crtc_reset,
+ .destroy = drm_crtc_cleanup,
+ .set_config = drm_atomic_helper_set_config,
+ .page_flip = drm_atomic_helper_page_flip,
+ .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
+ .get_vblank_counter = dcnano_crtc_get_vblank_counter,
+ .enable_vblank = dcnano_crtc_enable_vblank,
+ .disable_vblank = dcnano_crtc_disable_vblank,
+ .get_vblank_timestamp = drm_crtc_vblank_helper_get_vblank_timestamp,
+};
+
+irqreturn_t dcnano_irq_handler(int irq, void *data)
+{
+ struct drm_device *drm = data;
+ struct dcnano_dev *dcnano = to_dcnano_dev(drm);
+ unsigned long flags;
+
+ /* DCNANO_DISPLAYINTR will automatically clear after a read. */
+ dcnano_read(dcnano, DCNANO_DISPLAYINTR);
+
+ drm_crtc_handle_vblank(&dcnano->crtc);
+
+ spin_lock_irqsave(&drm->event_lock, flags);
+ if (dcnano->event) {
+ drm_crtc_send_vblank_event(&dcnano->crtc, dcnano->event);
+ dcnano->event = NULL;
+ drm_crtc_vblank_put(&dcnano->crtc);
+ }
+ spin_unlock_irqrestore(&drm->event_lock, flags);
+
+ return IRQ_HANDLED;
+}
+
+static int dcnano_get_pll_clock(struct dcnano_dev *dcnano)
+{
+ int ret, i;
+
+ /*
+ * There are one divider clock and gate clock between the pixel
+ * clock and the pll clock, so walk through the clock tree 3 levels
+ * up to get the pll clock.
+ */
+ dcnano->pll_clk = dcnano->pixel_clk;
+ for (i = 0; i < 3; i++) {
+ dcnano->pll_clk = clk_get_parent(dcnano->pll_clk);
+ if (IS_ERR(dcnano->pll_clk)) {
+ ret = PTR_ERR(dcnano->pll_clk);
+ drm_err(&dcnano->base,
+ "failed to get pll clock: %d\n", ret);
+ return ret;
+ } else if (!dcnano->pll_clk) {
+ drm_err(&dcnano->base, "no pll clock\n");
+ return -ENODEV;
+ }
+ }
+
+ return 0;
+}
+
+static void dcnano_reset_all_debug_counters(struct dcnano_dev *dcnano)
+{
+ struct drm_device *drm = &dcnano->base;
+
+ pm_runtime_get_sync(drm->dev);
+ dcnano_write(dcnano, DCNANO_DEBUGCOUNTERSELECT, RESET_ALL_CNTS);
+ pm_runtime_put_sync(drm->dev);
+}
+
+int dcnano_crtc_init(struct dcnano_dev *dcnano)
+{
+ int ret;
+
+ ret = dcnano_plane_init(dcnano);
+ if (ret)
+ return ret;
+
+ drm_crtc_helper_add(&dcnano->crtc, &dcnano_crtc_helper_funcs);
+ ret = drm_crtc_init_with_planes(&dcnano->base, &dcnano->crtc,
+ &dcnano->primary, NULL,
+ &dcnano_crtc_funcs, NULL);
+ if (ret) {
+ drm_err(&dcnano->base, "failed to initialize CRTC: %d\n", ret);
+ return ret;
+ }
+
+ ret = dcnano_get_pll_clock(dcnano);
+ if (ret)
+ return ret;
+
+ dcnano_reset_all_debug_counters(dcnano);
+
+ return 0;
+}
diff --git a/drivers/gpu/drm/imx/dcnano/dcnano-drv.c b/drivers/gpu/drm/imx/dcnano/dcnano-drv.c
new file mode 100644
index 000000000000..755021b6f31d
--- /dev/null
+++ b/drivers/gpu/drm/imx/dcnano/dcnano-drv.c
@@ -0,0 +1,364 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/*
+ * Copyright 2020,2021 NXP
+ */
+
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_modeset_helper.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+
+#include "dcnano-drv.h"
+#include "dcnano-reg.h"
+
+#define DRIVER_NAME "imx-dcnano-drm"
+
+static int legacyfb_depth = 32;
+module_param(legacyfb_depth, uint, 0444);
+
+DEFINE_DRM_GEM_CMA_FOPS(dcnano_driver_fops);
+
+static struct drm_driver dcnano_driver = {
+ .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
+ DRM_GEM_CMA_DRIVER_OPS,
+ .fops = &dcnano_driver_fops,
+ .name = "imx-dcnano",
+ .desc = "i.MX DCNANO DRM graphics",
+ .date = "20201221",
+ .major = 1,
+ .minor = 0,
+ .patchlevel = 0,
+};
+
+static int dcnano_reset(struct dcnano_dev *dcnano)
+{
+ struct drm_device *drm = &dcnano->base;
+ int ret;
+
+ pm_runtime_get_sync(drm->dev);
+
+ ret = reset_control_assert(dcnano->tied_resets);
+ if (ret) {
+ DRM_DEV_ERROR(drm->dev,
+ "failed to assert tied resets: %d\n", ret);
+ goto err;
+ }
+
+ /*
+ * 10 microseconds are enough for the 32-cycle(slowest clock)
+ * assertion duration.
+ */
+ usleep_range(10, 20);
+
+ ret = reset_control_deassert(dcnano->tied_resets);
+ if (ret) {
+ DRM_DEV_ERROR(drm->dev,
+ "failed to deassert tied resets: %d\n", ret);
+ goto err;
+ }
+
+ /*
+ * 40 microseconds are enough for the 128-cycle(slowest clock)
+ * de-assertion duration.
+ */
+ usleep_range(40, 50);
+
+err:
+ pm_runtime_put_sync(drm->dev);
+ return ret;
+}
+
+static int dcnano_irq_install(struct drm_device *dev, int irq)
+{
+ if (irq == IRQ_NOTCONNECTED)
+ return -ENOTCONN;
+
+ return request_irq(irq, dcnano_irq_handler, 0, dev->driver->name, dev);
+}
+
+static void dcnano_irq_uninstall(struct drm_device *dev)
+{
+ struct dcnano_dev *dcnano = to_dcnano_dev(dev);
+
+ free_irq(dcnano->irq, dev);
+}
+
+static int dcnano_check_chip_info(struct dcnano_dev *dcnano)
+{
+ struct drm_device *drm = &dcnano->base;
+ u32 val;
+ int ret = 0;
+
+ pm_runtime_get_sync(drm->dev);
+
+ val = dcnano_read(dcnano, DCNANO_DCCHIPREV);
+ if (val != DCCHIPREV) {
+ DRM_DEV_ERROR(drm->dev, "invalid chip revision(0x%08x)\n", val);
+ ret = -ENODEV;
+ goto err;
+ }
+ DRM_DEV_DEBUG(drm->dev, "chip revision is 0x%08x\n", val);
+
+ val = dcnano_read(dcnano, DCNANO_DCCHIPDATE);
+ if (val != DCCHIPDATE) {
+ DRM_DEV_ERROR(drm->dev, "invalid chip date(0x%08x)\n", val);
+ ret = -ENODEV;
+ goto err;
+ }
+ DRM_DEV_DEBUG(drm->dev, "chip date is 0x%08x\n", val);
+
+ val = dcnano_read(dcnano, DCNANO_DCCHIPPATCHREV);
+ if (val != DCCHIPPATCHREV) {
+ DRM_DEV_ERROR(drm->dev,
+ "invalid chip patch revision(0x%08x)\n", val);
+ ret = -ENODEV;
+ goto err;
+ }
+ DRM_DEV_DEBUG(drm->dev, "chip patch revision is 0x%08x\n", val);
+err:
+ pm_runtime_put_sync(drm->dev);
+ return ret;
+}
+
+static int dcnano_probe(struct platform_device *pdev)
+{
+ struct dcnano_dev *dcnano;
+ struct drm_device *drm;
+ int ret;
+
+ if (!pdev->dev.of_node)
+ return -ENODEV;
+
+ dcnano = devm_drm_dev_alloc(&pdev->dev, &dcnano_driver,
+ struct dcnano_dev, base);
+ if (IS_ERR(dcnano))
+ return PTR_ERR(dcnano);
+
+ drm = &dcnano->base;
+ dev_set_drvdata(&pdev->dev, dcnano);
+
+ dcnano->mmio_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(dcnano->mmio_base))
+ return PTR_ERR(dcnano->mmio_base);
+
+ ret = platform_get_irq(pdev, 0);
+ if (ret < 0)
+ return ret;
+ dcnano->irq = ret;
+
+ dcnano->axi_clk = devm_clk_get(drm->dev, "axi");
+ if (IS_ERR(dcnano->axi_clk)) {
+ ret = PTR_ERR(dcnano->axi_clk);
+ if (ret != -EPROBE_DEFER)
+ DRM_DEV_ERROR(drm->dev,
+ "failed to get axi clk: %d\n", ret);
+ return ret;
+ }
+
+ dcnano->ahb_clk = devm_clk_get(drm->dev, "ahb");
+ if (IS_ERR(dcnano->ahb_clk)) {
+ ret = PTR_ERR(dcnano->ahb_clk);
+ if (ret != -EPROBE_DEFER)
+ DRM_DEV_ERROR(drm->dev,
+ "failed to get ahb clk: %d\n", ret);
+ return ret;
+ }
+
+ dcnano->pixel_clk = devm_clk_get(drm->dev, "pixel");
+ if (IS_ERR(dcnano->pixel_clk)) {
+ ret = PTR_ERR(dcnano->pixel_clk);
+ if (ret != -EPROBE_DEFER)
+ DRM_DEV_ERROR(drm->dev,
+ "failed to get pixel clk: %d\n", ret);
+ return ret;
+ }
+
+ ret = dma_set_mask_and_coherent(drm->dev, DMA_BIT_MASK(32));
+ if (ret) {
+ DRM_DEV_ERROR(drm->dev,
+ "failed to set dma mask and coherent: %d\n", ret);
+ return ret;
+ }
+
+ pm_runtime_enable(drm->dev);
+
+ dcnano->tied_resets = devm_reset_control_get(drm->dev, NULL);
+ if (IS_ERR(dcnano->tied_resets)) {
+ ret = PTR_ERR(dcnano->tied_resets);
+ if (ret != -EPROBE_DEFER)
+ DRM_DEV_ERROR(drm->dev,
+ "failed to get tied resets: %d\n", ret);
+ goto err_reset_get;
+ }
+
+ ret = dcnano_reset(dcnano);
+ if (ret)
+ goto err_dcnano_reset;
+
+ pm_runtime_get_sync(drm->dev);
+ ret = dcnano_irq_install(drm, dcnano->irq);
+ pm_runtime_put_sync(drm->dev);
+
+ if (ret < 0) {
+ DRM_DEV_ERROR(drm->dev,
+ "failed to install IRQ handler: %d\n", ret);
+ goto err_irq_install;
+ }
+
+ ret = dcnano_check_chip_info(dcnano);
+ if (ret)
+ goto err_check_chip_info;
+
+ ret = dcnano_kms_prepare(dcnano);
+ if (ret)
+ goto err_kms_prepare;
+
+ ret = drm_dev_register(drm, 0);
+ if (ret) {
+ DRM_DEV_ERROR(drm->dev,
+ "failed to register drm device: %d\n", ret);
+ goto err_register;
+ }
+
+ if (legacyfb_depth != 16 && legacyfb_depth != 32) {
+ DRM_DEV_INFO(drm->dev,
+ "Invalid legacyfb_depth. Defaulting to 32bpp\n");
+ legacyfb_depth = 32;
+ }
+
+ drm_fbdev_generic_setup(drm, legacyfb_depth);
+
+ return 0;
+
+err_register:
+ drm_kms_helper_poll_fini(drm);
+err_kms_prepare:
+err_check_chip_info:
+ pm_runtime_get_sync(drm->dev);
+ dcnano_irq_uninstall(drm);
+ pm_runtime_put_sync(drm->dev);
+err_irq_install:
+err_dcnano_reset:
+err_reset_get:
+ pm_runtime_disable(drm->dev);
+ return ret;
+}
+
+static int dcnano_remove(struct platform_device *pdev)
+{
+ struct dcnano_dev *dcnano = dev_get_drvdata(&pdev->dev);
+ struct drm_device *drm = &dcnano->base;
+
+ drm_dev_unregister(drm);
+
+ drm_kms_helper_poll_fini(drm);
+
+ drm_atomic_helper_shutdown(drm);
+
+ pm_runtime_get_sync(drm->dev);
+ dcnano_irq_uninstall(drm);
+ pm_runtime_put_sync(drm->dev);
+
+ pm_runtime_disable(drm->dev);
+
+ return 0;
+}
+
+static int __maybe_unused dcnano_suspend(struct device *dev)
+{
+ struct dcnano_dev *dcnano = dev_get_drvdata(dev);
+
+ return drm_mode_config_helper_suspend(&dcnano->base);
+}
+
+static int __maybe_unused dcnano_resume(struct device *dev)
+{
+ struct dcnano_dev *dcnano = dev_get_drvdata(dev);
+
+ return drm_mode_config_helper_resume(&dcnano->base);
+}
+
+static int __maybe_unused dcnano_runtime_suspend(struct device *dev)
+{
+ struct dcnano_dev *dcnano = dev_get_drvdata(dev);
+
+ drm_dbg(&dcnano->base, "runtime suspend\n");
+
+ clk_disable_unprepare(dcnano->pixel_clk);
+ clk_disable_unprepare(dcnano->ahb_clk);
+ clk_disable_unprepare(dcnano->axi_clk);
+
+ return 0;
+}
+
+static int __maybe_unused dcnano_runtime_resume(struct device *dev)
+{
+ struct dcnano_dev *dcnano = dev_get_drvdata(dev);
+ int ret;
+
+ drm_dbg(&dcnano->base, "runtime resume\n");
+
+ ret = clk_prepare_enable(dcnano->axi_clk);
+ if (ret) {
+ DRM_DEV_ERROR(dev, "failed to enable axi clock: %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(dcnano->ahb_clk);
+ if (ret) {
+ DRM_DEV_ERROR(dev, "failed to enable ahb clock: %d\n", ret);
+ clk_disable_unprepare(dcnano->axi_clk);
+ return ret;
+ }
+
+ /*
+ * Pixel clock has to be enabled for like DCNANO in i.MX8ulp,
+ * otherwise registers cannot be accessed.
+ */
+ ret = clk_prepare_enable(dcnano->pixel_clk);
+ if (ret) {
+ DRM_DEV_ERROR(dev, "failed to enable pixel clock: %d\n", ret);
+ clk_disable_unprepare(dcnano->axi_clk);
+ clk_disable_unprepare(dcnano->ahb_clk);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct dev_pm_ops dcnano_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(dcnano_suspend, dcnano_resume)
+ SET_RUNTIME_PM_OPS(dcnano_runtime_suspend, dcnano_runtime_resume, NULL)
+};
+
+static const struct of_device_id dcnano_dt_ids[] = {
+ { .compatible = "nxp,imx8ulp-dcnano", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, dcnano_dt_ids);
+
+static struct platform_driver dcnano_platform_driver = {
+ .probe = dcnano_probe,
+ .remove = dcnano_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = dcnano_dt_ids,
+ .pm = &dcnano_pm_ops,
+ },
+};
+module_platform_driver(dcnano_platform_driver);
+
+MODULE_AUTHOR("NXP Semiconductor");
+MODULE_DESCRIPTION("i.MX DCNANO DRM driver");
+MODULE_ALIAS("platform:" DRIVER_NAME);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/imx/dcnano/dcnano-drv.h b/drivers/gpu/drm/imx/dcnano/dcnano-drv.h
new file mode 100644
index 000000000000..90903990df98
--- /dev/null
+++ b/drivers/gpu/drm/imx/dcnano/dcnano-drv.h
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+
+/*
+ * Copyright 2020,2021 NXP
+ */
+
+#ifndef __DCNANO_DRV_H__
+#define __DCNANO_DRV_H__
+
+#include <linux/clk.h>
+#include <linux/irqreturn.h>
+#include <linux/kernel.h>
+#include <linux/reset.h>
+
+#include <drm/drm_crtc.h>
+#include <drm/drm_device.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_vblank.h>
+
+enum dcnano_port {
+ DCNANO_DPI_PORT,
+ DCNANO_DBI_PORT,
+ DCNANO_PORT_NUM,
+};
+
+struct dcnano_dev {
+ struct drm_device base;
+ void __iomem *mmio_base;
+
+ unsigned int irq;
+
+ struct clk *axi_clk;
+ struct clk *ahb_clk;
+ struct clk *pixel_clk;
+ struct clk *pll_clk;
+
+ struct reset_control *tied_resets;
+
+ struct drm_crtc crtc;
+ struct drm_plane primary;
+ struct drm_encoder encoder;
+
+ struct drm_pending_vblank_event *event;
+
+ enum dcnano_port port;
+};
+
+static inline struct dcnano_dev *to_dcnano_dev(struct drm_device *drm)
+{
+ return container_of(drm, struct dcnano_dev, base);
+}
+
+irqreturn_t dcnano_irq_handler(int irq, void *data);
+
+int dcnano_crtc_init(struct dcnano_dev *dcnano);
+
+int dcnano_plane_init(struct dcnano_dev *dcnano);
+
+int dcnano_kms_prepare(struct dcnano_dev *dcnano);
+
+#endif
diff --git a/drivers/gpu/drm/imx/dcnano/dcnano-kms.c b/drivers/gpu/drm/imx/dcnano/dcnano-kms.c
new file mode 100644
index 000000000000..95808b522cdd
--- /dev/null
+++ b/drivers/gpu/drm/imx/dcnano/dcnano-kms.c
@@ -0,0 +1,194 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/*
+ * Copyright 2020,2021 NXP
+ */
+
+#include <linux/of.h>
+#include <linux/of_graph.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_bridge_connector.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_of.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+#include <drm/drm_vblank.h>
+
+#include "dcnano-drv.h"
+#include "dcnano-reg.h"
+
+static int dcnano_kms_init(struct dcnano_dev *dcnano)
+{
+ struct drm_device *drm = &dcnano->base;
+ struct drm_panel *panel;
+ struct drm_bridge *bridge;
+ struct drm_connector *connector;
+ struct device_node *np = drm->dev->of_node;
+ struct device_node *port, *ep, *remote;
+ struct of_endpoint endpoint;
+ u32 port_id;
+ bool found_ep = false;
+ int ret;
+
+ ret = dcnano_crtc_init(dcnano);
+ if (ret)
+ return ret;
+
+ for (port_id = 0; port_id < DCNANO_PORT_NUM; port_id++) {
+ port = of_graph_get_port_by_id(np, port_id);
+ if (!port) {
+ drm_err(drm, "failed to get output port%u\n", port_id);
+ return -EINVAL;
+ }
+
+ for_each_child_of_node(port, ep) {
+ remote = of_graph_get_remote_port_parent(ep);
+ if (!remote || !of_device_is_available(remote) ||
+ !of_device_is_available(remote->parent)) {
+ of_node_put(remote);
+ continue;
+ }
+
+ of_node_put(remote);
+
+ ret = of_graph_parse_endpoint(ep, &endpoint);
+ if (ret) {
+ drm_err(drm,
+ "failed to parse endpoint of port%u: %d\n",
+ port_id, ret);
+ of_node_put(ep);
+ of_node_put(port);
+ return ret;
+ }
+
+ ret = drm_of_find_panel_or_bridge(np,
+ port_id, endpoint.id,
+ &panel, &bridge);
+ if (ret) {
+ if (ret == -ENODEV) {
+ drm_dbg(drm,
+ "no panel or bridge on port%u ep%d\n",
+ port_id, endpoint.id);
+ continue;
+ } else if (ret != -EPROBE_DEFER) {
+ drm_err(drm,
+ "failed to find panel or bridge on port%u ep%d: %d\n",
+ port_id, endpoint.id, ret);
+ }
+ of_node_put(ep);
+ of_node_put(port);
+ return ret;
+ }
+
+ found_ep = true;
+ break;
+ }
+
+ of_node_put(port);
+ dcnano->port = port_id;
+
+ if (found_ep) {
+ drm_dbg(drm, "found valid endpoint%d @ port%u\n",
+ endpoint.id, port_id);
+ break;
+ }
+ }
+
+ if (!found_ep) {
+ drm_info(drm, "no valid endpoint\n");
+ return 0;
+ }
+
+ if (panel) {
+ bridge = devm_drm_panel_bridge_add(drm->dev, panel);
+ if (IS_ERR(bridge)) {
+ ret = PTR_ERR(bridge);
+ drm_err(drm,
+ "failed to add panel bridge on port%u ep%d: %d\n",
+ port_id, endpoint.id, ret);
+ goto err;
+ }
+ }
+
+ dcnano->encoder.possible_crtcs = drm_crtc_mask(&dcnano->crtc);
+ ret = drm_simple_encoder_init(drm, &dcnano->encoder,
+ DRM_MODE_ENCODER_NONE);
+ if (ret) {
+ drm_err(drm, "failed to initialize encoder on port%u ep%d: %d\n",
+ port_id, endpoint.id, ret);
+ goto err;
+ }
+
+ ret = drm_bridge_attach(&dcnano->encoder, bridge, NULL,
+ DRM_BRIDGE_ATTACH_NO_CONNECTOR);
+ if (ret) {
+ drm_err(drm,
+ "failed to attach bridge to encoder on port%u ep%d: %d\n",
+ port_id, endpoint.id, ret);
+ goto err;
+ }
+
+ connector = drm_bridge_connector_init(drm, &dcnano->encoder);
+ if (IS_ERR(connector)) {
+ ret = PTR_ERR(connector);
+ drm_err(drm,
+ "failed to initialize bridge connector on port%u ep%d: %d\n",
+ port_id, endpoint.id, ret);
+ goto err;
+ }
+
+ ret = drm_connector_attach_encoder(connector, &dcnano->encoder);
+ if (ret)
+ drm_err(drm,
+ "failed to attach encoder to connector on port%u ep%d: %d\n",
+ port_id, endpoint.id, ret);
+err:
+ return ret;
+}
+
+static const struct drm_mode_config_funcs dcnano_mode_config_funcs = {
+ .fb_create = drm_gem_fb_create,
+ .atomic_check = drm_atomic_helper_check,
+ .atomic_commit = drm_atomic_helper_commit,
+};
+
+static const struct drm_mode_config_helper_funcs dcnano_mode_config_helpers = {
+ .atomic_commit_tail = drm_atomic_helper_commit_tail_rpm,
+};
+
+int dcnano_kms_prepare(struct dcnano_dev *dcnano)
+{
+ struct drm_device *drm = &dcnano->base;
+ int ret;
+
+ ret = drmm_mode_config_init(drm);
+ if (ret)
+ return ret;
+
+ ret = dcnano_kms_init(dcnano);
+ if (ret)
+ return ret;
+
+ drm->mode_config.min_width = 32;
+ drm->mode_config.min_height = 32;
+ drm->mode_config.max_width = 1280;
+ drm->mode_config.max_height = 1280;
+ drm->mode_config.funcs = &dcnano_mode_config_funcs;
+ drm->mode_config.helper_private = &dcnano_mode_config_helpers;
+ drm->max_vblank_count = DEBUGCOUNTERVALUE_MAX;
+
+ ret = drm_vblank_init(drm, 1);
+ if (ret < 0) {
+ drm_err(drm, "failed to initialize vblank: %d\n", ret);
+ return ret;
+ }
+
+ drm_mode_config_reset(drm);
+
+ drm_kms_helper_poll_init(drm);
+
+ return 0;
+}
diff --git a/drivers/gpu/drm/imx/dcnano/dcnano-plane.c b/drivers/gpu/drm/imx/dcnano/dcnano-plane.c
new file mode 100644
index 000000000000..d1e6062e89e8
--- /dev/null
+++ b/drivers/gpu/drm/imx/dcnano/dcnano-plane.c
@@ -0,0 +1,178 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/*
+ * Copyright 2020,2021 NXP
+ */
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_atomic_state_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_gem_atomic_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_plane_helper.h>
+#include <drm/drm_print.h>
+#include <drm/drm_rect.h>
+
+#include "dcnano-drv.h"
+#include "dcnano-reg.h"
+
+#define DCNANO_FB_PITCH_ALIGN 128 /* in byte */
+
+#define dcnano_plane_dbg(plane, fmt, ...) \
+ drm_dbg_kms((plane)->dev, "[PLANE:%d:%s] " fmt, \
+ (plane)->base.id, (plane)->name, ##__VA_ARGS__)
+
+/* primary plane formats */
+static const u32 dcnano_primary_plane_formats[] = {
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_XRGB8888,
+};
+
+static unsigned int
+dcnano_primary_plane_format_count = ARRAY_SIZE(dcnano_primary_plane_formats);
+
+static inline struct dcnano_dev *plane_to_dcnano_dev(struct drm_plane *plane)
+{
+ return to_dcnano_dev(plane->dev);
+}
+
+static inline dma_addr_t
+drm_plane_state_to_baseaddr(struct drm_plane_state *state)
+{
+ struct drm_framebuffer *fb = state->fb;
+ struct drm_gem_cma_object *cma_obj;
+ unsigned int x = state->src.x1 >> 16;
+ unsigned int y = state->src.y1 >> 16;
+
+ cma_obj = drm_fb_cma_get_gem_obj(fb, 0);
+
+ return cma_obj->paddr + fb->offsets[0] + fb->pitches[0] * y +
+ fb->format->cpp[0] * x;
+}
+
+/***************************/
+/* primary plane functions */
+/***************************/
+
+static int dcnano_primary_plane_atomic_check(struct drm_plane *plane,
+ struct drm_atomic_state *state)
+{
+ struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state,
+ plane);
+ struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state,
+ plane);
+ struct dcnano_dev *dcnano = plane_to_dcnano_dev(plane);
+ struct drm_crtc_state *crtc_state;
+ struct drm_framebuffer *fb = new_state->fb;
+ struct drm_framebuffer *old_fb = old_state->fb;
+ u32 src_w;
+ unsigned int pitch_no_padding;
+ int ret;
+
+ /* ok to disable */
+ if (!fb)
+ return 0;
+
+ crtc_state = drm_atomic_get_new_crtc_state(state, &dcnano->crtc);
+ if (WARN_ON(!crtc_state))
+ return -EINVAL;
+
+ ret = drm_atomic_helper_check_plane_state(new_state, crtc_state,
+ DRM_PLANE_HELPER_NO_SCALING,
+ DRM_PLANE_HELPER_NO_SCALING,
+ false, true);
+ if (ret)
+ return ret;
+
+ if (fb->pitches[0] > FBSTRIDE_MAX) {
+ dcnano_plane_dbg(plane, "fb pitches[0] 0x%08x is out of range\n",
+ fb->pitches[0]);
+ return -EINVAL;
+ }
+
+ /*
+ * The primary plane's stride value in register has to be 128byte
+ * aligned, _but_ no dedicated padding is allowed.
+ */
+ src_w = drm_rect_width(&new_state->src) >> 16;
+ pitch_no_padding = fb->format->cpp[0] * src_w;
+ if (fb->pitches[0] != pitch_no_padding) {
+ dcnano_plane_dbg(plane,
+ "fb pitches[0] 0x%08x should be no padding - 0x%08x\n",
+ fb->pitches[0], pitch_no_padding);
+ return -EINVAL;
+ }
+
+ /*
+ * Force CRTC mode change if framebuffer stride or pixel format
+ * are changed.
+ */
+ if (old_fb &&
+ (fb->pitches[0] != old_fb->pitches[0] ||
+ fb->format->format != old_fb->format->format))
+ crtc_state->mode_changed = true;
+
+ return 0;
+}
+
+static void dcnano_primary_plane_atomic_update(struct drm_plane *plane,
+ struct drm_atomic_state *state)
+{
+ struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state,
+ plane);
+ struct dcnano_dev *dcnano = plane_to_dcnano_dev(plane);
+ struct drm_framebuffer *fb = new_state->fb;
+ dma_addr_t baseaddr;
+
+ if (!fb) {
+ dcnano_plane_dbg(plane, "no fb\n");
+ return;
+ }
+
+ baseaddr = drm_plane_state_to_baseaddr(new_state);
+
+ dcnano_plane_dbg(plane, "fb address %pad, pitch 0x%08x\n",
+ &baseaddr, fb->pitches[0]);
+
+ dcnano_write(dcnano, DCNANO_FRAMEBUFFERADDRESS, baseaddr);
+
+ dcnano_write(dcnano, DCNANO_FRAMEBUFFERSTRIDE,
+ ALIGN(fb->pitches[0], DCNANO_FB_PITCH_ALIGN));
+}
+
+static const struct drm_plane_helper_funcs dcnano_primary_plane_helper_funcs = {
+ .prepare_fb = drm_gem_plane_helper_prepare_fb,
+ .atomic_check = dcnano_primary_plane_atomic_check,
+ .atomic_update = dcnano_primary_plane_atomic_update,
+};
+
+static const struct drm_plane_funcs dcnano_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,
+};
+
+int dcnano_plane_init(struct dcnano_dev *dcnano)
+{
+ int ret;
+
+ /* primary plane */
+ drm_plane_helper_add(&dcnano->primary,
+ &dcnano_primary_plane_helper_funcs);
+ ret = drm_universal_plane_init(&dcnano->base, &dcnano->primary, 0,
+ &dcnano_plane_funcs,
+ dcnano_primary_plane_formats,
+ dcnano_primary_plane_format_count,
+ NULL,
+ DRM_PLANE_TYPE_PRIMARY, NULL);
+ if (ret)
+ drm_err(&dcnano->base,
+ "failed to initialize primary plane: %d\n", ret);
+
+ return ret;
+}
diff --git a/drivers/gpu/drm/imx/dcnano/dcnano-reg.h b/drivers/gpu/drm/imx/dcnano/dcnano-reg.h
new file mode 100644
index 000000000000..aa31adcc2935
--- /dev/null
+++ b/drivers/gpu/drm/imx/dcnano/dcnano-reg.h
@@ -0,0 +1,438 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+
+/*
+ * Copyright 2020,2021 NXP
+ */
+
+#ifndef _DCNANO_REG_H_
+#define _DCNANO_REG_H_
+
+#include <linux/bitfield.h>
+#include <linux/io.h>
+#include <linux/sizes.h>
+
+#define DCNANO_FRAMEBUFFERCONFIG 0x1240
+/* double buffered */
+#define FBCFG_FORMAT_MASK GENMASK(2, 0)
+#define FBCFG_FORMAT_NONE FIELD_PREP(FBCFG_FORMAT_MASK, 0)
+#define FBCFG_FORMAT_R4G4B4 FIELD_PREP(FBCFG_FORMAT_MASK, 1)
+#define FBCFG_FORMAT_R5G5B5 FIELD_PREP(FBCFG_FORMAT_MASK, 2)
+#define FBCFG_FORMAT_R5G6B5 FIELD_PREP(FBCFG_FORMAT_MASK, 3)
+#define FBCFG_FORMAT_R8G8B8 FIELD_PREP(FBCFG_FORMAT_MASK, 4)
+/* double buffered */
+#define FBCFG_MODE_LINEAR 0
+#define FBCFG_MODE_TILE4X4 BIT(4)
+/* double buffered */
+#define FBCFG_OUTPUT_MASK BIT(8)
+#define FBCFG_OUTPUT_DISABLE 0
+#define FBCFG_OUTPUT_ENABLE BIT(8)
+/* double buffered */
+#define FBCFG_SWITCHPANEL_DISABLE 0
+#define FBCFG_SWITCHPANEL_ENABLE BIT(9)
+/* double buffered */
+#define FBCFG_GAMMA_DISABLE 0
+#define FBCFG_GAMMA_ENABLE BIT(12)
+#define FBCFG_VALID_WORKING 0
+#define FBCFG_VALID_PENDING BIT(16)
+#define FBCFG_RESET_MASK BIT(20)
+#define FBCFG_RESET_DISABLE 0
+#define FBCFG_RESET_ENABLE BIT(20)
+#define FBCFG_UNDERFLOW_NO 0
+#define FBCFG_UNDERFLOW_YES BIT(24)
+#define FBCFG_FLIP_INPROGRSS_NO 0
+#define FBCFG_FLIP_INPROGRSS_YES BIT(28)
+#define FBCFG_BACK_PRES_DISABLE_NO 0
+#define FBCFG_BACK_PRES_DISABLE_YES BIT(29)
+
+/* double buffered */
+#define DCNANO_FRAMEBUFFERADDRESS 0x1260
+#define FBADDRESS_MASK GENMASK(31, 0)
+#define FBADDRESS(x) FIELD_PREP(FBADDRESS_MASK, (x))
+#define FBADDRESS_TYPE_SYSTEM 0
+#define FBADDRESS_TYPE_VIRTUAL BIT(31)
+
+/* double buffered */
+#define DCNANO_FRAMEBUFFERSTRIDE 0x1280
+#define FBSTRIDE_MASK GENMASK(16, 0)
+#define FBSTRIDE(x) FIELD_PREP(FBSTRIDE_MASK, (x))
+#define FBSTRIDE_MAX FIELD_MAX(FBSTRIDE_MASK)
+
+#define DCNANO_DISPLAYDITHERCONFIG 0x1360
+#define DITHERCFG_BLUESIZE_MASK GENMASK(3, 0)
+#define DITHERCFG_BLUESIZE(x) FIELD_PREP(DITHERCFG_BLUESIZE_MASK, (x))
+#define DITHERCFG_GREENSIZE_MASK GENMASK(11, 8)
+#define DITHERCFG_GREENSIZE(x) FIELD_PREP(DITHERCFG_GREENSIZE_MASK, (x))
+#define DITHERCFG_REDSIZE_MASK GENMASK(19, 16)
+#define DITHERCFG_REDSIZE(x) FIELD_PREP(DITHERCFG_REDSIZE_MASK, (x))
+#define DITHERCFG_DISABLE 0
+/* double buffered */
+#define DITHERCFG_ENABLE BIT(31)
+
+#define DCNANO_DISPLAYDITHERTABLELOW 0x1380
+#define DITHERTLB_LOW_Y0X0_MASK GENMASK(3, 0)
+#define DITHERTLB_LOW_Y0X0(x) FIELD_PREP(DITHERTLB_LOW_Y0X0_MASK, (x))
+#define DITHERTLB_LOW_Y0X1_MASK GENMASK(7, 4)
+#define DITHERTLB_LOW_Y0X1(x) FIELD_PREP(DITHERTLB_LOW_Y0X1_MASK, (x))
+#define DITHERTLB_LOW_Y0X2_MASK GENMASK(11, 8)
+#define DITHERTLB_LOW_Y0X2(x) FIELD_PREP(DITHERTLB_LOW_Y0X2_MASK, (x))
+#define DITHERTLB_LOW_Y0X3_MASK GENMASK(15, 12)
+#define DITHERTLB_LOW_Y0X3(x) FIELD_PREP(DITHERTLB_LOW_Y0X3_MASK, (x))
+#define DITHERTLB_LOW_Y1X0_MASK GENMASK(19, 16)
+#define DITHERTLB_LOW_Y1X0(x) FIELD_PREP(DITHERTLB_LOW_Y1X0_MASK, (x))
+#define DITHERTLB_LOW_Y1X1_MASK GENMASK(23, 20)
+#define DITHERTLB_LOW_Y1X1(x) FIELD_PREP(DITHERTLB_LOW_Y1X1_MASK, (x))
+#define DITHERTLB_LOW_Y1X2_MASK GENMASK(27, 24)
+#define DITHERTLB_LOW_Y1X2(x) FIELD_PREP(DITHERTLB_LOW_Y1X2_MASK, (x))
+#define DITHERTLB_LOW_Y1X3_MASK GENMASK(31, 28)
+#define DITHERTLB_LOW_Y1X3(x) FIELD_PREP(DITHERTLB_LOW_Y1X3_MASK, (x))
+
+#define DCNANO_DISPLAYDITHERTABLEHIGH 0x13a0
+#define DITHERTLB_HIGH_Y2X0_MASK GENMASK(3, 0)
+#define DITHERTLB_HIGH_Y2X0(x) FIELD_PREP(DITHERTLB_HIGH_Y2X0_MASK, (x))
+#define DITHERTLB_HIGH_Y2X1_MASK GENMASK(7, 4)
+#define DITHERTLB_HIGH_Y2X1(x) FIELD_PREP(DITHERTLB_HIGH_Y2X1_MASK, (x))
+#define DITHERTLB_HIGH_Y2X2_MASK GENMASK(11, 8)
+#define DITHERTLB_HIGH_Y2X2(x) FIELD_PREP(DITHERTLB_HIGH_Y2X2_MASK, (x))
+#define DITHERTLB_HIGH_Y2X3_MASK GENMASK(15, 12)
+#define DITHERTLB_HIGH_Y2X3(x) FIELD_PREP(DITHERTLB_HIGH_Y2X3_MASK, (x))
+#define DITHERTLB_HIGH_Y3X0_MASK GENMASK(19, 16)
+#define DITHERTLB_HIGH_Y3X0(x) FIELD_PREP(DITHERTLB_HIGH_Y3X0_MASK, (x))
+#define DITHERTLB_HIGH_Y3X1_MASK GENMASK(23, 20)
+#define DITHERTLB_HIGH_Y3X1(x) FIELD_PREP(DITHERTLB_HIGH_Y3X1_MASK, (x))
+#define DITHERTLB_HIGH_Y3X2_MASK GENMASK(27, 24)
+#define DITHERTLB_HIGH_Y3X2(x) FIELD_PREP(DITHERTLB_HIGH_Y3X2_MASK, (x))
+#define DITHERTLB_HIGH_Y3X3_MASK GENMASK(31, 28)
+#define DITHERTLB_HIGH_Y3X3(x) FIELD_PREP(DITHERTLB_HIGH_Y3X3_MASK, (x))
+
+#define DCNANO_PANELCONFIG 0x13c0
+#define PANELCFG_DE_DISABLE 0
+#define PANELCFG_DE_ENABLE BIT(0)
+#define PANELCFG_DE_POL_POSITIVE 0
+#define PANELCFG_DE_POL_NEGATIVE BIT(1)
+/* double buffered? */
+#define PANELCFG_DATA_DISABLE 0
+#define PANELCFG_DATA_ENABLE BIT(4)
+#define PANELCFG_DATA_POL_POSITIVE 0
+#define PANELCFG_DATA_POL_NEGATIVE BIT(5)
+#define PANELCFG_CLOCK_DISABLE 0
+#define PANELCFG_CLOCK_ENABLE BIT(8)
+#define PANELCFG_CLOCK_POL_POSITIVE 0
+#define PANELCFG_CLOCK_POL_NEGATIVE BIT(9)
+#define PANELCFG_SEQUENCING_HARDWARE 0
+#define PANELCFG_SEQUENCING_SOFTWARE BIT(31)
+
+#define DCNANO_PANELTIMING 0x13e0
+#define PANELTIMING_POWER_ENABLE_MASK GENMASK(3, 0)
+#define PANELTIMING_POWER_ENABLE(x) \
+ FIELD_PREP(PANELTIMING_POWER_ENABLE_MASK, (x))
+#define PANELTIMING_BACKLIGHT_ENABLE_MASK GENMASK(7, 4)
+#define PANELTIMING_BACKLIGHT_ENABLE(x) \
+ FIELD_PREP(PANELTIMING_BACKLIGHT_ENABLE_MASK, (x))
+#define PANELTIMING_CLOCK_ENABLE_MASK GENMASK(11, 8)
+#define PANELTIMING_CLOCK_ENABLE(x) \
+ FIELD_PREP(PANELTIMING_CLOCK_ENABLE_MASK, (x))
+#define PANELTIMING_DATA_ENABLE_MASK GENMASK(15, 12)
+#define PANELTIMING_DATA_ENABLE(x) \
+ FIELD_PREP(PANELTIMING_DATA_ENABLE_MASK, (x))
+#define PANELTIMING_DATA_DISABLE_MASK GENMASK(19, 16)
+#define PANELTIMING_DATA_DISABLE(x) \
+ FIELD_PREP(PANELTIMING_DATA_DISABLE_MASK, (x))
+#define PANELTIMING_CLOCK_DISABLE_MASK GENMASK(23, 20)
+#define PANELTIMING_CLOCK_DISABLE(x) \
+ FIELD_PREP(PANELTIMING_CLOCK_DISABLE_MASK, (x))
+#define PANELTIMING_BACKLIGHT_DISABLE_MASK GENMASK(27, 24)
+#define PANELTIMING_BACKLIGHT_DISABLE(x) \
+ FIELD_PREP(PANELTIMING_BACKLIGHT_DISABLE_MASK, (x))
+#define PANELTIMING_POWER_DISABLE_MASK GENMASK(31, 28)
+#define PANELTIMING_POWER_DISABLE(x) \
+ FIELD_PREP(PANELTIMING_POWER_DISABLE_MASK, (x))
+
+#define DCNANO_HDISPLAY 0x1400
+#define HDISPLAY_END_MASK GENMASK(12, 0)
+#define HDISPLAY_END(x) FIELD_PREP(HDISPLAY_END_MASK, (x))
+#define HDISPLAY_TOTAL_MASK GENMASK(28, 16)
+#define HDISPLAY_TOTAL(x) FIELD_PREP(HDISPLAY_TOTAL_MASK, (x))
+
+#define DCNANO_HSYNC 0x1420
+#define HSYNC_START_MASK GENMASK(12, 0)
+#define HSYNC_START(x) FIELD_PREP(HSYNC_START_MASK, (x))
+#define HSYNC_END_MASK GENMASK(28, 16)
+#define HSYNC_END(x) FIELD_PREP(HSYNC_END_MASK, (x))
+/* double buffered? */
+#define HSYNC_PULSE_DISABLE 0
+#define HSYNC_PULSE_ENABLE BIT(30)
+#define HSYNC_POL_MASK BIT(31)
+#define HSYNC_POL_POSITIVE 0
+#define HSYNC_POL_NEGATIVE BIT(31)
+
+#define DCNANO_VDISPLAY 0x1480
+#define VDISPLAY_END_MASK GENMASK(11, 0)
+#define VDISPLAY_END(x) FIELD_PREP(VDISPLAY_END_MASK, (x))
+#define VDISPLAY_TOTAL_MASK GENMASK(27, 16)
+#define VDISPLAY_TOTAL(x) FIELD_PREP(VDISPLAY_TOTAL_MASK, (x))
+
+#define DCNANO_VSYNC 0x14a0
+#define VSYNC_START_MASK GENMASK(11, 0)
+#define VSYNC_START(x) FIELD_PREP(VSYNC_START_MASK, (x))
+#define VSYNC_END_MASK GENMASK(27, 16)
+#define VSYNC_END(x) FIELD_PREP(VSYNC_END_MASK, (x))
+/* double buffered? */
+#define VSYNC_PULSE_DISABLE 0
+#define VSYNC_PULSE_ENABLE BIT(30)
+#define VSYNC_POL_MASK BIT(31)
+#define VSYNC_POL_POSITIVE 0
+#define VSYNC_POL_NEGATIVE BIT(31)
+
+#define DCNANO_DISPLAYCURRENTLOCATION 0x14c0
+#define CURRENTLOCATION_X_MASK GENMASK(15, 0)
+#define CURRENTLOCATION_X(x) FIELD_PREP(CURRENTLOCATION_X_MASK, (x))
+#define CURRENTLOCATION_X_GET(x) FIELD_GET(CURRENTLOCATION_X_MASK, (x))
+#define CURRENTLOCATION_Y_MASK GENMASK(31, 16)
+#define CURRENTLOCATION_Y(x) FIELD_PREP(CURRENTLOCATION_Y_MASK, (x))
+#define CURRENTLOCATION_Y_GET(x) FIELD_GET(CURRENTLOCATION_Y_MASK, (x))
+
+#define DCNANO_GAMMAINDEX 0x14e0
+#define GAMMAINDEX_MASK GENMASK(7, 0)
+#define GAMMAINDEX(x) FIELD_PREP(GAMMAINDEX_MASK, (x))
+
+#define DCNANO_GAMMADATA 0x1500
+#define GAMMADATA_BLUE_MASK GENMASK(7, 0)
+#define GAMMADATA_BLUE(x) FIELD_PREP(GAMMADATA_BLUE_MASK, (x))
+#define GAMMADATA_GREEN_MASK GENMASK(15, 8)
+#define GAMMADATA_GREEN(x) FIELD_PREP(GAMMADATA_GREEN_MASK, (x))
+#define GAMMADATA_RED_MASK GENMASK(23, 16)
+#define GAMMADATA_RED(x) FIELD_PREP(GAMMADATA_RED_MASK, (x))
+
+#define DCNANO_CURSORCONFIG 0x1520
+/* double buffered */
+#define CURSORCFG_FORMAT_MASK GENMASK(1, 0)
+#define CURSORCFG_FORMAT_NONE FIELD_PREP(CURSORCFG_FORMAT_MASK, 0)
+#define CURSORCFG_FORMAT_MASKED FIELD_PREP(CURSORCFG_FORMAT_MASK, 1)
+#define CURSORCFG_FORMAT_A8R8G8B8 FIELD_PREP(CURSORCFG_FORMAT_MASK, 2)
+#define CURSORCFG_DISPLAY_MASK BIT(4)
+#define CURSORCFG_DISPLAY0 0
+#define CURSORCFG_DISPLAY1 BIT(4)
+/* double buffered */
+#define CURSORCFG_HOTSPOT_Y_MASK GENMASK(12, 8)
+#define CURSORCFG_HOTSPOT_Y(x) FIELD_PREP(CURSORCFG_HOTSPOT_Y_MASK, 0)
+/* double buffered */
+#define CURSORCFG_HOTSPOT_X_MASK GENMASK(20, 16)
+#define CURSORCFG_HOTSPOT_X(x) FIELD_PREP(CURSORCFG_HOTSPOT_X_MASK, 0)
+#define CURSORCFG_FLIP_INPROGRSS_NO 0
+#define CURSORCFG_FLIP_INPROGRSS_YES BIT(31)
+
+/* double buffered */
+#define DCNANO_CURSORADDRESS 0x1530
+#define CURSORADDRESS_MASK GENMASK(31, 0)
+#define CURSORADDRESS(x) FIELD_PREP(CURSORADDRESS_MASK, (x))
+#define CURSORADDRESS_TYPE_SYSTEM 0
+#define CURSORADDRESS_TYPE_VIRTUAL BIT(31)
+
+/* double buffered */
+#define DCNANO_CURSORLOCATION 0x1540
+#define CURSORLOCATION_X_MASK GENMASK(12, 0)
+#define CURSORLOCATION_X(x) FIELD_PREP(CURSORLOCATION_X_MASK, (x))
+#define CURSORLOCATION_X_MAX FIELD_MAX(CURSORLOCATION_X_MASK)
+#define CURSORLOCATION_Y_MASK GENMASK(27, 16)
+#define CURSORLOCATION_Y(x) FIELD_PREP(CURSORLOCATION_Y_MASK, (x))
+#define CURSORLOCATION_Y_MAX FIELD_MAX(CURSORLOCATION_Y_MASK)
+
+/* double buffered */
+#define DCNANO_CURSORBACKGROUND 0x1550
+/* double buffered */
+#define DCNANO_CURSORFOREGROUND 0x1560
+#define CURSOR_BLUE_MASK GENMASK(7, 0)
+#define CURSOR_BLUE(x) FIELD_PREP(CURSOR_BLUE_MASK, (x))
+#define CURSOR_GREEN_MASK GENMASK(15, 8)
+#define CURSOR_GREEN(x) FIELD_PREP(CURSOR_GREEN_MASK, (x))
+#define CURSOR_RED_MASK GENMASK(23, 16)
+#define CURSOR_RED(x) FIELD_PREP(CURSOR_RED_MASK, (x))
+
+#define DCNANO_DISPLAYINTR 0x1600
+#define DCNANO_DISPLAYINTRENABLE 0x1610
+#define DISPLAYINTR_DISP0 BIT(0)
+
+#define DCNANO_DBICONFIG 0x1620
+#define DBICFG_DBI_TYPE_MASK GENMASK(1, 0)
+#define DBICFG_DBI_TYPE_A_FIXED_E FIELD_PREP(DBICFG_DBI_TYPE_MASK, 0)
+#define DBICFG_DBI_TYPE_A_CLOCK_E FIELD_PREP(DBICFG_DBI_TYPE_MASK, 1)
+#define DBICFG_DBI_TYPE_B FIELD_PREP(DBICFG_DBI_TYPE_MASK, 2)
+#define DBICFG_DBI_TYPE_C FIELD_PREP(DBICFG_DBI_TYPE_MASK, 3)
+#define DBICFG_DATA_FORMAT_MASK GENMASK(5, 2)
+
+/* 8bit data bus - D[7 : 0] */
+/* 8bpp */
+#define DBICFG_DATA_FORMAT_D8R3G3B2 FIELD_PREP(DBICFG_DATA_FORMAT_MASK, 0)
+/* 12bpp */
+#define DBICFG_DATA_FORMAT_D8R4G4B4 FIELD_PREP(DBICFG_DATA_FORMAT_MASK, 1)
+/* 16bpp */
+#define DBICFG_DATA_FORMAT_D8R5G6B5 FIELD_PREP(DBICFG_DATA_FORMAT_MASK, 2)
+/* 18bpp */
+#define DBICFG_DATA_FORMAT_D8R6G6B6 FIELD_PREP(DBICFG_DATA_FORMAT_MASK, 3)
+/* 24bpp */
+#define DBICFG_DATA_FORMAT_D8R8G8B8 FIELD_PREP(DBICFG_DATA_FORMAT_MASK, 4)
+
+/* 9bit data bus - D[8 : 0] */
+/* 18bpp */
+#define DBICFG_DATA_FORMAT_D9R6G6B6 FIELD_PREP(DBICFG_DATA_FORMAT_MASK, 5)
+
+/* 16bit data bus - D[15 : 0] */
+/* 8bpp */
+#define DBICFG_DATA_FORMAT_D16R3G3B2 FIELD_PREP(DBICFG_DATA_FORMAT_MASK, 6)
+/* 12bpp */
+#define DBICFG_DATA_FORMAT_D16R4G4B4 FIELD_PREP(DBICFG_DATA_FORMAT_MASK, 7)
+/* 16bpp */
+#define DBICFG_DATA_FORMAT_D16R5G6B5 FIELD_PREP(DBICFG_DATA_FORMAT_MASK, 8)
+/* 18bpp */
+#define DBICFG_DATA_FORMAT_D16R6G6B6OP1 FIELD_PREP(DBICFG_DATA_FORMAT_MASK, 9)
+#define DBICFG_DATA_FORMAT_D16R6G6B6OP2 FIELD_PREP(DBICFG_DATA_FORMAT_MASK, 10)
+/* 24bpp */
+#define DBICFG_DATA_FORMAT_D16R8G8B8OP1 FIELD_PREP(DBICFG_DATA_FORMAT_MASK, 11)
+#define DBICFG_DATA_FORMAT_D16R8G8B8OP2 FIELD_PREP(DBICFG_DATA_FORMAT_MASK, 12)
+
+#define DBICFG_BUS_OUTPUT_SEL_DPI 0
+#define DBICFG_BUS_OUTPUT_SEL_DBI BIT(6)
+#define DBICFG_DBIX_POLARITY_DEFAULT 0
+#define DBICFG_DBIX_POLARITY_REVERSE BIT(7)
+#define DBICFG_DBI_AC_TIME_UNIT_MASK GENMASK(11, 8)
+#define DBICFG_DBI_AC_TIME_UNIT(x) \
+ FIELD_PREP(DBICFG_DBI_AC_TIME_UNIT_MASK, (x))
+#define DBICFG_DBI_TYPEC_OPT_MASK GENMASK(13, 12)
+#define DBICFG_DBI_TYPEC_OPT1 FIELD_PREP(DBICFG_DBI_TYPEC_OPT_MASK, 0)
+#define DBICFG_DBI_TYPEC_OPT2 FIELD_PREP(DBICFG_DBI_TYPEC_OPT_MASK, 1)
+#define DBICFG_DBI_TYPEC_OPT3 FIELD_PREP(DBICFG_DBI_TYPEC_OPT_MASK, 2)
+
+#define DCNANO_DBIIFRESET 0x1640
+#define DBIIF_LEVEL_NO_RESET 0
+#define DBIIF_LEVEL_RESET BIT(0)
+
+#define DCNANO_DBIWRCHAR1 0x1660
+#define DBIWR_PERIOD_MASK GENMASK(7, 0)
+#define DBIWR_PERIOD(x) FIELD_PREP(DBIWR_PERIOD_MASK, (x))
+#define DBIWR_EOR_WR_ASSERT_MASK GENMASK(11, 8)
+#define DBIWR_EOR_WR_ASSERT(x) FIELD_PREP(DBIWR_EOR_WR_ASSERT_MASK, (x))
+#define DBIWR_CS_ASSERT_MASK GENMASK(15, 12)
+#define DBIWR_CS_ASSERT(x) FIELD_PREP(DBIWR_CS_ASSERT_MASK, (x))
+
+#define DCNANO_DBIWRCHAR2 0x1680
+#define DBIWR_EOR_WR_DE_ASRT_MASK GENMASK(7, 0)
+#define DBIWR_EOR_WR_DE_ASRT(x) \
+ FIELD_PREP(DBIWR_EOR_WR_DE_ASRT_MASK, (x))
+#define DBIWR_CS_DE_ASRT_MASK GENMASK(15, 8)
+#define DBIWR_CS_DE_ASRT(x) FIELD_PREP(DBIWR_CS_DE_ASRT_MASK, (x))
+
+#define DCNANO_DBICMD 0x16a0
+#define DBICMD_WORD_MASK GENMASK(15, 0)
+#define DBICMD_WORD(x) FIELD_PREP(DBICMD_WORD_MASK, (x))
+#define DBICMD_FLAG_MASK GENMASK(31, 30)
+#define DBICMD_FLAG_ADDRESS FIELD_PREP(DBICMD_FLAG_MASK, 0)
+#define DBICMD_FLAG_WRITE_MEM_START FIELD_PREP(DBICMD_FLAG_MASK, 1)
+#define DBICMD_FLAG_PARAMETER_OR_DATA FIELD_PREP(DBICMD_FLAG_MASK, 2)
+/* Read is unused. */
+#define DBICMD_FLAG_READ FIELD_PREP(DBICMD_FLAG_MASK, 3)
+
+#define DCNANO_DPICONFIG 0x16c0
+#define DPICFG_DATA_FORMAT_MASK GENMASK(2, 0)
+#define DPICFG_DATA_FORMAT_D16CFG1 FIELD_PREP(DPICFG_DATA_FORMAT_MASK, 0)
+#define DPICFG_DATA_FORMAT_D16CFG2 FIELD_PREP(DPICFG_DATA_FORMAT_MASK, 1)
+#define DPICFG_DATA_FORMAT_D16CFG3 FIELD_PREP(DPICFG_DATA_FORMAT_MASK, 2)
+#define DPICFG_DATA_FORMAT_D18CFG1 FIELD_PREP(DPICFG_DATA_FORMAT_MASK, 3)
+#define DPICFG_DATA_FORMAT_D18CFG2 FIELD_PREP(DPICFG_DATA_FORMAT_MASK, 4)
+#define DPICFG_DATA_FORMAT_D24 FIELD_PREP(DPICFG_DATA_FORMAT_MASK, 5)
+
+#define DCNANO_DCCHIPREV 0x16f0
+#define DCCHIPREV_MASK GENMASK(31, 0)
+#define DCCHIPREV 0x00005543
+
+#define DCNANO_DCCHIPDATE 0x1700
+#define DCCHIPDATE_MASK GENMASK(31, 0)
+#define DCCHIPDATE 0x20180612
+
+#define DCNANO_DCCHIPPATCHREV 0x1720
+#define DCCHIPPATCHREV_MASK GENMASK(31, 0)
+#define DCCHIPPATCHREV 0x00000003
+
+#define DCNANO_DCTILEINCFG 0x1740
+/* double buffered */
+#define DCTILEINCFG_TILE_FORMAT_MASK GENMASK(1, 0)
+#define DCTILEINCFG_TILE_FORMAT_NONE \
+ FIELD_PREP(DCTILEINCFG_TILE_FORMAT_MASK, 0)
+#define DCTILEINCFG_TILE_FORMAT_ARGB8888 \
+ FIELD_PREP(DCTILEINCFG_TILE_FORMAT_MASK, 1)
+#define DCTILEINCFG_TILE_FORMAT_YUY2 \
+ FIELD_PREP(DCTILEINCFG_TILE_FORMAT_MASK, 2)
+#define DCTILEINCFG_TILE_FORMAT_NV12 \
+ FIELD_PREP(DCTILEINCFG_TILE_FORMAT_MASK, 3)
+#define DCTILEINCFG_YUV_STANDARD_MASK GENMASK(3, 2)
+/* double buffered */
+#define DCTILEINCFG_YUV_BT601 \
+ FIELD_PREP(DCTILEINCFG_YUV_STANDARD_MASK, 0)
+#define DCTILEINCFG_YUV_BT709 \
+ FIELD_PREP(DCTILEINCFG_YUV_STANDARD_MASK, 1)
+/* double buffered */
+#define DCTILEINCFG_YUV2_RGB_EN_MASK BIT(4)
+#define DCTILEINCFG_YUV2_RGB_ENABLE BIT(4)
+#define DCTILEINCFG_YUV2_RGB_DISABLE 0
+#define DCTILEINCFG_CFG_MODE_EN BIT(5)
+#define DCTILEINCFG_CFG_MODE_ENABLE BIT(5)
+#define DCTILEINCFG_CFG_MODE_DISABLE 0
+
+/* double buffered */
+#define DCNANO_DCTILEUVFRAMEBUFFERADR 0x1760
+#define DCTILEUVFB_ADDRESS_MASK GENMASK(31, 0)
+#define DCTILEUVFB_ADDRESS(x) FIELD_PREP(DCTILEUVFB_ADDRESS_MASK, (x))
+#define DCTILEUVFB_ADDRESS_MAX FIELD_MAX(DCTILEUVFB_ADDRESS_MASK)
+
+/* double buffered */
+#define DCNANO_DCTILEUVFRAMEBUFFERSTR 0x1780
+#define DCTILEUVFB_STRIDE_MASK GENMASK(15, 0)
+#define DCTILEUVFB_STRIDE(x) FIELD_PREP(DCTILEUVFB_STRIDE_MASK, (x))
+#define DCTILEUVFB_STRIDE_MAX FIELD_MAX(DCTILEUVFB_STRIDE_MASK)
+
+#define DCNANO_DCPRODUCTID 0x17b0
+#define DCPRODUCTID_MASK GENMASK(31, 0)
+#define DCPRODUCTID 0x02000361
+
+#define DCNANO_DCSTATUS 0x1800
+#define DCSTATUS_DBI_TYPEC_FIFO_FULL BIT(0)
+
+#define DCNANO_DEBUGCOUNTERSELECT 0x1820
+#define DEBUGCOUNTERSELECT_MASK GENMASK(7, 0)
+#define TOTAL_AXI_RD_REQ_CNT FIELD_PREP(DEBUGCOUNTERSELECT_MASK, 0)
+#define TOTAL_AXI_RD_LAST_CNT FIELD_PREP(DEBUGCOUNTERSELECT_MASK, 1)
+#define TOTAL_AXI_REQ_BURST_CNT FIELD_PREP(DEBUGCOUNTERSELECT_MASK, 2)
+#define TOTAL_AXI_RD_BURST_CNT FIELD_PREP(DEBUGCOUNTERSELECT_MASK, 3)
+#define TOTAL_PIXEL_CNT FIELD_PREP(DEBUGCOUNTERSELECT_MASK, 4)
+#define TOTAL_FRAME_CNT FIELD_PREP(DEBUGCOUNTERSELECT_MASK, 5)
+#define TOTAL_INPUT_DBI_CMD_CNT FIELD_PREP(DEBUGCOUNTERSELECT_MASK, 6)
+#define TOTAL_OUTPUT_DBI_CMD_CNT FIELD_PREP(DEBUGCOUNTERSELECT_MASK, 7)
+#define DEBUG_SIGNALS0 FIELD_PREP(DEBUGCOUNTERSELECT_MASK, 8)
+#define RESET_ALL_CNTS FIELD_PREP(DEBUGCOUNTERSELECT_MASK, 0xFF)
+
+#define DCNANO_DEBUGCOUNTERVALUE 0x1840
+#define DEBUGCOUNTERVALUE_MASK GENMASK(31, 0)
+#define DEBUGCOUNTERVALUE(x) FIELD_PREP(DEBUGCOUNTERVALUE_MASK, (x))
+#define DEBUGCOUNTERVALUE_MAX FIELD_MAX(DEBUGCOUNTERVALUE_MASK)
+
+static inline u32 dcnano_read(struct dcnano_dev *dcnano, unsigned int reg)
+{
+ return readl(dcnano->mmio_base + reg);
+}
+
+static inline void dcnano_write(struct dcnano_dev *dcnano,
+ unsigned int reg, u32 value)
+{
+ writel(value, dcnano->mmio_base + reg);
+}
+
+static inline void dcnano_write_mask(struct dcnano_dev *dcnano,
+ unsigned int reg, u32 mask, u32 value)
+{
+ u32 tmp;
+
+ tmp = dcnano_read(dcnano, reg);
+ tmp &= ~mask;
+ dcnano_write(dcnano, reg, tmp | value);
+}
+
+#endif
diff --git a/drivers/gpu/drm/imx/dcss/Kconfig b/drivers/gpu/drm/imx/dcss/Kconfig
index 2b17a964ff05..2c5e159dab8f 100644
--- a/drivers/gpu/drm/imx/dcss/Kconfig
+++ b/drivers/gpu/drm/imx/dcss/Kconfig
@@ -1,5 +1,6 @@
config DRM_IMX_DCSS
tristate "i.MX8MQ DCSS"
+ depends on DRM && OF
select IMX_IRQSTEER
select DRM_KMS_CMA_HELPER
select VIDEOMODE_HELPERS
diff --git a/drivers/gpu/drm/imx/dcss/Makefile b/drivers/gpu/drm/imx/dcss/Makefile
index 8c7c8da42792..eb3a1860edd0 100644
--- a/drivers/gpu/drm/imx/dcss/Makefile
+++ b/drivers/gpu/drm/imx/dcss/Makefile
@@ -1,6 +1,7 @@
imx-dcss-objs := dcss-drv.o dcss-dev.o dcss-blkctl.o dcss-ctxld.o dcss-dtg.o \
dcss-ss.o dcss-dpr.o dcss-scaler.o dcss-kms.o dcss-crtc.o \
- dcss-plane.o
+ dcss-plane.o dcss-dec400d.o dcss-hdr10.o dcss-wrscl.o \
+ dcss-rdsrc.o dcss-dtrc.o
obj-$(CONFIG_DRM_IMX_DCSS) += imx-dcss.o
diff --git a/drivers/gpu/drm/imx/dcss/dcss-crtc.c b/drivers/gpu/drm/imx/dcss/dcss-crtc.c
index 31267c00782f..6260cff317f0 100644
--- a/drivers/gpu/drm/imx/dcss/dcss-crtc.c
+++ b/drivers/gpu/drm/imx/dcss/dcss-crtc.c
@@ -3,19 +3,77 @@
* Copyright 2019 NXP.
*/
+#include <drm/bridge/cdns-mhdp.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
+#include <drm/drm_atomic_state_helper.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_edid.h>
#include <drm/drm_vblank.h>
+
+#include <linux/hdmi.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include "dcss-dev.h"
#include "dcss-kms.h"
+static void dcss_drm_crtc_reset(struct drm_crtc *crtc)
+{
+ struct dcss_crtc_state *state;
+
+ if (crtc->state) {
+ __drm_atomic_helper_crtc_destroy_state(crtc->state);
+
+ state = to_dcss_crtc_state(crtc->state);
+ kfree(state);
+ crtc->state = NULL;
+ }
+
+ state = kzalloc(sizeof(*state), GFP_KERNEL);
+ if (state) {
+ crtc->state = &state->base;
+ crtc->state->crtc = crtc;
+ }
+}
+
+static struct drm_crtc_state *
+dcss_drm_crtc_atomic_duplicate_state(struct drm_crtc *crtc)
+{
+ struct dcss_crtc_state *state, *copy;
+
+ if (WARN_ON(!crtc->state))
+ return NULL;
+
+ copy = kzalloc(sizeof(*copy), GFP_KERNEL);
+ if (!copy)
+ return NULL;
+
+ __drm_atomic_helper_crtc_duplicate_state(crtc, &copy->base);
+ state = to_dcss_crtc_state(crtc->state);
+ copy->output_encoding = state->output_encoding;
+ copy->opipe_nl = state->opipe_nl;
+ copy->opipe_g = state->opipe_g;
+ copy->opipe_pr = state->opipe_pr;
+
+ return &copy->base;
+}
+
+static void dcss_drm_crtc_atomic_destroy_state(struct drm_crtc *crtc,
+ struct drm_crtc_state *state)
+{
+ struct dcss_crtc_state *dcss_crtc_state;
+
+ if (state) {
+ __drm_atomic_helper_crtc_destroy_state(state);
+ dcss_crtc_state = to_dcss_crtc_state(state);
+ kfree(dcss_crtc_state);
+ }
+}
+
static int dcss_enable_vblank(struct drm_crtc *crtc)
{
- struct dcss_crtc *dcss_crtc = container_of(crtc, struct dcss_crtc,
- base);
+ struct dcss_crtc *dcss_crtc = to_dcss_crtc(crtc);
struct dcss_dev *dcss = crtc->dev->dev_private;
dcss_dtg_vblank_irq_enable(dcss->dtg, true);
@@ -29,15 +87,15 @@ static int dcss_enable_vblank(struct drm_crtc *crtc)
static void dcss_disable_vblank(struct drm_crtc *crtc)
{
- struct dcss_crtc *dcss_crtc = container_of(crtc, struct dcss_crtc,
- base);
+ struct dcss_crtc *dcss_crtc = to_dcss_crtc(crtc);
struct dcss_dev *dcss = dcss_crtc->base.dev->dev_private;
disable_irq_nosync(dcss_crtc->irq);
dcss_dtg_vblank_irq_enable(dcss->dtg, false);
- if (dcss_crtc->disable_ctxld_kick_irq)
+ if (!dcss_dtrc_is_running(dcss->dtrc) &&
+ dcss_crtc->disable_ctxld_kick_irq)
dcss_dtg_ctxld_kick_irq_enable(dcss->dtg, false);
}
@@ -45,9 +103,9 @@ static const struct drm_crtc_funcs dcss_crtc_funcs = {
.set_config = drm_atomic_helper_set_config,
.destroy = drm_crtc_cleanup,
.page_flip = drm_atomic_helper_page_flip,
- .reset = drm_atomic_helper_crtc_reset,
- .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
- .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
+ .reset = dcss_drm_crtc_reset,
+ .atomic_duplicate_state = dcss_drm_crtc_atomic_duplicate_state,
+ .atomic_destroy_state = dcss_drm_crtc_atomic_destroy_state,
.enable_vblank = dcss_enable_vblank,
.disable_vblank = dcss_disable_vblank,
};
@@ -61,8 +119,7 @@ static void dcss_crtc_atomic_begin(struct drm_crtc *crtc,
static void dcss_crtc_atomic_flush(struct drm_crtc *crtc,
struct drm_atomic_state *state)
{
- struct dcss_crtc *dcss_crtc = container_of(crtc, struct dcss_crtc,
- base);
+ struct dcss_crtc *dcss_crtc = to_dcss_crtc(crtc);
struct dcss_dev *dcss = dcss_crtc->base.dev->dev_private;
spin_lock_irq(&crtc->dev->event_lock);
@@ -82,9 +139,10 @@ static void dcss_crtc_atomic_enable(struct drm_crtc *crtc,
{
struct drm_crtc_state *old_crtc_state = drm_atomic_get_old_crtc_state(state,
crtc);
- struct dcss_crtc *dcss_crtc = container_of(crtc, struct dcss_crtc,
- base);
+ struct dcss_crtc *dcss_crtc = to_dcss_crtc(crtc);
struct dcss_dev *dcss = dcss_crtc->base.dev->dev_private;
+ struct dcss_crtc_state *dcss_crtc_state =
+ to_dcss_crtc_state(crtc->state);
struct drm_display_mode *mode = &crtc->state->adjusted_mode;
struct drm_display_mode *old_mode = &old_crtc_state->adjusted_mode;
struct videomode vm;
@@ -95,8 +153,8 @@ static void dcss_crtc_atomic_enable(struct drm_crtc *crtc,
vm.pixelclock = mode->crtc_clock * 1000;
- dcss_ss_subsam_set(dcss->ss);
- dcss_dtg_css_set(dcss->dtg);
+ dcss_ss_subsam_set(dcss->ss, dcss_crtc_state->output_encoding);
+ dcss_dtg_css_set(dcss->dtg, dcss_crtc_state->output_encoding);
if (!drm_mode_equal(mode, old_mode) || !old_crtc_state->active) {
dcss_dtg_sync_set(dcss->dtg, &vm);
@@ -118,8 +176,7 @@ static void dcss_crtc_atomic_disable(struct drm_crtc *crtc,
{
struct drm_crtc_state *old_crtc_state = drm_atomic_get_old_crtc_state(state,
crtc);
- struct dcss_crtc *dcss_crtc = container_of(crtc, struct dcss_crtc,
- base);
+ struct dcss_crtc *dcss_crtc = to_dcss_crtc(crtc);
struct dcss_dev *dcss = dcss_crtc->base.dev->dev_private;
struct drm_display_mode *mode = &crtc->state->adjusted_mode;
struct drm_display_mode *old_mode = &old_crtc_state->adjusted_mode;
@@ -158,11 +215,25 @@ static void dcss_crtc_atomic_disable(struct drm_crtc *crtc,
pm_runtime_put_autosuspend(dcss->dev);
}
+static enum drm_mode_status dcss_crtc_mode_valid(struct drm_crtc *crtc,
+ const struct drm_display_mode *mode)
+{
+ /*
+ * From DCSS perspective, dissallow any mode higher than
+ * 3840x2160 or 2160x3840.
+ */
+ if (mode->hdisplay * mode->vdisplay > 3840 * 2160)
+ return MODE_BAD;
+
+ return MODE_OK;
+}
+
static const struct drm_crtc_helper_funcs dcss_helper_funcs = {
.atomic_begin = dcss_crtc_atomic_begin,
.atomic_flush = dcss_crtc_atomic_flush,
.atomic_enable = dcss_crtc_atomic_enable,
.atomic_disable = dcss_crtc_atomic_disable,
+ .mode_valid = dcss_crtc_mode_valid,
};
static irqreturn_t dcss_crtc_irq_handler(int irq, void *dev_id)
@@ -181,6 +252,144 @@ static irqreturn_t dcss_crtc_irq_handler(int irq, void *dev_id)
return IRQ_HANDLED;
}
+static void __dcss_crtc_setup_opipe_gamut(u32 colorspace,
+ const struct drm_display_mode *mode,
+ enum dcss_hdr10_gamut *g,
+ enum dcss_hdr10_nonlinearity *nl)
+{
+ u8 vic;
+
+ switch (colorspace) {
+ case DRM_MODE_COLORIMETRY_BT709_YCC:
+ case DRM_MODE_COLORIMETRY_XVYCC_709:
+ *g = G_REC709;
+ *nl = NL_REC709;
+ return;
+ case DRM_MODE_COLORIMETRY_SMPTE_170M_YCC:
+ case DRM_MODE_COLORIMETRY_XVYCC_601:
+ case DRM_MODE_COLORIMETRY_SYCC_601:
+ case DRM_MODE_COLORIMETRY_OPYCC_601:
+ *g = G_REC601_NTSC;
+ *nl = NL_REC709;
+ return;
+ case DRM_MODE_COLORIMETRY_BT2020_CYCC:
+ case DRM_MODE_COLORIMETRY_BT2020_RGB:
+ case DRM_MODE_COLORIMETRY_BT2020_YCC:
+ *g = G_REC2020;
+ *nl = NL_REC2084;
+ return;
+ case DRM_MODE_COLORIMETRY_OPRGB:
+ *g = G_REC709;
+ *nl = NL_SRGB;
+ return;
+ default:
+ break;
+ }
+
+ /*
+ * If we reached this point, it means the default colorimetry is used.
+ */
+
+ /* non-CEA mode, sRGB is used */
+ vic = drm_match_cea_mode(mode);
+ if (vic == 0) {
+ *g = G_REC709;
+ *nl = NL_SRGB;
+ return;
+ }
+
+ /* use REC709 otherwise, by default */
+ *g = G_REC709;
+ *nl = NL_REC709;
+}
+
+static void __dcss_crtc_setup_opipe(struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct dcss_crtc_state *dcss_crtc_state = to_dcss_crtc_state(crtc_state);
+ struct dcss_dev *dcss = crtc_state->crtc->dev->dev_private;
+ enum hdmi_quantization_range qr;
+
+ qr = drm_default_rgb_quant_range(&crtc_state->adjusted_mode);
+
+ __dcss_crtc_setup_opipe_gamut(conn_state->colorspace,
+ &crtc_state->adjusted_mode,
+ &dcss_crtc_state->opipe_g,
+ &dcss_crtc_state->opipe_nl);
+
+ dcss_crtc_state->opipe_pr = qr == HDMI_QUANTIZATION_RANGE_FULL ?
+ PR_FULL : PR_LIMITED;
+
+ dcss_crtc_state->output_encoding = DCSS_PIPE_OUTPUT_RGB;
+
+ if (dcss->hdmi_output) {
+ struct cdns_mhdp_device *mhdp_dev =
+ container_of(conn_state->connector,
+ struct cdns_mhdp_device,
+ connector.base);
+
+ switch (mhdp_dev->video_info.color_fmt) {
+ case YCBCR_4_2_2:
+ dcss_crtc_state->output_encoding =
+ DCSS_PIPE_OUTPUT_YUV422;
+ break;
+ case YCBCR_4_2_0:
+ dcss_crtc_state->output_encoding =
+ DCSS_PIPE_OUTPUT_YUV420;
+ break;
+ case YCBCR_4_4_4:
+ dcss_crtc_state->output_encoding =
+ DCSS_PIPE_OUTPUT_YUV444;
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+int dcss_crtc_setup_opipe(struct drm_device *dev, struct drm_atomic_state *state)
+{
+ struct dcss_dev *dcss = dev->dev_private;
+ struct dcss_kms_dev *dcss_kms =
+ container_of(dev, struct dcss_kms_dev, base);
+ struct drm_crtc_state *crtc_state;
+ struct drm_connector *conn;
+ struct drm_connector_state *conn_state;
+ int i, ret;
+
+ crtc_state = drm_atomic_get_crtc_state(state, &dcss_kms->crtc.base);
+ if (WARN_ON(IS_ERR(crtc_state))) {
+ ret = PTR_ERR(crtc_state);
+ dev_dbg(dcss->dev, "failed to get CRTC state: %d\n", ret);
+ return ret;
+ }
+
+ if (!dcss_drv_is_componentized(dcss->dev)) {
+ conn_state = drm_atomic_get_connector_state(state,
+ dcss_kms->connector);
+ if (IS_ERR(conn_state)) {
+ ret = PTR_ERR(conn_state);
+ dev_dbg(dcss->dev,
+ "failed to get connector state: %d\n", ret);
+ return ret;
+ }
+
+ __dcss_crtc_setup_opipe(crtc_state, conn_state);
+ } else {
+ for_each_new_connector_in_state(state, conn, conn_state, i) {
+ if (!conn_state->best_encoder)
+ continue;
+
+ if (!crtc_state->active)
+ continue;
+
+ __dcss_crtc_setup_opipe(crtc_state, conn_state);
+ }
+ }
+
+ return 0;
+}
+
int dcss_crtc_init(struct dcss_crtc *crtc, struct drm_device *drm)
{
struct dcss_dev *dcss = drm->dev_private;
@@ -188,7 +397,7 @@ int dcss_crtc_init(struct dcss_crtc *crtc, struct drm_device *drm)
int ret;
crtc->plane[0] = dcss_plane_init(drm, drm_crtc_mask(&crtc->base),
- DRM_PLANE_TYPE_PRIMARY, 0);
+ DRM_PLANE_TYPE_PRIMARY, 2);
if (IS_ERR(crtc->plane[0]))
return PTR_ERR(crtc->plane[0]);
@@ -202,6 +411,18 @@ int dcss_crtc_init(struct dcss_crtc *crtc, struct drm_device *drm)
return ret;
}
+ crtc->plane[1] = dcss_plane_init(drm, drm_crtc_mask(&crtc->base),
+ DRM_PLANE_TYPE_OVERLAY, 1);
+ if (IS_ERR(crtc->plane[1]))
+ crtc->plane[1] = NULL;
+
+ crtc->plane[2] = dcss_plane_init(drm, drm_crtc_mask(&crtc->base),
+ DRM_PLANE_TYPE_OVERLAY, 0);
+ if (IS_ERR(crtc->plane[2]))
+ crtc->plane[2] = NULL;
+
+ drm_plane_create_alpha_property(&crtc->plane[0]->base);
+
crtc->irq = platform_get_irq_byname(pdev, "vblank");
if (crtc->irq < 0)
return crtc->irq;
@@ -218,6 +439,26 @@ int dcss_crtc_init(struct dcss_crtc *crtc, struct drm_device *drm)
return 0;
}
+void dcss_crtc_attach_color_mgmt_properties(struct dcss_crtc *crtc)
+{
+ int i;
+
+ /* create color management properties only for video planes */
+ for (i = 1; i < 3; i++) {
+ if (crtc->plane[i]->type == DRM_PLANE_TYPE_PRIMARY)
+ return;
+
+ drm_plane_create_color_properties(&crtc->plane[i]->base,
+ BIT(DRM_COLOR_YCBCR_BT601) |
+ BIT(DRM_COLOR_YCBCR_BT709) |
+ BIT(DRM_COLOR_YCBCR_BT2020),
+ BIT(DRM_COLOR_YCBCR_FULL_RANGE) |
+ BIT(DRM_COLOR_YCBCR_LIMITED_RANGE),
+ DRM_COLOR_YCBCR_BT709,
+ DRM_COLOR_YCBCR_FULL_RANGE);
+ }
+}
+
void dcss_crtc_deinit(struct dcss_crtc *crtc, struct drm_device *drm)
{
free_irq(crtc->irq, crtc);
diff --git a/drivers/gpu/drm/imx/dcss/dcss-ctxld.c b/drivers/gpu/drm/imx/dcss/dcss-ctxld.c
index 3a84cb3209c4..06668205094b 100644
--- a/drivers/gpu/drm/imx/dcss/dcss-ctxld.c
+++ b/drivers/gpu/drm/imx/dcss/dcss-ctxld.c
@@ -267,6 +267,11 @@ static int dcss_ctxld_enable_locked(struct dcss_ctxld *ctxld)
dcss_scaler_write_sclctrl(dcss->scaler);
+ if (dcss_dtrc_is_running(dcss->dtrc)) {
+ dcss_dtrc_switch_banks(dcss->dtrc);
+ ctxld->armed = true;
+ }
+
sb_hp_cnt = ctxld->ctx_size[curr_ctx][CTX_SB_HP];
sb_lp_cnt = ctxld->ctx_size[curr_ctx][CTX_SB_LP];
db_cnt = ctxld->ctx_size[curr_ctx][CTX_DB];
diff --git a/drivers/gpu/drm/imx/dcss/dcss-dec400d.c b/drivers/gpu/drm/imx/dcss/dcss-dec400d.c
new file mode 100644
index 000000000000..11636d813802
--- /dev/null
+++ b/drivers/gpu/drm/imx/dcss/dcss-dec400d.c
@@ -0,0 +1,270 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2019 NXP.
+ */
+
+#include <linux/device.h>
+#include <linux/bitops.h>
+#include <linux/io.h>
+#include <drm/drm_fourcc.h>
+
+#include "dcss-dev.h"
+
+/* DEC400D registers offsets */
+#define DEC400D_READCONFIG_BASE 0x800
+#define DEC400D_READCONFIG(i) (DEC400D_READCONFIG_BASE + ((i) << 2))
+#define COMPRESSION_ENABLE_BIT BIT(0)
+#define COMPRESSION_FORMAT_POS 3
+#define COMPRESSION_ALIGN_MODE_POS 16
+#define TILE_ALIGN_MODE_POS 22
+#define TILE_MODE_POS 25
+#define DEC400D_READBUFFERBASE0 0x900
+#define DEC400D_READCACHEBASE0 0x980
+#define DEC400D_CONTROL 0xB00
+#define DEC400D_CLEAR 0xB80
+#define DEC400D_READBUFFERBASE0 0x900
+#define DEC400D_READCACHEBASE0 0x980
+#define DEC400D_CONTROL 0xB00
+#define DISABLE_COMPRESSION_BIT BIT(1)
+#define SHADOW_TRIGGER_BIT BIT(29)
+#define DEC400_CFMT_ARGB8 0x0
+#define DEC400_CFMT_XRGB8 0x1
+#define DEC400_CFMT_AYUV 0x2
+#define DEC400_CFMT_UYVY 0x3
+#define DEC400_CFMT_YUY2 0x4
+#define DEC400_CFMT_YUV_ONLY 0x5
+#define DEC400_CFMT_UV_MIX 0x6
+#define DEC400_CFMT_ARGB4 0x7
+#define DEC400_CFMT_XRGB4 0x8
+#define DEC400_CFMT_A1R5G5B5 0x9
+#define DEC400_CFMT_X1R5G5B5 0xA
+#define DEC400_CFMT_R5G6B5 0xB
+#define DEC400_CFMT_Z24S8 0xC
+#define DEC400_CFMT_Z24 0xD
+#define DEC400_CFMT_Z16 0xE
+#define DEC400_CFMT_A2R10G10B10 0xF
+#define DEC400_CFMT_BAYER 0x10
+#define DEC400_CFMT_SIGNED_BAYER 0x11
+
+struct dcss_dec400d {
+ struct device *dev;
+ void __iomem *base_reg;
+ u32 base_ofs;
+ struct dcss_ctxld *ctxld;
+ u32 ctx_id;
+ bool bypass; /* bypass or decompress */
+};
+
+static void dcss_dec400d_write(struct dcss_dec400d *dec400d,
+ u32 value,
+ u32 offset)
+{
+ dcss_ctxld_write(dec400d->ctxld, dec400d->ctx_id,
+ value, dec400d->base_ofs + offset);
+}
+
+int dcss_dec400d_init(struct dcss_dev *dcss, unsigned long dec400d_base)
+{
+ struct dcss_dec400d *dec400d;
+ int ret;
+
+ dec400d = kzalloc(sizeof(*dec400d), GFP_KERNEL);
+ if (!dec400d)
+ return -ENOMEM;
+
+ dcss->dec400d = dec400d;
+ dec400d->dev = dcss->dev;
+ dec400d->ctxld = dcss->ctxld;
+
+ dec400d->base_reg = ioremap(dec400d_base, SZ_4K);
+ if (!dec400d->base_reg) {
+ dev_err(dcss->dev, "dec400d: unable to remap dec400d base\n");
+ ret = -ENOMEM;
+ goto free_mem;
+ }
+
+ dec400d->base_ofs = dec400d_base;
+
+ dec400d->ctx_id = CTX_SB_HP;
+
+ return 0;
+
+free_mem:
+ kfree(dcss->dec400d);
+ return ret;
+}
+
+void dcss_dec400d_exit(struct dcss_dec400d *dec400d)
+{
+ if (dec400d->base_reg)
+ iounmap(dec400d->base_reg);
+
+ kfree(dec400d);
+}
+
+void dcss_dec400d_read_config(struct dcss_dec400d *dec400d,
+ u32 read_id,
+ bool compress_en,
+ u32 compress_format)
+{
+ u32 cformat = 0;
+ u32 read_config = 0x0;
+
+ /* TODO: using 'read_id' 0 by default */
+ if (read_id) {
+ WARN_ON(1);
+ return;
+ }
+
+ if (!compress_en)
+ goto config;
+
+ switch (compress_format) {
+ case _VIV_CFMT_ARGB8:
+ cformat = DEC400_CFMT_ARGB8;
+ break;
+ case _VIV_CFMT_XRGB8:
+ cformat = DEC400_CFMT_XRGB8;
+ break;
+ case _VIV_CFMT_AYUV:
+ cformat = DEC400_CFMT_AYUV;
+ break;
+ case _VIV_CFMT_UYVY:
+ cformat = DEC400_CFMT_UYVY;
+ break;
+ case _VIV_CFMT_YUY2:
+ cformat = DEC400_CFMT_YUY2;
+ break;
+ case _VIV_CFMT_YUV_ONLY:
+ cformat = DEC400_CFMT_YUV_ONLY;
+ break;
+ case _VIV_CFMT_UV_MIX:
+ cformat = DEC400_CFMT_UV_MIX;
+ break;
+ case _VIV_CFMT_ARGB4:
+ cformat = DEC400_CFMT_ARGB4;
+ break;
+ case _VIV_CFMT_XRGB4:
+ cformat = DEC400_CFMT_XRGB4;
+ break;
+ case _VIV_CFMT_A1R5G5B5:
+ cformat = DEC400_CFMT_A1R5G5B5;
+ break;
+ case _VIV_CFMT_X1R5G5B5:
+ cformat = DEC400_CFMT_X1R5G5B5;
+ break;
+ case _VIV_CFMT_R5G6B5:
+ cformat = DEC400_CFMT_R5G6B5;
+ break;
+ case _VIV_CFMT_Z24S8:
+ cformat = DEC400_CFMT_Z24S8;
+ break;
+ case _VIV_CFMT_Z24:
+ cformat = DEC400_CFMT_Z24;
+ break;
+ case _VIV_CFMT_Z16:
+ cformat = DEC400_CFMT_Z16;
+ break;
+ case _VIV_CFMT_A2R10G10B10:
+ cformat = DEC400_CFMT_A2R10G10B10;
+ break;
+ case _VIV_CFMT_BAYER:
+ cformat = DEC400_CFMT_BAYER;
+ break;
+ case _VIV_CFMT_SIGNED_BAYER:
+ cformat = DEC400_CFMT_SIGNED_BAYER;
+ break;
+ default:
+ /* TODO: not support yet */
+ WARN_ON(1);
+ return;
+ }
+
+ /* Dec compress format */
+ read_config |= cformat << COMPRESSION_FORMAT_POS;
+
+ /* ALIGN32_BYTE */
+ read_config |= 0x2 << COMPRESSION_ALIGN_MODE_POS;
+
+ /* TILE1_ALIGN */
+ read_config |= 0x0 << TILE_ALIGN_MODE_POS;
+
+ /* TILE8x4 */
+ read_config |= 0x3 << TILE_MODE_POS;
+
+ /* Compression Enable */
+ read_config |= COMPRESSION_ENABLE_BIT;
+
+config:
+ dcss_dec400d_write(dec400d, read_config, DEC400D_READCONFIG(read_id));
+}
+
+void dcss_dec400d_bypass(struct dcss_dec400d *dec400d)
+{
+ u32 control;
+
+ dcss_dec400d_read_config(dec400d, 0, false, 0);
+
+ control = dcss_readl(dec400d->base_reg + DEC400D_CONTROL);
+ dev_dbg(dec400d->dev, "%s: dec400d control = %#x\n", __func__, control);
+
+ control |= DISABLE_COMPRESSION_BIT;
+ dcss_dec400d_write(dec400d, control, DEC400D_CONTROL);
+
+ /* Trigger shadow registers */
+ control |= SHADOW_TRIGGER_BIT;
+ dcss_dec400d_write(dec400d, control, DEC400D_CONTROL);
+
+ dec400d->bypass = true;
+}
+
+void dcss_dec400d_shadow_trig(struct dcss_dec400d *dec400d)
+{
+ u32 control;
+
+ /* do nothing */
+ if (dec400d->bypass)
+ return;
+
+ control = dcss_readl(dec400d->base_reg + DEC400D_CONTROL);
+
+ /* Trigger shadow registers */
+ control |= SHADOW_TRIGGER_BIT;
+ dcss_dec400d_write(dec400d, control, DEC400D_CONTROL);
+}
+
+void dcss_dec400d_addr_set(struct dcss_dec400d *dec400d, u32 baddr, u32 caddr)
+{
+ /* set frame buffer base addr */
+ dcss_dec400d_write(dec400d, baddr, DEC400D_READBUFFERBASE0);
+
+ /* set tile status cache addr */
+ dcss_dec400d_write(dec400d, caddr, DEC400D_READCACHEBASE0);
+
+ dec400d->bypass = false;
+}
+
+void dcss_dec400d_fast_clear_config(struct dcss_dec400d *dec400d,
+ u32 fc_value,
+ bool enable)
+{
+ dcss_dec400d_write(dec400d, fc_value, DEC400D_CLEAR);
+}
+
+void dcss_dec400d_enable(struct dcss_dec400d *dec400d)
+{
+ u32 control;
+
+ if (dec400d->bypass)
+ return;
+
+ control = dcss_readl(dec400d->base_reg + DEC400D_CONTROL);
+
+ /* enable compression */
+ control &= ~(DISABLE_COMPRESSION_BIT);
+ dcss_dec400d_write(dec400d, control, DEC400D_CONTROL);
+
+ /* Trigger shadow registers */
+ control |= SHADOW_TRIGGER_BIT;
+ dcss_dec400d_write(dec400d, control, DEC400D_CONTROL);
+}
diff --git a/drivers/gpu/drm/imx/dcss/dcss-dev.c b/drivers/gpu/drm/imx/dcss/dcss-dev.c
index 3f5750cc2673..a964cb6c9c58 100644
--- a/drivers/gpu/drm/imx/dcss/dcss-dev.c
+++ b/drivers/gpu/drm/imx/dcss/dcss-dev.c
@@ -10,6 +10,7 @@
#include <linux/slab.h>
#include <drm/drm_bridge_connector.h>
#include <drm/drm_device.h>
+#include <linux/busfreq-imx.h>
#include <drm/drm_modeset_helper.h>
#include "dcss-dev.h"
@@ -17,6 +18,11 @@
static void dcss_clocks_enable(struct dcss_dev *dcss)
{
+ if (dcss->hdmi_output) {
+ clk_prepare_enable(dcss->pll_phy_ref_clk);
+ clk_prepare_enable(dcss->pll_src_clk);
+ }
+
clk_prepare_enable(dcss->axi_clk);
clk_prepare_enable(dcss->apb_clk);
clk_prepare_enable(dcss->rtrm_clk);
@@ -31,6 +37,11 @@ static void dcss_clocks_disable(struct dcss_dev *dcss)
clk_disable_unprepare(dcss->rtrm_clk);
clk_disable_unprepare(dcss->apb_clk);
clk_disable_unprepare(dcss->axi_clk);
+
+ if (dcss->hdmi_output) {
+ clk_disable_unprepare(dcss->pll_src_clk);
+ clk_disable_unprepare(dcss->pll_phy_ref_clk);
+ }
}
static void dcss_disable_dtg_and_ss_cb(void *data)
@@ -83,22 +94,57 @@ static int dcss_submodules_init(struct dcss_dev *dcss)
if (ret)
goto ss_err;
+ ret = dcss_dtrc_init(dcss, base_addr + devtype->dtrc_ofs);
+ if (ret)
+ goto dtrc_err;
+
ret = dcss_dpr_init(dcss, base_addr + devtype->dpr_ofs);
if (ret)
goto dpr_err;
+ ret = dcss_wrscl_init(dcss, base_addr + devtype->wrscl_ofs);
+ if (ret)
+ goto wrscl_err;
+
+ ret = dcss_rdsrc_init(dcss, base_addr + devtype->rdsrc_ofs);
+ if (ret)
+ goto rdsrc_err;
+
ret = dcss_scaler_init(dcss, base_addr + devtype->scaler_ofs);
if (ret)
goto scaler_err;
+ ret = dcss_dec400d_init(dcss, base_addr + devtype->dec400d_ofs);
+ if (ret)
+ goto dec400d_err;
+
+ ret = dcss_hdr10_init(dcss, base_addr + devtype->hdr10_ofs);
+ if (ret)
+ goto hdr10_err;
+
dcss_clocks_disable(dcss);
return 0;
+hdr10_err:
+ dcss_dec400d_exit(dcss->dec400d);
+
+dec400d_err:
+ dcss_scaler_exit(dcss->scaler);
+
scaler_err:
+ dcss_rdsrc_exit(dcss->rdsrc);
+
+rdsrc_err:
+ dcss_wrscl_exit(dcss->wrscl);
+
+wrscl_err:
dcss_dpr_exit(dcss->dpr);
dpr_err:
+ dcss_dtrc_exit(dcss->dtrc);
+
+dtrc_err:
dcss_ss_exit(dcss->ss);
ss_err:
@@ -118,8 +164,13 @@ ctxld_err:
static void dcss_submodules_stop(struct dcss_dev *dcss)
{
dcss_clocks_enable(dcss);
+ dcss_hdr10_exit(dcss->hdr10);
+ dcss_dec400d_exit(dcss->dec400d);
dcss_scaler_exit(dcss->scaler);
+ dcss_rdsrc_exit(dcss->rdsrc);
+ dcss_wrscl_exit(dcss->wrscl);
dcss_dpr_exit(dcss->dpr);
+ dcss_dtrc_exit(dcss->dtrc);
dcss_ss_exit(dcss->ss);
dcss_dtg_exit(dcss->dtg);
dcss_ctxld_exit(dcss->ctxld);
@@ -133,17 +184,20 @@ static int dcss_clks_init(struct dcss_dev *dcss)
struct {
const char *id;
struct clk **clk;
+ bool required;
} clks[] = {
- {"apb", &dcss->apb_clk},
- {"axi", &dcss->axi_clk},
- {"pix", &dcss->pix_clk},
- {"rtrm", &dcss->rtrm_clk},
- {"dtrc", &dcss->dtrc_clk},
+ {"apb", &dcss->apb_clk, true},
+ {"axi", &dcss->axi_clk, true},
+ {"pix", &dcss->pix_clk, true},
+ {"rtrm", &dcss->rtrm_clk, true},
+ {"dtrc", &dcss->dtrc_clk, true},
+ {"pll_src", &dcss->pll_src_clk, dcss->hdmi_output},
+ {"pll_phy_ref", &dcss->pll_phy_ref_clk, dcss->hdmi_output},
};
for (i = 0; i < ARRAY_SIZE(clks); i++) {
*clks[i].clk = devm_clk_get(dcss->dev, clks[i].id);
- if (IS_ERR(*clks[i].clk)) {
+ if (IS_ERR(*clks[i].clk) && clks[i].required) {
dev_err(dcss->dev, "failed to get %s clock\n",
clks[i].id);
return PTR_ERR(*clks[i].clk);
@@ -257,7 +311,11 @@ int dcss_dev_suspend(struct device *dev)
struct dcss_kms_dev *kms = container_of(ddev, struct dcss_kms_dev, base);
int ret;
- drm_bridge_connector_disable_hpd(kms->connector);
+ if (!dcss)
+ return 0;
+
+ if (!dcss_drv_is_componentized(dev))
+ drm_bridge_connector_disable_hpd(kms->connector);
drm_mode_config_helper_suspend(ddev);
@@ -270,6 +328,8 @@ int dcss_dev_suspend(struct device *dev)
dcss_clocks_disable(dcss);
+ release_bus_freq(BUS_FREQ_HIGH);
+
return 0;
}
@@ -279,11 +339,16 @@ int dcss_dev_resume(struct device *dev)
struct drm_device *ddev = dcss_drv_dev_to_drm(dev);
struct dcss_kms_dev *kms = container_of(ddev, struct dcss_kms_dev, base);
+ if (!dcss)
+ return 0;
+
if (pm_runtime_suspended(dev)) {
drm_mode_config_helper_resume(ddev);
return 0;
}
+ request_bus_freq(BUS_FREQ_HIGH);
+
dcss_clocks_enable(dcss);
dcss_blkctl_cfg(dcss->blkctl);
@@ -292,7 +357,8 @@ int dcss_dev_resume(struct device *dev)
drm_mode_config_helper_resume(ddev);
- drm_bridge_connector_enable_hpd(kms->connector);
+ if (!dcss_drv_is_componentized(dev))
+ drm_bridge_connector_enable_hpd(kms->connector);
return 0;
}
@@ -304,12 +370,17 @@ int dcss_dev_runtime_suspend(struct device *dev)
struct dcss_dev *dcss = dcss_drv_dev_to_dcss(dev);
int ret;
+ if (!dcss)
+ return 0;
+
ret = dcss_ctxld_suspend(dcss->ctxld);
if (ret)
return ret;
dcss_clocks_disable(dcss);
+ release_bus_freq(BUS_FREQ_HIGH);
+
return 0;
}
@@ -317,6 +388,11 @@ int dcss_dev_runtime_resume(struct device *dev)
{
struct dcss_dev *dcss = dcss_drv_dev_to_dcss(dev);
+ if (!dcss)
+ return 0;
+
+ request_bus_freq(BUS_FREQ_HIGH);
+
dcss_clocks_enable(dcss);
dcss_blkctl_cfg(dcss->blkctl);
diff --git a/drivers/gpu/drm/imx/dcss/dcss-dev.h b/drivers/gpu/drm/imx/dcss/dcss-dev.h
index 1e582270c6ea..ad66ea1d16fa 100644
--- a/drivers/gpu/drm/imx/dcss/dcss-dev.h
+++ b/drivers/gpu/drm/imx/dcss/dcss-dev.h
@@ -6,6 +6,7 @@
#ifndef __DCSS_PRV_H__
#define __DCSS_PRV_H__
+#include <drm/drm_atomic.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_plane.h>
#include <linux/io.h>
@@ -58,6 +59,13 @@ enum dcss_ctxld_ctx_type {
CTX_SB_LP, /* low-priority */
};
+enum dcss_pixel_pipe_output {
+ DCSS_PIPE_OUTPUT_RGB = 0,
+ DCSS_PIPE_OUTPUT_YUV444,
+ DCSS_PIPE_OUTPUT_YUV422,
+ DCSS_PIPE_OUTPUT_YUV420,
+};
+
struct dcss_dev {
struct device *dev;
const struct dcss_type_data *devtype;
@@ -93,6 +101,7 @@ struct dcss_dev {
struct dcss_dev *dcss_drv_dev_to_dcss(struct device *dev);
struct drm_device *dcss_drv_dev_to_drm(struct device *dev);
+bool dcss_drv_is_componentized(struct device *dev);
struct dcss_dev *dcss_dev_create(struct device *dev, bool hdmi_output);
void dcss_dev_destroy(struct dcss_dev *dcss);
int dcss_dev_runtime_suspend(struct device *dev);
@@ -122,6 +131,9 @@ int dcss_ctxld_enable(struct dcss_ctxld *ctxld);
void dcss_ctxld_register_completion(struct dcss_ctxld *ctxld,
struct completion *dis_completion);
void dcss_ctxld_assert_locked(struct dcss_ctxld *ctxld);
+void dcss_ctxld_register_dtrc_cb(struct dcss_ctxld *ctxld,
+ bool (*cb)(void *),
+ void *data);
/* DPR */
int dcss_dpr_init(struct dcss_dev *dcss, unsigned long dpr_base);
@@ -142,7 +154,8 @@ bool dcss_dtg_vblank_irq_valid(struct dcss_dtg *dtg);
void dcss_dtg_vblank_irq_enable(struct dcss_dtg *dtg, bool en);
void dcss_dtg_vblank_irq_clear(struct dcss_dtg *dtg);
void dcss_dtg_sync_set(struct dcss_dtg *dtg, struct videomode *vm);
-void dcss_dtg_css_set(struct dcss_dtg *dtg);
+void dcss_dtg_css_set(struct dcss_dtg *dtg,
+ enum dcss_pixel_pipe_output output_encoding);
void dcss_dtg_enable(struct dcss_dtg *dtg);
void dcss_dtg_shutoff(struct dcss_dtg *dtg);
bool dcss_dtg_is_enabled(struct dcss_dtg *dtg);
@@ -159,7 +172,8 @@ int dcss_ss_init(struct dcss_dev *dcss, unsigned long subsam_base);
void dcss_ss_exit(struct dcss_ss *ss);
void dcss_ss_enable(struct dcss_ss *ss);
void dcss_ss_shutoff(struct dcss_ss *ss);
-void dcss_ss_subsam_set(struct dcss_ss *ss);
+void dcss_ss_subsam_set(struct dcss_ss *ss,
+ enum dcss_pixel_pipe_output output_encoding);
void dcss_ss_sync_set(struct dcss_ss *ss, struct videomode *vm,
bool phsync, bool pvsync);
@@ -177,4 +191,166 @@ int dcss_scaler_get_min_max_ratios(struct dcss_scaler *scl, int ch_num,
int *min, int *max);
void dcss_scaler_write_sclctrl(struct dcss_scaler *scl);
+/* DEC400D */
+
+#define VIV_VIDMEM_METADATA_MAGIC fourcc_code('v', 'i', 'v', 'm')
+
+/* Compressed format now was defined same as dec400d, should be general. */
+typedef enum _VIV_COMPRESS_FMT
+{
+ _VIV_CFMT_ARGB8 = 0,
+ _VIV_CFMT_XRGB8,
+ _VIV_CFMT_AYUV,
+ _VIV_CFMT_UYVY,
+ _VIV_CFMT_YUY2,
+ _VIV_CFMT_YUV_ONLY,
+ _VIV_CFMT_UV_MIX,
+ _VIV_CFMT_ARGB4,
+ _VIV_CFMT_XRGB4,
+ _VIV_CFMT_A1R5G5B5,
+ _VIV_CFMT_X1R5G5B5,
+ _VIV_CFMT_R5G6B5,
+ _VIV_CFMT_Z24S8,
+ _VIV_CFMT_Z24,
+ _VIV_CFMT_Z16,
+ _VIV_CFMT_A2R10G10B10,
+ _VIV_CFMT_BAYER,
+ _VIV_CFMT_SIGNED_BAYER,
+ _VIV_CFMT_VAA16,
+ _VIV_CFMT_S8,
+
+ _VIV_CFMT_MAX,
+} _VIV_COMPRESS_FMT;
+
+/* Metadata for cross-device fd share with additional (ts) info. */
+typedef struct _VIV_VIDMEM_METADATA
+{
+ uint32_t magic;
+
+ int32_t ts_fd;
+ void * ts_dma_buf;
+
+ uint32_t fc_enabled;
+ uint32_t fc_value;
+ uint32_t fc_value_upper;
+
+ uint32_t compressed;
+ uint32_t compress_format;
+} _VIV_VIDMEM_METADATA;
+
+int dcss_dec400d_init(struct dcss_dev *dcss, unsigned long dec400d_base);
+void dcss_dec400d_exit(struct dcss_dec400d *dec400d);
+void dcss_dec400d_bypass(struct dcss_dec400d *dec400d);
+void dcss_dec400d_shadow_trig(struct dcss_dec400d *dec400d);
+void dcss_dec400d_enable(struct dcss_dec400d *dec400d);
+void dcss_dec400d_fast_clear_config(struct dcss_dec400d *dec400d,
+ u32 fc_value,
+ bool enable);
+void dcss_dec400d_read_config(struct dcss_dec400d *dec400d,
+ u32 read_id,
+ bool compress_en,
+ u32 compress_format);
+void dcss_dec400d_addr_set(struct dcss_dec400d *dec400d, u32 baddr, u32 caddr);
+
+/* HDR10 */
+enum dcss_hdr10_nonlinearity {
+ NL_REC2084,
+ NL_REC709,
+ NL_BT1886,
+ NL_2100HLG,
+ NL_SRGB,
+};
+
+enum dcss_hdr10_pixel_range {
+ PR_LIMITED,
+ PR_FULL,
+};
+
+enum dcss_hdr10_gamut {
+ G_REC2020,
+ G_REC709,
+ G_REC601_NTSC,
+ G_REC601_PAL,
+ G_ADOBE_ARGB,
+};
+
+struct dcss_hdr10_pipe_cfg {
+ bool is_yuv;
+ enum dcss_hdr10_nonlinearity nl;
+ enum dcss_hdr10_pixel_range pr;
+ enum dcss_hdr10_gamut g;
+};
+
+int dcss_hdr10_init(struct dcss_dev *dcss, unsigned long hdr10_base);
+void dcss_hdr10_exit(struct dcss_hdr10 *hdr10);
+bool dcss_hdr10_pipe_cfg_is_supported(struct dcss_hdr10 *hdr10,
+ struct dcss_hdr10_pipe_cfg *ipipe_cfg,
+ struct dcss_hdr10_pipe_cfg *opipe_cfg);
+void dcss_hdr10_setup(struct dcss_hdr10 *hdr10, int ch_num,
+ struct dcss_hdr10_pipe_cfg *ipipe_cfg,
+ struct dcss_hdr10_pipe_cfg *opipe_cfg);
+
+/* enums common to both WRSCL and RDSRC */
+enum dcss_wrscl_rdsrc_psize {
+ PSIZE_64,
+ PSIZE_128,
+ PSIZE_256,
+ PSIZE_512,
+ PSIZE_1024,
+ PSIZE_2048,
+ PSIZE_4096,
+};
+
+enum dcss_wrscl_rdsrc_tsize {
+ TSIZE_64,
+ TSIZE_128,
+ TSIZE_256,
+ TSIZE_512,
+};
+
+enum dcss_wrscl_rdsrc_fifo_size {
+ FIFO_512,
+ FIFO_1024,
+ FIFO_2048,
+ FIFO_4096,
+};
+
+enum dcss_wrscl_rdsrc_bpp {
+ BPP_38, /* 38 bit unpacked components */
+ BPP_32_UPCONVERT,
+ BPP_32_10BIT_OUTPUT,
+ BPP_20, /* 10-bit YUV422 */
+ BPP_16, /* 8-bit YUV422 */
+};
+
+/* WRSCL */
+int dcss_wrscl_init(struct dcss_dev *dcss, unsigned long wrscl_base);
+void dcss_wrscl_exit(struct dcss_wrscl *wrscl);
+u32 dcss_wrscl_setup(struct dcss_wrscl *wrscl, u32 pix_format, u32 pix_clk_hz,
+ u32 dst_xres, u32 dst_yres);
+void dcss_wrscl_enable(struct dcss_wrscl *wrscl);
+void dcss_wrscl_disable(struct dcss_wrscl *wrscl);
+
+/* RDSRC */
+int dcss_rdsrc_init(struct dcss_dev *dcss, unsigned long rdsrc_base);
+void dcss_rdsrc_exit(struct dcss_rdsrc *rdsrc);
+void dcss_rdsrc_setup(struct dcss_rdsrc *rdsrc, u32 pix_format, u32 dst_xres,
+ u32 dst_yres, u32 base_addr);
+void dcss_rdsrc_enable(struct dcss_rdsrc *rdsrc);
+void dcss_rdsrc_disable(struct dcss_rdsrc *rdsrc);
+
+/* DTRC */
+int dcss_dtrc_init(struct dcss_dev *dcss, unsigned long dtrc_base);
+void dcss_dtrc_exit(struct dcss_dtrc *dtrc);
+void dcss_dtrc_bypass(struct dcss_dtrc *dtrc, int ch_num);
+void dcss_dtrc_set_format_mod(struct dcss_dtrc *dtrc, int ch_num, u64 modifier);
+void dcss_dtrc_addr_set(struct dcss_dtrc *dtrc, int ch_num,
+ u32 p1_ba, u32 p2_ba, uint64_t dec_table_ofs);
+bool dcss_dtrc_ch_running(struct dcss_dtrc *dtrc, int ch_num);
+bool dcss_dtrc_is_running(struct dcss_dtrc *dtrc);
+void dcss_dtrc_enable(struct dcss_dtrc *dtrc, int ch_num, bool enable);
+void dcss_dtrc_set_res(struct dcss_dtrc *dtrc, int ch_num,
+ struct drm_plane_state *state, u32 *dtrc_w, u32 *dtrc_h);
+void dcss_dtrc_switch_banks(struct dcss_dtrc *dtrc);
+
#endif /* __DCSS_PRV_H__ */
diff --git a/drivers/gpu/drm/imx/dcss/dcss-dpr.c b/drivers/gpu/drm/imx/dcss/dcss-dpr.c
index df9dab949bf2..e7dc98f73d1a 100644
--- a/drivers/gpu/drm/imx/dcss/dcss-dpr.c
+++ b/drivers/gpu/drm/imx/dcss/dcss-dpr.c
@@ -106,12 +106,17 @@ struct dcss_dpr_ch {
bool sys_ctrl_chgd;
+ u32 pitch;
+
int ch_num;
int irq;
+
+ bool use_dtrc;
};
struct dcss_dpr {
struct device *dev;
+ struct dcss_dtrc *dtrc;
struct dcss_ctxld *ctxld;
u32 ctx_id;
@@ -163,6 +168,7 @@ int dcss_dpr_init(struct dcss_dev *dcss, unsigned long dpr_base)
dpr->dev = dcss->dev;
dpr->ctxld = dcss->ctxld;
dpr->ctx_id = CTX_SB_HP;
+ dpr->dtrc = dcss->dtrc;
if (dcss_dpr_ch_init_all(dpr, dpr_base)) {
int i;
@@ -211,6 +217,9 @@ static u32 dcss_dpr_x_pix_wide_adjust(struct dcss_dpr_ch *ch, u32 pix_wide,
pix_in_64byte = pix_in_64byte_map[ch->pix_size][ch->tile];
+ if (pix_format == DRM_FORMAT_NV15)
+ pix_wide = pix_wide * 10 / 8;
+
div_64byte_mod = pix_wide % pix_in_64byte;
offset = (div_64byte_mod == 0) ? 0 : (pix_in_64byte - div_64byte_mod);
@@ -238,7 +247,8 @@ void dcss_dpr_set_res(struct dcss_dpr *dpr, int ch_num, u32 xres, u32 yres)
u32 pix_x_wide, pix_y_high;
if (pix_format == DRM_FORMAT_NV12 ||
- pix_format == DRM_FORMAT_NV21)
+ pix_format == DRM_FORMAT_NV21 ||
+ pix_format == DRM_FORMAT_NV15)
max_planes = 2;
for (plane = 0; plane < max_planes; plane++) {
@@ -247,12 +257,16 @@ void dcss_dpr_set_res(struct dcss_dpr *dpr, int ch_num, u32 xres, u32 yres)
pix_x_wide = dcss_dpr_x_pix_wide_adjust(ch, xres, pix_format);
pix_y_high = dcss_dpr_y_pix_high_adjust(ch, yres, pix_format);
+ if (plane == 0)
+ ch->pitch = pix_x_wide;
+
dcss_dpr_write(ch, pix_x_wide,
DCSS_DPR_FRAME_1P_PIX_X_CTRL + plane * gap);
dcss_dpr_write(ch, pix_y_high,
DCSS_DPR_FRAME_1P_PIX_Y_CTRL + plane * gap);
- dcss_dpr_write(ch, 2, DCSS_DPR_FRAME_1P_CTRL0 + plane * gap);
+ dcss_dpr_write(ch, ch->use_dtrc ? 7 : 2,
+ DCSS_DPR_FRAME_1P_CTRL0 + plane * gap);
}
}
@@ -261,9 +275,19 @@ void dcss_dpr_addr_set(struct dcss_dpr *dpr, int ch_num, u32 luma_base_addr,
{
struct dcss_dpr_ch *ch = &dpr->ch[ch_num];
- dcss_dpr_write(ch, luma_base_addr, DCSS_DPR_FRAME_1P_BASE_ADDR);
+ if (ch->use_dtrc) {
+ luma_base_addr = 0x0;
+ chroma_base_addr = 0x10000000;
+ }
- dcss_dpr_write(ch, chroma_base_addr, DCSS_DPR_FRAME_2P_BASE_ADDR);
+ if (!dcss_dtrc_ch_running(dpr->dtrc, ch_num)) {
+ dcss_dpr_write(ch, luma_base_addr, DCSS_DPR_FRAME_1P_BASE_ADDR);
+ dcss_dpr_write(ch, chroma_base_addr,
+ DCSS_DPR_FRAME_2P_BASE_ADDR);
+ }
+
+ if (ch->use_dtrc)
+ pitch = ch->pitch;
ch->frame_ctrl &= ~PITCH_MASK;
ch->frame_ctrl |= (((u32)pitch << PITCH_POS) & PITCH_MASK);
@@ -292,6 +316,7 @@ static void dcss_dpr_pix_size_set(struct dcss_dpr_ch *ch,
switch (format->format) {
case DRM_FORMAT_NV12:
case DRM_FORMAT_NV21:
+ case DRM_FORMAT_NV15:
val = PIX_SIZE_8;
break;
@@ -401,6 +426,7 @@ static void dcss_dpr_rtram_set(struct dcss_dpr_ch *ch, u32 pix_format)
switch (pix_format) {
case DRM_FORMAT_NV21:
case DRM_FORMAT_NV12:
+ case DRM_FORMAT_NV15:
ch->rtram_3buf_en = true;
ch->rtram_4line_en = false;
break;
@@ -474,6 +500,8 @@ static void dcss_dpr_setup_components(struct dcss_dpr_ch *ch,
static void dcss_dpr_tile_set(struct dcss_dpr_ch *ch, uint64_t modifier)
{
+ struct device *dev = ch->dpr->dev;
+
switch (ch->ch_num) {
case 0:
switch (modifier) {
@@ -484,16 +512,35 @@ static void dcss_dpr_tile_set(struct dcss_dpr_ch *ch, uint64_t modifier)
ch->tile = TILE_GPU_STANDARD;
break;
case DRM_FORMAT_MOD_VIVANTE_SUPER_TILED:
+ case DRM_FORMAT_MOD_VIVANTE_SUPER_TILED_FC:
ch->tile = TILE_GPU_SUPER;
break;
default:
- WARN_ON(1);
+ dev_err(dev, "dpr: unsupported modifier(0x%016llx) for graphics path.\n",
+ modifier);
break;
}
break;
case 1:
case 2:
- ch->tile = TILE_LINEAR;
+ switch (modifier) {
+ case DRM_FORMAT_MOD_LINEAR:
+ case DRM_FORMAT_MOD_VSI_G1_TILED:
+ case DRM_FORMAT_MOD_VSI_G2_TILED:
+ case DRM_FORMAT_MOD_VSI_G2_TILED_COMPRESSED:
+ ch->tile = TILE_LINEAR;
+ break;
+ case DRM_FORMAT_MOD_VIVANTE_TILED:
+ ch->tile = TILE_GPU_STANDARD;
+ break;
+ case DRM_FORMAT_MOD_VIVANTE_SUPER_TILED:
+ ch->tile = TILE_GPU_SUPER;
+ break;
+ default:
+ dev_err(dev, "dpr: unsupported modifier(0x%016llx) for video path.\n",
+ modifier);
+ break;
+ }
break;
default:
WARN_ON(1);
@@ -510,6 +557,10 @@ void dcss_dpr_format_set(struct dcss_dpr *dpr, int ch_num,
struct dcss_dpr_ch *ch = &dpr->ch[ch_num];
ch->format = *format;
+ ch->use_dtrc = ch_num &&
+ (modifier == DRM_FORMAT_MOD_VSI_G1_TILED ||
+ modifier == DRM_FORMAT_MOD_VSI_G2_TILED ||
+ modifier == DRM_FORMAT_MOD_VSI_G2_TILED_COMPRESSED);
dcss_dpr_yuv_en(ch, format->is_yuv);
diff --git a/drivers/gpu/drm/imx/dcss/dcss-drv.c b/drivers/gpu/drm/imx/dcss/dcss-drv.c
index 8dc2f85c514b..dab15913d8c7 100644
--- a/drivers/gpu/drm/imx/dcss/dcss-drv.c
+++ b/drivers/gpu/drm/imx/dcss/dcss-drv.c
@@ -6,6 +6,7 @@
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
+#include <linux/component.h>
#include <drm/drm_of.h>
#include "dcss-dev.h"
@@ -14,6 +15,8 @@
struct dcss_drv {
struct dcss_dev *dcss;
struct dcss_kms_dev *kms;
+
+ bool is_componentized;
};
struct dcss_dev *dcss_drv_dev_to_dcss(struct device *dev)
@@ -30,30 +33,25 @@ struct drm_device *dcss_drv_dev_to_drm(struct device *dev)
return mdrv ? &mdrv->kms->base : NULL;
}
-static int dcss_drv_platform_probe(struct platform_device *pdev)
+bool dcss_drv_is_componentized(struct device *dev)
{
- struct device *dev = &pdev->dev;
- struct device_node *remote;
- struct dcss_drv *mdrv;
- int err = 0;
- bool hdmi_output = true;
-
- if (!dev->of_node)
- return -ENODEV;
-
- remote = of_graph_get_remote_node(dev->of_node, 0, 0);
- if (!remote)
- return -ENODEV;
+ struct dcss_drv *mdrv = dev_get_drvdata(dev);
- hdmi_output = !of_device_is_compatible(remote, "fsl,imx8mq-nwl-dsi");
+ return mdrv->is_componentized;
+}
- of_node_put(remote);
+static int dcss_drv_init(struct device *dev, bool componentized)
+{
+ struct dcss_drv *mdrv;
+ int err = 0;
mdrv = kzalloc(sizeof(*mdrv), GFP_KERNEL);
if (!mdrv)
return -ENOMEM;
- mdrv->dcss = dcss_dev_create(dev, hdmi_output);
+ mdrv->is_componentized = componentized;
+
+ mdrv->dcss = dcss_dev_create(dev, componentized);
if (IS_ERR(mdrv->dcss)) {
err = PTR_ERR(mdrv->dcss);
goto err;
@@ -61,7 +59,7 @@ static int dcss_drv_platform_probe(struct platform_device *pdev)
dev_set_drvdata(dev, mdrv);
- mdrv->kms = dcss_kms_attach(mdrv->dcss);
+ mdrv->kms = dcss_kms_attach(mdrv->dcss, componentized);
if (IS_ERR(mdrv->kms)) {
err = PTR_ERR(mdrv->kms);
goto dcss_shutoff;
@@ -79,19 +77,73 @@ err:
return err;
}
-static int dcss_drv_platform_remove(struct platform_device *pdev)
+static void dcss_drv_deinit(struct device *dev, bool componentized)
{
- struct dcss_drv *mdrv = dev_get_drvdata(&pdev->dev);
+ struct dcss_drv *mdrv = dev_get_drvdata(dev);
if (!mdrv)
- return 0;
+ return;
- dcss_kms_detach(mdrv->kms);
+ dcss_kms_detach(mdrv->kms, componentized);
dcss_dev_destroy(mdrv->dcss);
- dev_set_drvdata(&pdev->dev, NULL);
+ dev_set_drvdata(dev, NULL);
kfree(mdrv);
+}
+
+static int dcss_drv_bind(struct device *dev)
+{
+ return dcss_drv_init(dev, true);
+}
+
+static void dcss_drv_unbind(struct device *dev)
+{
+ return dcss_drv_deinit(dev, true);
+}
+
+static const struct component_master_ops dcss_master_ops = {
+ .bind = dcss_drv_bind,
+ .unbind = dcss_drv_unbind,
+};
+
+static int compare_of(struct device *dev, void *data)
+{
+ return dev->of_node == data;
+}
+
+static int dcss_drv_platform_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct component_match *match = NULL;
+ struct device_node *remote;
+
+ if (!dev->of_node)
+ return -ENODEV;
+
+ remote = of_graph_get_remote_node(dev->of_node, 0, 0);
+ if (!remote)
+ return -ENODEV;
+
+ if (of_device_is_compatible(remote, "fsl,imx8mq-nwl-dsi")) {
+ of_node_put(remote);
+ return dcss_drv_init(dev, false);
+ }
+
+ drm_of_component_match_add(dev, &match, compare_of, remote);
+ of_node_put(remote);
+
+ return component_master_add_with_match(dev, &dcss_master_ops, match);
+}
+
+static int dcss_drv_platform_remove(struct platform_device *pdev)
+{
+ struct dcss_drv *mdrv = dev_get_drvdata(&pdev->dev);
+
+ if (mdrv->is_componentized)
+ component_master_del(&pdev->dev, &dcss_master_ops);
+ else
+ dcss_drv_deinit(&pdev->dev, false);
return 0;
}
@@ -102,9 +154,14 @@ static struct dcss_type_data dcss_types[] = {
.blkctl_ofs = 0x2F000,
.ctxld_ofs = 0x23000,
.dtg_ofs = 0x20000,
+ .rdsrc_ofs = 0x22000,
+ .wrscl_ofs = 0x21000,
.scaler_ofs = 0x1C000,
.ss_ofs = 0x1B000,
.dpr_ofs = 0x18000,
+ .dec400d_ofs = 0x15000,
+ .hdr10_ofs = 0x00000,
+ .dtrc_ofs = 0x16000,
},
};
diff --git a/drivers/gpu/drm/imx/dcss/dcss-dtg.c b/drivers/gpu/drm/imx/dcss/dcss-dtg.c
index 30de00540f63..b5994d1929cd 100644
--- a/drivers/gpu/drm/imx/dcss/dcss-dtg.c
+++ b/drivers/gpu/drm/imx/dcss/dcss-dtg.c
@@ -83,6 +83,7 @@ struct dcss_dtg {
u32 ctx_id;
bool in_use;
+ bool hdmi_output;
u32 dis_ulc_x;
u32 dis_ulc_y;
@@ -159,6 +160,7 @@ int dcss_dtg_init(struct dcss_dev *dcss, unsigned long dtg_base)
dcss->dtg = dtg;
dtg->dev = dcss->dev;
dtg->ctxld = dcss->ctxld;
+ dtg->hdmi_output = dcss->hdmi_output;
dtg->base_reg = ioremap(dtg_base, SZ_4K);
if (!dtg->base_reg) {
@@ -221,6 +223,15 @@ void dcss_dtg_sync_set(struct dcss_dtg *dtg, struct videomode *vm)
vm->vactive - 1;
clk_disable_unprepare(dcss->pix_clk);
+ if (dcss->hdmi_output) {
+ int err;
+
+ clk_disable_unprepare(dcss->pll_src_clk);
+ err = clk_set_parent(dcss->pll_src_clk, dcss->pll_phy_ref_clk);
+ if (err < 0)
+ dev_warn(dcss->dev, "clk_set_parent() returned %d", err);
+ clk_prepare_enable(dcss->pll_src_clk);
+ }
clk_set_rate(dcss->pix_clk, vm->pixelclock);
clk_prepare_enable(dcss->pix_clk);
@@ -304,8 +315,14 @@ void dcss_dtg_plane_alpha_set(struct dcss_dtg *dtg, int ch_num,
dtg->alpha = alpha;
}
-void dcss_dtg_css_set(struct dcss_dtg *dtg)
+void dcss_dtg_css_set(struct dcss_dtg *dtg,
+ enum dcss_pixel_pipe_output output_encoding)
{
+ dtg->control_status &= ~CSS_PIX_COMP_SWAP_MASK;
+
+ if (output_encoding != DCSS_PIPE_OUTPUT_RGB)
+ return;
+
dtg->control_status |=
(0x5 << CSS_PIX_COMP_SWAP_POS) & CSS_PIX_COMP_SWAP_MASK;
}
diff --git a/drivers/gpu/drm/imx/dcss/dcss-dtrc.c b/drivers/gpu/drm/imx/dcss/dcss-dtrc.c
new file mode 100644
index 000000000000..47cc73f27c2a
--- /dev/null
+++ b/drivers/gpu/drm/imx/dcss/dcss-dtrc.c
@@ -0,0 +1,517 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2019, 2022 NXP.
+ */
+
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_rect.h>
+
+#include "dcss-dev.h"
+
+#define DTRC_F0_OFS 0x00
+#define DTRC_F1_OFS 0x60
+
+#define DCSS_DTRC_DYDSADDR 0x00
+#define DCSS_DTRC_DCDSADDR 0x04
+#define DCSS_DTRC_DYTSADDR 0x08
+#define DCSS_DTRC_DCTSADDR 0x0C
+#define DCSS_DTRC_SIZE 0x10
+#define FRAME_WIDTH_POS 0
+#define FRAME_WIDTH_MASK GENMASK(9, 0)
+#define FRAME_HEIGHT_POS 16
+#define FRAME_HEIGHT_MASK GENMASK(25, 16)
+#define DCSS_DTRC_SYSSA 0x14
+#define DCSS_DTRC_SYSEA 0x18
+#define DCSS_DTRC_SUVSSA 0x1C
+#define DCSS_DTRC_SUVSEA 0x20
+#define DCSS_DTRC_CROPORIG 0x24
+#define DCSS_DTRC_CROPSIZE 0x28
+#define CROP_HEIGHT_POS 16
+#define CROP_HEIGHT_MASK GENMASK(28, 16)
+#define CROP_WIDTH_POS 0
+#define CROP_WIDTH_MASK GENMASK(12, 0)
+#define DCSS_DTRC_DCTL 0x2C
+#define CROPPING_EN BIT(18)
+#define COMPRESSION_DIS BIT(17)
+#define PIX_DEPTH_8BIT_EN BIT(1)
+#define CONFIG_READY BIT(0)
+#define DCSS_DTRC_DYDSADDR_EXT 0x30
+#define DCSS_DTRC_DCDSADDR_EXT 0x34
+#define DCSS_DTRC_DYTSADDR_EXT 0x38
+#define DCSS_DTRC_DCTSADDR_EXT 0x3C
+#define DCSS_DTRC_SYSSA_EXT 0x40
+#define DCSS_DTRC_SYSEA_EXT 0x44
+#define DCSS_DTRC_SUVSSA_EXT 0x48
+#define DCSS_DTRC_SUVSEA_EXT 0x4C
+
+#define DCSS_DTRC_INTEN 0xC0
+#define DCSS_DTRC_FDINTR 0xC4
+#define DCSS_DTRC_DTCTRL 0xC8
+#define CURRENT_FRAME BIT(31)
+#define ADDRESS_ID_ENABLE BIT(30)
+#define ENDIANNESS_10BIT BIT(29)
+#define MERGE_ARID_ENABLE BIT(28)
+#define NON_G1_2_SWAP_MODE_POS 24
+#define NON_G1_2_SWAP_MODE_MASK GENMASK(27, 24)
+#define TABLE_DATA_SWAP_POS 20
+#define TABLE_DATA_SWAP_MASK GENMASK(23, 20)
+#define TILED_SWAP_POS 16
+#define TILED_SWAP_MASK GENMASK(19, 16)
+#define RASTER_SWAP_POS 12
+#define RASTER_SWAP_MASK GENMASK(15, 12)
+#define BURST_LENGTH_POS 4
+#define BURST_LENGTH_MASK GENMASK(11, 4)
+#define G1_TILED_DATA_EN BIT(3)
+#define HOT_RESET BIT(2)
+#define ARIDR_MODE_DETILE 0
+#define ARIDR_MODE_BYPASS 2
+#define DCSS_DTRC_ARIDR 0xCC
+#define DCSS_DTRC_DTID2DDR 0xD0
+#define DCSS_DTRC_CONFIG 0xD4
+#define DCSS_DTRC_VER 0xD8
+#define DCSS_DTRC_PFCTRL 0xF0
+#define DCSS_DTRC_PFCR 0xF4
+#define DCSS_DTRC_TOCR 0xF8
+
+struct dcss_dtrc_ch {
+ struct dcss_dtrc *dtrc;
+
+ void __iomem *base_reg;
+ u32 base_ofs;
+
+ u32 xres;
+ u32 yres;
+ u32 pix_format;
+ u64 format_modifier;
+ u32 y_dec_ofs;
+ u32 uv_dec_ofs;
+
+ int curr_frame;
+
+ u32 dctl;
+
+ bool bypass;
+ bool running;
+
+ int irq;
+ int ch_num;
+};
+
+struct dcss_dtrc {
+ struct device *dev;
+
+ struct dcss_dtrc_ch ch[2];
+
+ u32 ctx_id;
+ struct dcss_ctxld *ctxld;
+};
+
+static irqreturn_t dcss_dtrc_irq_handler(int irq, void *data)
+{
+ struct dcss_dtrc_ch *ch = data;
+ u32 b0, b1, curr_bank;
+
+ b0 = dcss_readl(ch->base_reg + DCSS_DTRC_DCTL) & 0x1;
+ b1 = dcss_readl(ch->base_reg + DTRC_F1_OFS + DCSS_DTRC_DCTL) & 0x1;
+ curr_bank = dcss_readl(ch->base_reg + DCSS_DTRC_DTCTRL) >> 31;
+
+ dcss_update(1, 1, ch->base_reg + DCSS_DTRC_FDINTR);
+
+ return IRQ_HANDLED;
+}
+
+static int dcss_dtrc_irq_config(struct dcss_dtrc *dtrc, int ch_num)
+{
+ struct platform_device *pdev = to_platform_device(dtrc->dev);
+ struct dcss_dtrc_ch *ch = &dtrc->ch[ch_num];
+ char irq_name[20];
+ int ret;
+
+ sprintf(irq_name, "dtrc_ch%d", ch_num + 1);
+ irq_name[8] = 0;
+
+ ch->irq = platform_get_irq_byname(pdev, irq_name);
+ if (ch->irq < 0) {
+ dev_err(dtrc->dev, "dtrc: can't get DTRC irq\n");
+ return ch->irq;
+ }
+
+ ret = request_irq(ch->irq, dcss_dtrc_irq_handler,
+ 0, "dcss-dtrc", ch);
+ if (ret) {
+ ch->irq = 0;
+ dev_err(dtrc->dev, "dtrc: irq request failed.\n");
+ return ret;
+ }
+
+ dcss_writel(1, ch->base_reg + DCSS_DTRC_INTEN);
+
+ return 0;
+}
+
+static int dcss_dtrc_ch_init_all(struct dcss_dtrc *dtrc, u32 dtrc_base)
+{
+ struct dcss_dtrc_ch *ch;
+ int i, ret;
+
+ for (i = 0; i < 2; i++) {
+ ch = &dtrc->ch[i];
+
+ ch->base_ofs = dtrc_base + i * 0x1000;
+
+ ch->base_reg = ioremap(ch->base_ofs, SZ_4K);
+ if (!ch->base_reg) {
+ dev_err(dtrc->dev, "dtrc: unable to remap ch base\n");
+ return -ENOMEM;
+ }
+
+ ch->ch_num = i;
+ ch->dtrc = dtrc;
+
+ ret = dcss_dtrc_irq_config(dtrc, i);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static void dcss_dtrc_write(struct dcss_dtrc_ch *ch, u32 val, u32 ofs)
+{
+ dcss_ctxld_write(ch->dtrc->ctxld, ch->dtrc->ctx_id,
+ val, ch->base_ofs + ofs);
+}
+
+static void dcss_dtrc_write_irqsafe(struct dcss_dtrc_ch *ch, u32 val, u32 ofs)
+{
+ dcss_ctxld_write_irqsafe(ch->dtrc->ctxld, ch->dtrc->ctx_id,
+ val, ch->base_ofs + ofs);
+}
+
+int dcss_dtrc_init(struct dcss_dev *dcss, unsigned long dtrc_base)
+{
+ struct dcss_dtrc *dtrc;
+
+ dtrc = kzalloc(sizeof(*dtrc), GFP_KERNEL);
+ if (!dtrc)
+ return -ENOMEM;
+
+ dcss->dtrc = dtrc;
+ dtrc->dev = dcss->dev;
+ dtrc->ctxld = dcss->ctxld;
+ dtrc->ctx_id = CTX_SB_HP;
+
+ if (dcss_dtrc_ch_init_all(dtrc, dtrc_base)) {
+ struct dcss_dtrc_ch *ch;
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ ch = &dtrc->ch[i];
+
+ if (ch->irq)
+ free_irq(ch->irq, ch);
+
+ if (ch->base_reg)
+ iounmap(ch->base_reg);
+ }
+
+ kfree(dtrc);
+
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+void dcss_dtrc_exit(struct dcss_dtrc *dtrc)
+{
+ int ch_no;
+
+ for (ch_no = 0; ch_no < 2; ch_no++) {
+ struct dcss_dtrc_ch *ch = &dtrc->ch[ch_no];
+
+ if (ch->base_reg) {
+ /* reset the module to default */
+ dcss_writel(HOT_RESET,
+ ch->base_reg + DCSS_DTRC_DTCTRL);
+ iounmap(ch->base_reg);
+ }
+
+ if (ch->irq)
+ free_irq(ch->irq, ch);
+ }
+
+ kfree(dtrc);
+}
+
+void dcss_dtrc_bypass(struct dcss_dtrc *dtrc, int ch_num)
+{
+ struct dcss_dtrc_ch *ch;
+
+ if (ch_num == 0)
+ return;
+
+ ch = &dtrc->ch[ch_num - 1];
+
+ if (ch->bypass)
+ return;
+
+ dcss_dtrc_write(ch, ARIDR_MODE_BYPASS, DCSS_DTRC_DTCTRL);
+ dcss_dtrc_write(ch, 0, DCSS_DTRC_DYTSADDR);
+ dcss_dtrc_write(ch, 0, DCSS_DTRC_DCTSADDR);
+ dcss_dtrc_write(ch, 0x0f0e0100, DCSS_DTRC_ARIDR);
+ dcss_dtrc_write(ch, 0x0f0e, DCSS_DTRC_DTID2DDR);
+
+ ch->running = false;
+ ch->bypass = true;
+}
+
+void dcss_dtrc_addr_set(struct dcss_dtrc *dtrc, int ch_num,
+ u32 p1_ba, u32 p2_ba, uint64_t dec_table_ofs)
+{
+ struct dcss_dtrc_ch *ch;
+
+ if (ch_num == 0)
+ return;
+
+ ch = &dtrc->ch[ch_num - 1];
+
+ dcss_dtrc_write(ch, p1_ba, DCSS_DTRC_DYDSADDR);
+ dcss_dtrc_write(ch, p2_ba, DCSS_DTRC_DCDSADDR);
+
+ dcss_dtrc_write(ch, p1_ba, DTRC_F1_OFS + DCSS_DTRC_DYDSADDR);
+ dcss_dtrc_write(ch, p2_ba, DTRC_F1_OFS + DCSS_DTRC_DCDSADDR);
+
+ if (ch->format_modifier == DRM_FORMAT_MOD_VSI_G2_TILED_COMPRESSED) {
+ ch->y_dec_ofs = dec_table_ofs & 0xFFFFFFFF;
+ ch->uv_dec_ofs = dec_table_ofs >> 32;
+
+ dcss_dtrc_write(ch, p1_ba + ch->y_dec_ofs,
+ DCSS_DTRC_DYTSADDR);
+ dcss_dtrc_write(ch, p1_ba + ch->uv_dec_ofs,
+ DCSS_DTRC_DCTSADDR);
+ dcss_dtrc_write(ch, p1_ba + ch->y_dec_ofs,
+ DTRC_F1_OFS + DCSS_DTRC_DYTSADDR);
+ dcss_dtrc_write(ch, p1_ba + ch->uv_dec_ofs,
+ DTRC_F1_OFS + DCSS_DTRC_DCTSADDR);
+ }
+
+ ch->bypass = false;
+}
+
+void dcss_dtrc_set_res(struct dcss_dtrc *dtrc, int ch_num,
+ struct drm_plane_state *state, u32 *dtrc_w, u32 *dtrc_h)
+{
+ struct drm_framebuffer *fb = state->fb;
+ u32 pixel_format = fb->format->format;
+ struct dcss_dtrc_ch *ch;
+ u32 frame_height, frame_width;
+ u32 crop_w, crop_h, crop_orig_w, crop_orig_h;
+ int bank;
+ u32 old_xres, old_yres, xres, yres;
+ u32 x1, y1, x2, y2;
+ u32 pix_depth;
+ u16 width_align = 0;
+
+ if (ch_num == 0)
+ return;
+
+ ch = &dtrc->ch[ch_num - 1];
+
+ bank = dcss_readl(ch->base_reg + DCSS_DTRC_DTCTRL) >> 31;
+
+ ch->pix_format = pixel_format;
+ ch->format_modifier = fb->modifier;
+
+ pix_depth = ch->pix_format == DRM_FORMAT_NV15 ? 10 : 8;
+
+ old_xres = state->src_w >> 16;
+ old_yres = state->src_h >> 16;
+
+ x1 = (state->src.x1 >> 16) & ~1;
+ y1 = (state->src.y1 >> 16) & ~1;
+ x2 = state->src.x2 >> 16;
+ y2 = state->src.y2 >> 16;
+
+ xres = x2 - x1;
+ yres = y2 - y1;
+
+ frame_height = ((old_yres >> 3) << FRAME_HEIGHT_POS) & FRAME_HEIGHT_MASK;
+ frame_width = ((old_xres >> 3) << FRAME_WIDTH_POS) & FRAME_WIDTH_MASK;
+
+ dcss_dtrc_write(ch, frame_height | frame_width,
+ DTRC_F1_OFS * bank + DCSS_DTRC_SIZE);
+
+ dcss_dtrc_write(ch, frame_height | frame_width,
+ DTRC_F1_OFS * (bank ^ 1) + DCSS_DTRC_SIZE);
+
+ /*
+ * Image original size is aligned:
+ * - 128 pixels for width (8-bit) or 256 (10-bit);
+ * - 8 lines for height;
+ */
+ width_align = ch->pix_format == DRM_FORMAT_NV15 ? 0xff : 0x7f;
+
+ if (xres == old_xres && !(xres & width_align) &&
+ yres == old_yres && !(yres & 0xf)) {
+ ch->dctl &= ~CROPPING_EN;
+ goto exit;
+ }
+
+ /* align the image size: down align for compressed formats */
+ if (ch->format_modifier == DRM_FORMAT_MOD_VSI_G2_TILED_COMPRESSED && x1)
+ xres = xres & ~width_align;
+ else
+ xres = (xres + width_align) & ~width_align;
+
+ if (ch->format_modifier == DRM_FORMAT_MOD_VSI_G2_TILED_COMPRESSED && y1)
+ yres = yres & ~0xf;
+ else
+ yres = (yres + 0xf) & ~0xf;
+
+ crop_orig_w = (x1 << CROP_WIDTH_POS) & CROP_WIDTH_MASK;
+ crop_orig_h = (y1 << CROP_HEIGHT_POS) & CROP_HEIGHT_MASK;
+
+ dcss_dtrc_write(ch, crop_orig_w | crop_orig_h,
+ DCSS_DTRC_CROPORIG);
+ dcss_dtrc_write(ch, crop_orig_w | crop_orig_h,
+ DTRC_F1_OFS + DCSS_DTRC_CROPORIG);
+
+ crop_w = (xres << CROP_WIDTH_POS) & CROP_WIDTH_MASK;
+ crop_h = (yres << CROP_HEIGHT_POS) & CROP_HEIGHT_MASK;
+
+ dcss_dtrc_write(ch, crop_w | crop_h,
+ DTRC_F1_OFS * bank + DCSS_DTRC_CROPSIZE);
+ dcss_dtrc_write(ch, crop_w | crop_h,
+ DTRC_F1_OFS * (bank ^ 1) + DCSS_DTRC_CROPSIZE);
+
+ ch->dctl |= CROPPING_EN;
+
+exit:
+ dcss_dtrc_write(ch, xres * yres * pix_depth / 8,
+ DCSS_DTRC_SYSEA);
+ dcss_dtrc_write(ch, xres * yres * pix_depth / 8,
+ DTRC_F1_OFS + DCSS_DTRC_SYSEA);
+
+ dcss_dtrc_write(ch, 0x10000000 + xres * yres * pix_depth / 8 / 2,
+ DCSS_DTRC_SUVSEA);
+ dcss_dtrc_write(ch, 0x10000000 + xres * yres * pix_depth / 8 / 2,
+ DTRC_F1_OFS + DCSS_DTRC_SUVSEA);
+
+ *dtrc_w = xres;
+ *dtrc_h = yres;
+
+ if (ch->running)
+ return;
+
+ dcss_dtrc_write(ch, 0x0, DCSS_DTRC_SYSSA);
+ dcss_dtrc_write(ch, 0x0, DTRC_F1_OFS + DCSS_DTRC_SYSSA);
+
+ dcss_dtrc_write(ch, 0x10000000, DCSS_DTRC_SUVSSA);
+ dcss_dtrc_write(ch, 0x10000000, DTRC_F1_OFS + DCSS_DTRC_SUVSSA);
+}
+
+void dcss_dtrc_enable(struct dcss_dtrc *dtrc, int ch_num, bool enable)
+{
+ struct dcss_dtrc_ch *ch;
+ int curr_frame;
+ u32 fdctl, dtctrl;
+
+ if (ch_num == 0)
+ return;
+
+ ch = &dtrc->ch[ch_num - 1];
+
+ if (ch->bypass)
+ return;
+
+ if (!enable) {
+ ch->running = false;
+ return;
+ }
+
+ if (ch->running)
+ return;
+
+ dcss_update(HOT_RESET, HOT_RESET, ch->base_reg + DCSS_DTRC_DTCTRL);
+ while (dcss_readl(ch->base_reg + DCSS_DTRC_DTCTRL) & HOT_RESET)
+ usleep_range(100, 200);
+
+ dcss_dtrc_write(ch, 0x0f0e0100,
+ DCSS_DTRC_ARIDR);
+ dcss_dtrc_write(ch, 0x0f0e,
+ DCSS_DTRC_DTID2DDR);
+
+ dtctrl = ADDRESS_ID_ENABLE | MERGE_ARID_ENABLE |
+ ((0xF << TABLE_DATA_SWAP_POS) & TABLE_DATA_SWAP_MASK) |
+ ((0x10 << BURST_LENGTH_POS) & BURST_LENGTH_MASK);
+
+ if (ch->format_modifier == DRM_FORMAT_MOD_VSI_G1_TILED)
+ dtctrl |= G1_TILED_DATA_EN;
+
+ dcss_dtrc_write(ch, dtctrl, DCSS_DTRC_DTCTRL);
+
+ curr_frame = dcss_readl(ch->base_reg + DCSS_DTRC_DTCTRL) >> 31;
+
+ fdctl = ch->dctl & ~(PIX_DEPTH_8BIT_EN | COMPRESSION_DIS);
+
+ fdctl |= ch->pix_format == DRM_FORMAT_NV15 ? 0 : PIX_DEPTH_8BIT_EN;
+
+ if (ch->format_modifier != DRM_FORMAT_MOD_VSI_G2_TILED_COMPRESSED)
+ fdctl |= COMPRESSION_DIS;
+
+ dcss_dtrc_write(ch, fdctl,
+ (curr_frame ^ 1) * DTRC_F1_OFS + DCSS_DTRC_DCTL);
+ dcss_dtrc_write(ch, fdctl | CONFIG_READY,
+ curr_frame * DTRC_F1_OFS + DCSS_DTRC_DCTL);
+
+ ch->curr_frame = curr_frame;
+ ch->dctl = fdctl;
+ ch->running = true;
+}
+
+bool dcss_dtrc_ch_running(struct dcss_dtrc *dtrc, int ch_num)
+{
+ struct dcss_dtrc_ch *ch;
+
+ if (ch_num == 0)
+ return false;
+
+ ch = &dtrc->ch[ch_num - 1];
+
+ return ch->running;
+}
+
+bool dcss_dtrc_is_running(struct dcss_dtrc *dtrc)
+{
+ return dtrc->ch[0].running || dtrc->ch[1].running;
+}
+
+static void dcss_dtrc_ch_switch_banks(struct dcss_dtrc *dtrc, int dtrc_ch)
+{
+ struct dcss_dtrc_ch *ch = &dtrc->ch[dtrc_ch];
+ u32 b0, b1;
+
+ if (!ch->running)
+ return;
+
+ b0 = dcss_readl(ch->base_reg + DCSS_DTRC_DCTL) & 0x1;
+ b1 = dcss_readl(ch->base_reg + DTRC_F1_OFS + DCSS_DTRC_DCTL) & 0x1;
+
+ ch->curr_frame = dcss_readl(ch->base_reg + DCSS_DTRC_DTCTRL) >> 31;
+
+ dcss_dtrc_write_irqsafe(ch, ch->dctl | CONFIG_READY,
+ (ch->curr_frame ^ 1) * DTRC_F1_OFS + DCSS_DTRC_DCTL);
+}
+
+void dcss_dtrc_switch_banks(struct dcss_dtrc *dtrc)
+{
+ dcss_dtrc_ch_switch_banks(dtrc, 0);
+ dcss_dtrc_ch_switch_banks(dtrc, 1);
+}
diff --git a/drivers/gpu/drm/imx/dcss/dcss-hdr10-tables.h b/drivers/gpu/drm/imx/dcss/dcss-hdr10-tables.h
new file mode 100644
index 000000000000..6baa886bb358
--- /dev/null
+++ b/drivers/gpu/drm/imx/dcss/dcss-hdr10-tables.h
@@ -0,0 +1,3018 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright 2018-2020 NXP.
+ */
+
+
+#ifndef __DCSS_HDR10_TABLES_H__
+#define __DCSS_HDR10_TABLES_H__
+
+static const u32 dcss_cscas[20][29] = {
+ {0x8000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0},
+ {0x3, 0x4000, 0x0, 0x5c45, 0x4000, 0xfffff5b6, 0xffffdc41, 0x4000,
+ 0x75b9, 0x0, 0xffffffc0, 0xfffffe00, 0xfffffe00, 0x0, 0xfffffe40,
+ 0xfffffe40, 0x36c, 0x1c0, 0x1c0, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x36c, 0x36c, 0x36c},
+ {0x3, 0x36ce, 0x0, 0x50d1, 0x36ce, 0xfffff6fd, 0xffffe0b1, 0x36ce,
+ 0x671c, 0x0, 0x0, 0xfffffe00, 0xfffffe00, 0x0, 0xfffffe00, 0xfffffe00,
+ 0x3ff, 0x1ff, 0x1ff, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x36c, 0x36c,
+ 0x36c},
+ {0x3, 0x4000, 0x0, 0x628a, 0x4000, 0xfffff449, 0xffffe2b7, 0x4000,
+ 0x741c, 0x0, 0xffffffc0, 0xfffffe00, 0xfffffe00, 0x0, 0xfffffe40,
+ 0xfffffe40, 0x36c, 0x1c0, 0x1c0, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x36c, 0x36c, 0x36c},
+ {0x3, 0x4000, 0x0, 0x57ba, 0x4000, 0xffffea79, 0xffffd352, 0x4000,
+ 0x6ee1, 0x0, 0xffffffc0, 0xfffffe00, 0xfffffe00, 0x0, 0xfffffe40,
+ 0xfffffe40, 0x36c, 0x1c0, 0x1c0, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x36c, 0x36c, 0x36c},
+ {0x3, 0x36ce, 0x0, 0x564e, 0x36ce, 0xfffff5bd, 0xffffe65a, 0x36ce,
+ 0x65b2, 0x0, 0x0, 0xfffffe00, 0xfffffe00, 0x0, 0xfffffe00, 0xfffffe00,
+ 0x3ff, 0x1ff, 0x1ff, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x36c, 0x36c,
+ 0x36c},
+ {0x3, 0x36ce, 0x0, 0x4cd6, 0x36ce, 0xffffed25, 0xffffd8de, 0x36ce,
+ 0x611d, 0x0, 0x0, 0xfffffe00, 0xfffffe00, 0x0, 0xfffffe00, 0xfffffe00,
+ 0x3ff, 0x1ff, 0x1ff, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x36c, 0x36c,
+ 0x36c},
+ {0x1, 0x4abe, 0x0, 0x0, 0x0, 0x4abe, 0x0, 0x0, 0x0, 0x4abe, 0xffffffc0,
+ 0xffffffc0, 0xffffffc0, 0x0, 0x0, 0x0, 0x36c, 0x36c, 0x36c, 0xe, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x3ff, 0x3ff, 0x3ff},
+ {0x3, 0x255f, 0x0, 0x35e1, 0x255f, 0xfffff9fe, 0xffffeb21, 0x255f,
+ 0x44bd, 0x0, 0xffffffc0, 0xfffffe00, 0xfffffe00, 0x0, 0xfffffe40,
+ 0xfffffe40, 0x36c, 0x1c0, 0x1c0, 0xd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x3ff, 0x3ff, 0x3ff},
+ {0x3, 0x4000, 0x0, 0x5e60, 0x4000, 0xfffff579, 0xffffdb70, 0x4000,
+ 0x7869, 0x0, 0x0, 0xfffffe00, 0xfffffe00, 0x0, 0xfffffe00, 0xfffffe00,
+ 0x3ff, 0x1ff, 0x1ff, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3ff, 0x3ff,
+ 0x3ff},
+ {0x3, 0x255f, 0x0, 0x398a, 0x255f, 0xfffff929, 0xffffeee7, 0x255f,
+ 0x43cc, 0x0, 0xffffffc0, 0xfffffe00, 0xfffffe00, 0x0, 0xfffffe40,
+ 0xfffffe40, 0x36c, 0x1c0, 0x1c0, 0xd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x3ff, 0x3ff, 0x3ff},
+ {0x3, 0x255f, 0x0, 0x333a, 0x255f, 0xfffff36f, 0xffffe5ea, 0x255f,
+ 0x40be, 0x0, 0xffffffc0, 0xfffffe00, 0xfffffe00, 0x0, 0xfffffe40,
+ 0xfffffe40, 0x36c, 0x1c0, 0x1c0, 0xd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x3ff, 0x3ff, 0x3ff},
+ {0x3, 0x4000, 0x0, 0x64ca, 0x4000, 0xfffff404, 0xffffe20c, 0x4000,
+ 0x76c3, 0x0, 0x0, 0xfffffe00, 0xfffffe00, 0x0, 0xfffffe00, 0xfffffe00,
+ 0x3ff, 0x1ff, 0x1ff, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3ff, 0x3ff,
+ 0x3ff},
+ {0x3, 0x4000, 0x0, 0x59bb, 0x4000, 0xffffe9fb, 0xffffd24d, 0x4000,
+ 0x7169, 0x0, 0x0, 0xfffffe00, 0xfffffe00, 0x0, 0xfffffe00, 0xfffffe00,
+ 0x3ff, 0x1ff, 0x1ff, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3ff, 0x3ff,
+ 0x3ff},
+ {0x3, 0x4176, 0x0, 0x5e60, 0x4176, 0xfffff579, 0xffffdb70, 0x4176,
+ 0x7869, 0x0, 0xffffffc0, 0xfffffe00, 0xfffffe00, 0x0, 0xfffffe40,
+ 0xfffffe40, 0x36c, 0x1c0, 0x1c0, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x380, 0x380, 0x380},
+ {0x3, 0x380e, 0x0, 0x52a9, 0x380e, 0xfffff6c8, 0xffffdffa, 0x380e,
+ 0x6977, 0x0, 0x0, 0xfffffe00, 0xfffffe00, 0x0, 0xfffffe00, 0xfffffe00,
+ 0x3ff, 0x1ff, 0x1ff, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x380, 0x380,
+ 0x380},
+ {0x3, 0x4176, 0x0, 0x64ca, 0x4176, 0xfffff404, 0xffffe20c, 0x4176,
+ 0x76c3, 0x0, 0xffffffc0, 0xfffffe00, 0xfffffe00, 0x0, 0xfffffe40,
+ 0xfffffe40, 0x36c, 0x1c0, 0x1c0, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x380, 0x380, 0x380},
+ {0x3, 0x4176, 0x0, 0x59bb, 0x4176, 0xffffe9fb, 0xffffd24d, 0x4176,
+ 0x7169, 0x0, 0xffffffc0, 0xfffffe00, 0xfffffe00, 0x0, 0xfffffe40,
+ 0xfffffe40, 0x36c, 0x1c0, 0x1c0, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x380, 0x380, 0x380},
+ {0x3, 0x380e, 0x0, 0x5847, 0x380e, 0xfffff581, 0xffffe5c4, 0x380e,
+ 0x6804, 0x0, 0x0, 0xfffffe00, 0xfffffe00, 0x0, 0xfffffe00, 0xfffffe00,
+ 0x3ff, 0x1ff, 0x1ff, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x380, 0x380,
+ 0x380},
+ {0x3, 0x380e, 0x0, 0x4e97, 0x380e, 0xffffecb7, 0xffffd7fa, 0x380e,
+ 0x6355, 0x0, 0x0, 0xfffffe00, 0xfffffe00, 0x0, 0xfffffe00, 0xfffffe00,
+ 0x3ff, 0x1ff, 0x1ff, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x380, 0x380,
+ 0x380},
+};
+
+static const u16 dcss_iluts[11][1026] = {
+ {0x3, 0x0, 0x200, 0x600, 0x840, 0x9c0, 0xac0, 0xbe0, 0xc90, 0xd40,
+ 0xe08, 0xe78, 0xef0, 0xf78, 0x1008, 0x1054, 0x10ac, 0x1108, 0x116c,
+ 0x11d4, 0x1222, 0x125c, 0x129c, 0x12de, 0x1324, 0x136e, 0x13ba, 0x1406,
+ 0x1430, 0x145d, 0x148c, 0x14bc, 0x14ef, 0x1524, 0x155b, 0x1595, 0x15d1,
+ 0x1607, 0x1627, 0x1649, 0x166b, 0x168f, 0x16b5, 0x16db, 0x1703, 0x172c,
+ 0x1757, 0x1783, 0x17b1, 0x17e0, 0x1808, 0x1821, 0x183a, 0x1855, 0x1870,
+ 0x188c, 0x18a9, 0x18c7, 0x18e6, 0x1905, 0x1926, 0x1947, 0x1969, 0x198c,
+ 0x19b1, 0x19d6, 0x19fc, 0x1a11, 0x1a25, 0x1a3a, 0x1a4f, 0x1a64, 0x1a7a,
+ 0x1a91, 0x1aa8, 0x1ac0, 0x1ad8, 0x1af1, 0x1b0b, 0x1b25, 0x1b3f, 0x1b5b,
+ 0x1b77, 0x1b93, 0x1bb0, 0x1bce, 0x1bed, 0x1c06, 0x1c16, 0x1c26, 0x1c37,
+ 0x1c48, 0x1c59, 0x1c6b, 0x1c7d, 0x1c8f, 0x1ca2, 0x1cb6, 0x1cc9, 0x1cdd,
+ 0x1cf2, 0x1d07, 0x1d1c, 0x1d32, 0x1d48, 0x1d5f, 0x1d76, 0x1d8d, 0x1da5,
+ 0x1dbd, 0x1dd6, 0x1df0, 0x1e04, 0x1e12, 0x1e1f, 0x1e2d, 0x1e3b, 0x1e49,
+ 0x1e57, 0x1e66, 0x1e75, 0x1e84, 0x1e94, 0x1ea4, 0x1eb4, 0x1ec4, 0x1ed5,
+ 0x1ee6, 0x1ef7, 0x1f08, 0x1f1a, 0x1f2c, 0x1f3f, 0x1f52, 0x1f65, 0x1f78,
+ 0x1f8c, 0x1fa0, 0x1fb5, 0x1fca, 0x1fdf, 0x1ff4, 0x2005, 0x2010, 0x201b,
+ 0x2027, 0x2032, 0x203e, 0x204a, 0x2057, 0x2063, 0x2070, 0x207d, 0x208a,
+ 0x2097, 0x20a4, 0x20b2, 0x20c0, 0x20ce, 0x20dd, 0x20eb, 0x20fa, 0x2109,
+ 0x2118, 0x2128, 0x2138, 0x2148, 0x2158, 0x2168, 0x2179, 0x218a, 0x219b,
+ 0x21ad, 0x21bf, 0x21d1, 0x21e3, 0x21f6, 0x2204, 0x220e, 0x2217, 0x2221,
+ 0x222b, 0x2235, 0x2240, 0x224a, 0x2255, 0x225f, 0x226a, 0x2275, 0x2281,
+ 0x228c, 0x2298, 0x22a3, 0x22af, 0x22bb, 0x22c8, 0x22d4, 0x22e1, 0x22ed,
+ 0x22fa, 0x2307, 0x2315, 0x2322, 0x2330, 0x233e, 0x234c, 0x235a, 0x2368,
+ 0x2377, 0x2386, 0x2395, 0x23a4, 0x23b4, 0x23c3, 0x23d3, 0x23e3, 0x23f4,
+ 0x2402, 0x240a, 0x2413, 0x241b, 0x2424, 0x242d, 0x2436, 0x243f, 0x2448,
+ 0x2452, 0x245b, 0x2465, 0x246e, 0x2478, 0x2482, 0x248c, 0x2496, 0x24a1,
+ 0x24ab, 0x24b6, 0x24c1, 0x24cc, 0x24d7, 0x24e2, 0x24ed, 0x24f9, 0x2504,
+ 0x2510, 0x251c, 0x2528, 0x2534, 0x2541, 0x254d, 0x255a, 0x2567, 0x2574,
+ 0x2581, 0x258f, 0x259c, 0x25aa, 0x25b8, 0x25c6, 0x25d4, 0x25e3, 0x25f1,
+ 0x2600, 0x2607, 0x260f, 0x2616, 0x261e, 0x2626, 0x262e, 0x2636, 0x263e,
+ 0x2647, 0x264f, 0x2657, 0x2660, 0x2669, 0x2671, 0x267a, 0x2683, 0x268c,
+ 0x2696, 0x269f, 0x26a8, 0x26b2, 0x26bc, 0x26c5, 0x26cf, 0x26d9, 0x26e4,
+ 0x26ee, 0x26f8, 0x2703, 0x270d, 0x2718, 0x2723, 0x272e, 0x2739, 0x2745,
+ 0x2750, 0x275c, 0x2767, 0x2773, 0x277f, 0x278b, 0x2798, 0x27a4, 0x27b1,
+ 0x27bd, 0x27ca, 0x27d7, 0x27e4, 0x27f2, 0x27ff, 0x2806, 0x280d, 0x2814,
+ 0x281b, 0x2822, 0x2829, 0x2831, 0x2838, 0x2840, 0x2847, 0x284f, 0x2857,
+ 0x285e, 0x2866, 0x286e, 0x2877, 0x287f, 0x2887, 0x288f, 0x2898, 0x28a1,
+ 0x28a9, 0x28b2, 0x28bb, 0x28c4, 0x28cd, 0x28d6, 0x28df, 0x28e9, 0x28f2,
+ 0x28fc, 0x2906, 0x2910, 0x291a, 0x2924, 0x292e, 0x2938, 0x2942, 0x294d,
+ 0x2958, 0x2962, 0x296d, 0x2978, 0x2983, 0x298f, 0x299a, 0x29a6, 0x29b1,
+ 0x29bd, 0x29c9, 0x29d5, 0x29e1, 0x29ee, 0x29fa, 0x2a03, 0x2a09, 0x2a10,
+ 0x2a16, 0x2a1d, 0x2a24, 0x2a2a, 0x2a31, 0x2a38, 0x2a3f, 0x2a46, 0x2a4d,
+ 0x2a54, 0x2a5c, 0x2a63, 0x2a6a, 0x2a72, 0x2a79, 0x2a81, 0x2a89, 0x2a91,
+ 0x2a99, 0x2aa1, 0x2aa9, 0x2ab1, 0x2ab9, 0x2ac2, 0x2aca, 0x2ad3, 0x2adb,
+ 0x2ae4, 0x2aed, 0x2af6, 0x2aff, 0x2b08, 0x2b11, 0x2b1b, 0x2b24, 0x2b2e,
+ 0x2b37, 0x2b41, 0x2b4b, 0x2b55, 0x2b5f, 0x2b69, 0x2b73, 0x2b7e, 0x2b88,
+ 0x2b93, 0x2b9e, 0x2ba9, 0x2bb3, 0x2bbf, 0x2bca, 0x2bd5, 0x2be1, 0x2bec,
+ 0x2bf8, 0x2c02, 0x2c08, 0x2c0e, 0x2c14, 0x2c1a, 0x2c20, 0x2c26, 0x2c2d,
+ 0x2c33, 0x2c3a, 0x2c40, 0x2c47, 0x2c4e, 0x2c55, 0x2c5b, 0x2c62, 0x2c69,
+ 0x2c71, 0x2c78, 0x2c7f, 0x2c86, 0x2c8e, 0x2c95, 0x2c9d, 0x2ca4, 0x2cac,
+ 0x2cb4, 0x2cbc, 0x2cc4, 0x2ccc, 0x2cd4, 0x2cdc, 0x2ce4, 0x2ced, 0x2cf5,
+ 0x2cfe, 0x2d07, 0x2d0f, 0x2d18, 0x2d21, 0x2d2a, 0x2d33, 0x2d3d, 0x2d46,
+ 0x2d4f, 0x2d59, 0x2d63, 0x2d6c, 0x2d76, 0x2d80, 0x2d8a, 0x2d94, 0x2d9f,
+ 0x2da9, 0x2db3, 0x2dbe, 0x2dc9, 0x2dd4, 0x2dde, 0x2dea, 0x2df5, 0x2e00,
+ 0x2e05, 0x2e0b, 0x2e11, 0x2e17, 0x2e1d, 0x2e23, 0x2e29, 0x2e2f, 0x2e35,
+ 0x2e3b, 0x2e42, 0x2e48, 0x2e4f, 0x2e55, 0x2e5c, 0x2e62, 0x2e69, 0x2e70,
+ 0x2e77, 0x2e7e, 0x2e85, 0x2e8c, 0x2e93, 0x2e9a, 0x2ea2, 0x2ea9, 0x2eb1,
+ 0x2eb8, 0x2ec0, 0x2ec7, 0x2ecf, 0x2ed7, 0x2edf, 0x2ee7, 0x2eef, 0x2ef7,
+ 0x2f00, 0x2f08, 0x2f11, 0x2f19, 0x2f22, 0x2f2a, 0x2f33, 0x2f3c, 0x2f45,
+ 0x2f4e, 0x2f58, 0x2f61, 0x2f6a, 0x2f74, 0x2f7d, 0x2f87, 0x2f91, 0x2f9b,
+ 0x2fa5, 0x2faf, 0x2fb9, 0x2fc3, 0x2fce, 0x2fd8, 0x2fe3, 0x2fee, 0x2ff8,
+ 0x3001, 0x3007, 0x300d, 0x3012, 0x3018, 0x301e, 0x3024, 0x3029, 0x302f,
+ 0x3035, 0x303c, 0x3042, 0x3048, 0x304e, 0x3055, 0x305b, 0x3061, 0x3068,
+ 0x306f, 0x3075, 0x307c, 0x3083, 0x308a, 0x3091, 0x3098, 0x309f, 0x30a6,
+ 0x30ad, 0x30b4, 0x30bc, 0x30c3, 0x30cb, 0x30d3, 0x30da, 0x30e2, 0x30ea,
+ 0x30f2, 0x30fa, 0x3102, 0x310a, 0x3113, 0x311b, 0x3123, 0x312c, 0x3135,
+ 0x313d, 0x3146, 0x314f, 0x3158, 0x3161, 0x316a, 0x3174, 0x317d, 0x3187,
+ 0x3190, 0x319a, 0x31a4, 0x31ad, 0x31b7, 0x31c2, 0x31cc, 0x31d6, 0x31e0,
+ 0x31eb, 0x31f6, 0x3200, 0x3205, 0x320b, 0x3210, 0x3216, 0x321c, 0x3221,
+ 0x3227, 0x322d, 0x3233, 0x3239, 0x323f, 0x3245, 0x324b, 0x3251, 0x3258,
+ 0x325e, 0x3264, 0x326b, 0x3271, 0x3278, 0x327f, 0x3285, 0x328c, 0x3293,
+ 0x329a, 0x32a1, 0x32a8, 0x32af, 0x32b6, 0x32be, 0x32c5, 0x32cd, 0x32d4,
+ 0x32dc, 0x32e4, 0x32eb, 0x32f3, 0x32fb, 0x3303, 0x330b, 0x3314, 0x331c,
+ 0x3324, 0x332d, 0x3335, 0x333e, 0x3347, 0x3350, 0x3358, 0x3361, 0x336b,
+ 0x3374, 0x337d, 0x3387, 0x3390, 0x339a, 0x33a3, 0x33ad, 0x33b7, 0x33c1,
+ 0x33cb, 0x33d5, 0x33e0, 0x33ea, 0x33f5, 0x33ff, 0x3405, 0x340a, 0x3410,
+ 0x3415, 0x341b, 0x3421, 0x3426, 0x342c, 0x3432, 0x3438, 0x343e, 0x3444,
+ 0x344a, 0x3450, 0x3456, 0x345d, 0x3463, 0x346a, 0x3470, 0x3477, 0x347d,
+ 0x3484, 0x348b, 0x3492, 0x3498, 0x349f, 0x34a7, 0x34ae, 0x34b5, 0x34bc,
+ 0x34c4, 0x34cb, 0x34d3, 0x34da, 0x34e2, 0x34ea, 0x34f1, 0x34f9, 0x3501,
+ 0x350a, 0x3512, 0x351a, 0x3522, 0x352b, 0x3533, 0x353c, 0x3545, 0x354e,
+ 0x3557, 0x3560, 0x3569, 0x3572, 0x357b, 0x3585, 0x358e, 0x3598, 0x35a1,
+ 0x35ab, 0x35b5, 0x35bf, 0x35c9, 0x35d3, 0x35de, 0x35e8, 0x35f3, 0x35fe,
+ 0x3604, 0x3609, 0x360f, 0x3614, 0x361a, 0x3620, 0x3626, 0x362b, 0x3631,
+ 0x3637, 0x363d, 0x3643, 0x3649, 0x3650, 0x3656, 0x365c, 0x3663, 0x3669,
+ 0x3670, 0x3676, 0x367d, 0x3684, 0x368a, 0x3691, 0x3698, 0x369f, 0x36a7,
+ 0x36ae, 0x36b5, 0x36bc, 0x36c4, 0x36cb, 0x36d3, 0x36db, 0x36e2, 0x36ea,
+ 0x36f2, 0x36fa, 0x3702, 0x370b, 0x3713, 0x371b, 0x3724, 0x372c, 0x3735,
+ 0x373e, 0x3747, 0x374f, 0x3758, 0x3762, 0x376b, 0x3774, 0x377e, 0x3787,
+ 0x3791, 0x379b, 0x37a4, 0x37ae, 0x37b9, 0x37c3, 0x37cd, 0x37d7, 0x37e2,
+ 0x37ed, 0x37f7, 0x3801, 0x3806, 0x380c, 0x3812, 0x3817, 0x381d, 0x3823,
+ 0x3829, 0x382f, 0x3835, 0x383b, 0x3841, 0x3847, 0x384d, 0x3854, 0x385a,
+ 0x3861, 0x3867, 0x386e, 0x3874, 0x387b, 0x3882, 0x3889, 0x3890, 0x3897,
+ 0x389e, 0x38a6, 0x38ad, 0x38b4, 0x38bc, 0x38c3, 0x38cb, 0x38d3, 0x38db,
+ 0x38e3, 0x38eb, 0x38f3, 0x38fb, 0x3903, 0x390b, 0x3914, 0x391c, 0x3925,
+ 0x392e, 0x3937, 0x3940, 0x3949, 0x3952, 0x395b, 0x3965, 0x396e, 0x3978,
+ 0x3981, 0x398b, 0x3995, 0x399f, 0x39a9, 0x39b3, 0x39be, 0x39c8, 0x39d3,
+ 0x39de, 0x39e8, 0x39f3, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x3},
+ {0x3, 0x0, 0x200, 0x500, 0x700, 0x8c0, 0xa20, 0xae0, 0xbe0, 0xc70,
+ 0xd00, 0xdb0, 0xe30, 0xe98, 0xf00, 0xf78, 0xff8, 0x103c, 0x1084,
+ 0x10d0, 0x1120, 0x1178, 0x11d0, 0x1218, 0x124a, 0x127e, 0x12b6, 0x12f0,
+ 0x132c, 0x136c, 0x13ae, 0x13f2, 0x141d, 0x1442, 0x1469, 0x1491, 0x14bb,
+ 0x14e6, 0x1513, 0x1542, 0x1572, 0x15a4, 0x15d7, 0x1606, 0x1621, 0x163e,
+ 0x165b, 0x1679, 0x1698, 0x16b9, 0x16da, 0x16fc, 0x171f, 0x1743, 0x1768,
+ 0x178e, 0x17b5, 0x17dd, 0x1803, 0x1818, 0x182e, 0x1844, 0x185b, 0x1873,
+ 0x188b, 0x18a4, 0x18bd, 0x18d7, 0x18f2, 0x190d, 0x1929, 0x1945, 0x1962,
+ 0x1980, 0x199f, 0x19be, 0x19de, 0x19ff, 0x1a10, 0x1a21, 0x1a32, 0x1a44,
+ 0x1a56, 0x1a69, 0x1a7c, 0x1a8f, 0x1aa3, 0x1ab7, 0x1acc, 0x1ae1, 0x1af6,
+ 0x1b0c, 0x1b23, 0x1b39, 0x1b51, 0x1b68, 0x1b81, 0x1b99, 0x1bb2, 0x1bcc,
+ 0x1be6, 0x1c00, 0x1c0e, 0x1c1b, 0x1c29, 0x1c38, 0x1c46, 0x1c55, 0x1c64,
+ 0x1c74, 0x1c83, 0x1c93, 0x1ca3, 0x1cb4, 0x1cc5, 0x1cd6, 0x1ce7, 0x1cf9,
+ 0x1d0b, 0x1d1d, 0x1d30, 0x1d42, 0x1d56, 0x1d69, 0x1d7d, 0x1d91, 0x1da6,
+ 0x1dbb, 0x1dd0, 0x1de6, 0x1dfc, 0x1e09, 0x1e14, 0x1e20, 0x1e2b, 0x1e37,
+ 0x1e43, 0x1e4f, 0x1e5c, 0x1e69, 0x1e75, 0x1e82, 0x1e90, 0x1e9d, 0x1eab,
+ 0x1eb9, 0x1ec7, 0x1ed5, 0x1ee3, 0x1ef2, 0x1f01, 0x1f10, 0x1f20, 0x1f2f,
+ 0x1f3f, 0x1f4f, 0x1f60, 0x1f70, 0x1f81, 0x1f92, 0x1fa3, 0x1fb5, 0x1fc7,
+ 0x1fd9, 0x1feb, 0x1ffe, 0x2008, 0x2012, 0x201b, 0x2025, 0x202f, 0x2039,
+ 0x2043, 0x204e, 0x2058, 0x2063, 0x206e, 0x2079, 0x2084, 0x208f, 0x209b,
+ 0x20a6, 0x20b2, 0x20be, 0x20ca, 0x20d6, 0x20e3, 0x20ef, 0x20fc, 0x2109,
+ 0x2116, 0x2123, 0x2131, 0x213e, 0x214c, 0x215a, 0x2168, 0x2176, 0x2185,
+ 0x2194, 0x21a2, 0x21b2, 0x21c1, 0x21d0, 0x21e0, 0x21f0, 0x2200, 0x2208,
+ 0x2210, 0x2218, 0x2221, 0x2229, 0x2232, 0x223b, 0x2244, 0x224d, 0x2256,
+ 0x225f, 0x2268, 0x2272, 0x227b, 0x2285, 0x228f, 0x2299, 0x22a3, 0x22ad,
+ 0x22b7, 0x22c2, 0x22cc, 0x22d7, 0x22e2, 0x22ed, 0x22f8, 0x2303, 0x230e,
+ 0x231a, 0x2325, 0x2331, 0x233d, 0x2349, 0x2355, 0x2361, 0x236e, 0x237a,
+ 0x2387, 0x2394, 0x23a1, 0x23ae, 0x23bc, 0x23c9, 0x23d7, 0x23e5, 0x23f3,
+ 0x2400, 0x2407, 0x240f, 0x2416, 0x241d, 0x2425, 0x242c, 0x2434, 0x243c,
+ 0x2444, 0x244c, 0x2454, 0x245c, 0x2464, 0x246c, 0x2475, 0x247d, 0x2486,
+ 0x248e, 0x2497, 0x24a0, 0x24a9, 0x24b2, 0x24bb, 0x24c4, 0x24ce, 0x24d7,
+ 0x24e1, 0x24eb, 0x24f4, 0x24fe, 0x2508, 0x2512, 0x251d, 0x2527, 0x2531,
+ 0x253c, 0x2547, 0x2552, 0x255d, 0x2568, 0x2573, 0x257e, 0x2589, 0x2595,
+ 0x25a1, 0x25ac, 0x25b8, 0x25c4, 0x25d1, 0x25dd, 0x25e9, 0x25f6, 0x2601,
+ 0x2607, 0x260e, 0x2614, 0x261b, 0x2622, 0x2629, 0x262f, 0x2636, 0x263d,
+ 0x2644, 0x264c, 0x2653, 0x265a, 0x2661, 0x2669, 0x2670, 0x2678, 0x2680,
+ 0x2687, 0x268f, 0x2697, 0x269f, 0x26a7, 0x26af, 0x26b8, 0x26c0, 0x26c8,
+ 0x26d1, 0x26d9, 0x26e2, 0x26eb, 0x26f4, 0x26fd, 0x2706, 0x270f, 0x2718,
+ 0x2722, 0x272b, 0x2734, 0x273e, 0x2748, 0x2752, 0x275c, 0x2766, 0x2770,
+ 0x277a, 0x2784, 0x278f, 0x2799, 0x27a4, 0x27af, 0x27b9, 0x27c4, 0x27d0,
+ 0x27db, 0x27e6, 0x27f1, 0x27fd, 0x2804, 0x280a, 0x2810, 0x2816, 0x281c,
+ 0x2822, 0x2828, 0x282f, 0x2835, 0x283b, 0x2842, 0x2848, 0x284f, 0x2855,
+ 0x285c, 0x2863, 0x286a, 0x2870, 0x2877, 0x287e, 0x2886, 0x288d, 0x2894,
+ 0x289b, 0x28a3, 0x28aa, 0x28b2, 0x28b9, 0x28c1, 0x28c9, 0x28d0, 0x28d8,
+ 0x28e0, 0x28e8, 0x28f1, 0x28f9, 0x2901, 0x2909, 0x2912, 0x291a, 0x2923,
+ 0x292c, 0x2935, 0x293d, 0x2946, 0x294f, 0x2959, 0x2962, 0x296b, 0x2975,
+ 0x297e, 0x2988, 0x2991, 0x299b, 0x29a5, 0x29af, 0x29b9, 0x29c3, 0x29cd,
+ 0x29d8, 0x29e2, 0x29ed, 0x29f7, 0x2a01, 0x2a06, 0x2a0c, 0x2a11, 0x2a17,
+ 0x2a1c, 0x2a22, 0x2a28, 0x2a2e, 0x2a33, 0x2a39, 0x2a3f, 0x2a45, 0x2a4b,
+ 0x2a52, 0x2a58, 0x2a5e, 0x2a64, 0x2a6b, 0x2a71, 0x2a78, 0x2a7e, 0x2a85,
+ 0x2a8b, 0x2a92, 0x2a99, 0x2aa0, 0x2aa7, 0x2aae, 0x2ab5, 0x2abc, 0x2ac3,
+ 0x2aca, 0x2ad2, 0x2ad9, 0x2ae1, 0x2ae8, 0x2af0, 0x2af7, 0x2aff, 0x2b07,
+ 0x2b0f, 0x2b17, 0x2b1f, 0x2b27, 0x2b2f, 0x2b38, 0x2b40, 0x2b48, 0x2b51,
+ 0x2b59, 0x2b62, 0x2b6b, 0x2b74, 0x2b7d, 0x2b86, 0x2b8f, 0x2b98, 0x2ba1,
+ 0x2baa, 0x2bb4, 0x2bbd, 0x2bc7, 0x2bd0, 0x2bda, 0x2be4, 0x2bee, 0x2bf8,
+ 0x2c01, 0x2c06, 0x2c0b, 0x2c10, 0x2c16, 0x2c1b, 0x2c20, 0x2c26, 0x2c2b,
+ 0x2c31, 0x2c36, 0x2c3c, 0x2c41, 0x2c47, 0x2c4d, 0x2c53, 0x2c59, 0x2c5e,
+ 0x2c64, 0x2c6a, 0x2c70, 0x2c77, 0x2c7d, 0x2c83, 0x2c89, 0x2c90, 0x2c96,
+ 0x2c9d, 0x2ca3, 0x2caa, 0x2cb0, 0x2cb7, 0x2cbe, 0x2cc5, 0x2ccc, 0x2cd3,
+ 0x2cda, 0x2ce1, 0x2ce8, 0x2cef, 0x2cf6, 0x2cfe, 0x2d05, 0x2d0d, 0x2d14,
+ 0x2d1c, 0x2d24, 0x2d2b, 0x2d33, 0x2d3b, 0x2d43, 0x2d4b, 0x2d53, 0x2d5b,
+ 0x2d64, 0x2d6c, 0x2d74, 0x2d7d, 0x2d85, 0x2d8e, 0x2d97, 0x2da0, 0x2da9,
+ 0x2db2, 0x2dbb, 0x2dc4, 0x2dcd, 0x2dd6, 0x2de0, 0x2de9, 0x2df3, 0x2dfc,
+ 0x2e03, 0x2e08, 0x2e0d, 0x2e12, 0x2e17, 0x2e1c, 0x2e21, 0x2e26, 0x2e2b,
+ 0x2e30, 0x2e36, 0x2e3b, 0x2e41, 0x2e46, 0x2e4c, 0x2e51, 0x2e57, 0x2e5c,
+ 0x2e62, 0x2e68, 0x2e6e, 0x2e73, 0x2e79, 0x2e7f, 0x2e85, 0x2e8b, 0x2e92,
+ 0x2e98, 0x2e9e, 0x2ea4, 0x2eab, 0x2eb1, 0x2eb8, 0x2ebe, 0x2ec5, 0x2ecb,
+ 0x2ed2, 0x2ed9, 0x2ee0, 0x2ee6, 0x2eed, 0x2ef4, 0x2efb, 0x2f03, 0x2f0a,
+ 0x2f11, 0x2f18, 0x2f20, 0x2f27, 0x2f2f, 0x2f36, 0x2f3e, 0x2f46, 0x2f4e,
+ 0x2f55, 0x2f5d, 0x2f65, 0x2f6d, 0x2f76, 0x2f7e, 0x2f86, 0x2f8e, 0x2f97,
+ 0x2f9f, 0x2fa8, 0x2fb1, 0x2fb9, 0x2fc2, 0x2fcb, 0x2fd4, 0x2fdd, 0x2fe6,
+ 0x2ff0, 0x2ff9, 0x3001, 0x3006, 0x300a, 0x300f, 0x3014, 0x3019, 0x301e,
+ 0x3023, 0x3028, 0x302d, 0x3032, 0x3037, 0x303d, 0x3042, 0x3047, 0x304d,
+ 0x3052, 0x3057, 0x305d, 0x3062, 0x3068, 0x306e, 0x3073, 0x3079, 0x307f,
+ 0x3085, 0x308b, 0x3091, 0x3097, 0x309d, 0x30a3, 0x30a9, 0x30af, 0x30b6,
+ 0x30bc, 0x30c2, 0x30c9, 0x30cf, 0x30d6, 0x30dd, 0x30e3, 0x30ea, 0x30f1,
+ 0x30f8, 0x30ff, 0x3106, 0x310d, 0x3114, 0x311b, 0x3122, 0x3129, 0x3131,
+ 0x3138, 0x3140, 0x3147, 0x314f, 0x3157, 0x315e, 0x3166, 0x316e, 0x3176,
+ 0x317e, 0x3186, 0x318f, 0x3197, 0x319f, 0x31a8, 0x31b0, 0x31b9, 0x31c1,
+ 0x31ca, 0x31d3, 0x31dc, 0x31e5, 0x31ee, 0x31f7, 0x3200, 0x3204, 0x3209,
+ 0x320e, 0x3213, 0x3217, 0x321c, 0x3221, 0x3226, 0x322b, 0x3230, 0x3235,
+ 0x323a, 0x323f, 0x3245, 0x324a, 0x324f, 0x3255, 0x325a, 0x325f, 0x3265,
+ 0x326a, 0x3270, 0x3276, 0x327b, 0x3281, 0x3287, 0x328d, 0x3293, 0x3299,
+ 0x329f, 0x32a5, 0x32ab, 0x32b1, 0x32b7, 0x32bd, 0x32c4, 0x32ca, 0x32d1,
+ 0x32d7, 0x32de, 0x32e4, 0x32eb, 0x32f2, 0x32f8, 0x32ff, 0x3306, 0x330d,
+ 0x3314, 0x331b, 0x3322, 0x332a, 0x3331, 0x3338, 0x3340, 0x3347, 0x334f,
+ 0x3357, 0x335e, 0x3366, 0x336e, 0x3376, 0x337e, 0x3386, 0x338e, 0x3396,
+ 0x339e, 0x33a7, 0x33af, 0x33b8, 0x33c0, 0x33c9, 0x33d2, 0x33da, 0x33e3,
+ 0x33ec, 0x33f5, 0x33fe, 0x3404, 0x3408, 0x340d, 0x3412, 0x3416, 0x341b,
+ 0x3420, 0x3425, 0x342a, 0x342f, 0x3434, 0x3439, 0x343e, 0x3443, 0x3448,
+ 0x344e, 0x3453, 0x3458, 0x345e, 0x3463, 0x3469, 0x346e, 0x3474, 0x347a,
+ 0x347f, 0x3485, 0x348b, 0x3491, 0x3497, 0x349d, 0x34a3, 0x34a9, 0x34af,
+ 0x34b5, 0x34bb, 0x34c2, 0x34c8, 0x34ce, 0x34d5, 0x34db, 0x34e2, 0x34e9,
+ 0x34ef, 0x34f6, 0x34fd, 0x3504, 0x350b, 0x3512, 0x3519, 0x3520, 0x3527,
+ 0x352f, 0x3536, 0x353d, 0x3545, 0x354c, 0x3554, 0x355c, 0x3563, 0x356b,
+ 0x3573, 0x357b, 0x3583, 0x358b, 0x3593, 0x359c, 0x35a4, 0x35ad, 0x35b5,
+ 0x35be, 0x35c6, 0x35cf, 0x35d8, 0x35e1, 0x35ea, 0x35f3, 0x35fc, 0x3602,
+ 0x3607, 0x360c, 0x3610, 0x3615, 0x361a, 0x361f, 0x3624, 0x3629, 0x362e,
+ 0x3633, 0x3638, 0x363d, 0x3642, 0x3647, 0x364d, 0x3652, 0x3657, 0x365d,
+ 0x3662, 0x3668, 0x366d, 0x3673, 0x3679, 0x367f, 0x3684, 0x368a, 0x3690,
+ 0x3696, 0x369c, 0x36a2, 0x36a8, 0x36ae, 0x36b5, 0x36bb, 0x36c1, 0x36c8,
+ 0x36ce, 0x36d5, 0x36db, 0x36e2, 0x36e9, 0x36f0, 0x36f6, 0x36fd, 0x3704,
+ 0x370b, 0x3712, 0x371a, 0x3721, 0x3728, 0x372f, 0x3737, 0x373e, 0x3746,
+ 0x374e, 0x3755, 0x375d, 0x3765, 0x376d, 0x3775, 0x377d, 0x3785, 0x378d,
+ 0x3796, 0x379e, 0x37a7, 0x37af, 0x37b8, 0x37c1, 0x37c9, 0x37d2, 0x37db,
+ 0x37e4, 0x37ed, 0x37f7, 0x3800, 0x3804, 0x3809, 0x380e, 0x3813, 0x3818,
+ 0x381d, 0x3821, 0x3826, 0x382c, 0x3831, 0x3836, 0x383b, 0x3840, 0x3846,
+ 0x384b, 0x3850, 0x3856, 0x385b, 0x3861, 0x3866, 0x386c, 0x3872, 0x3878,
+ 0x387d, 0x3883, 0x3889, 0x388f, 0x3895, 0x389b, 0x38a2, 0x38a8, 0x38ae,
+ 0x38b5, 0x38bb, 0x38c1, 0x38c8, 0x38cf, 0x38d5, 0x38dc, 0x38e3, 0x38ea,
+ 0x38f1, 0x38f8, 0x38ff, 0x3906, 0x390d, 0x3914, 0x391b, 0x3923, 0x392a,
+ 0x3932, 0x3939, 0x3941, 0x3949, 0x3951, 0x3959, 0x3961, 0x3969, 0x3971,
+ 0x3979, 0x3981, 0x398a, 0x3992, 0x399b, 0x39a3, 0x39ac, 0x39b5, 0x39be,
+ 0x39c7, 0x39d0, 0x39d9, 0x39e2, 0x39ec, 0x39f5, 0x39ff, 0x3},
+ {0x3, 0x0, 0x4, 0x8, 0xc, 0x10, 0x14, 0x18, 0x1d, 0x21, 0x25, 0x29,
+ 0x2d, 0x31, 0x36, 0x3a, 0x3e, 0x42, 0x46, 0x4a, 0x4e, 0x53, 0x57, 0x5b,
+ 0x5f, 0x63, 0x67, 0x6c, 0x70, 0x74, 0x78, 0x7c, 0x80, 0x84, 0x89, 0x8d,
+ 0x91, 0x95, 0x99, 0x9d, 0xa2, 0xa6, 0xaa, 0xae, 0xb2, 0xb6, 0xbb, 0xbf,
+ 0xc3, 0xc7, 0xcb, 0xcf, 0xd3, 0xd8, 0xdc, 0xe0, 0xe4, 0xe8, 0xec, 0xf1,
+ 0xf5, 0xf9, 0xfd, 0x101, 0x105, 0x109, 0x10e, 0x112, 0x116, 0x11a,
+ 0x11e, 0x122, 0x126, 0x12a, 0x12e, 0x132, 0x137, 0x13b, 0x13f, 0x143,
+ 0x148, 0x14c, 0x151, 0x155, 0x15a, 0x15e, 0x163, 0x167, 0x16c, 0x171,
+ 0x176, 0x17a, 0x17f, 0x184, 0x189, 0x18e, 0x193, 0x197, 0x19c, 0x1a1,
+ 0x1a6, 0x1ac, 0x1b1, 0x1b6, 0x1bb, 0x1c0, 0x1c5, 0x1cb, 0x1d0, 0x1d5,
+ 0x1db, 0x1e0, 0x1e6, 0x1eb, 0x1f1, 0x1f6, 0x1fc, 0x201, 0x207, 0x20d,
+ 0x212, 0x218, 0x21e, 0x224, 0x22a, 0x230, 0x235, 0x23b, 0x241, 0x247,
+ 0x24d, 0x254, 0x25a, 0x260, 0x266, 0x26c, 0x273, 0x279, 0x27f, 0x286,
+ 0x28c, 0x292, 0x299, 0x29f, 0x2a6, 0x2ad, 0x2b3, 0x2ba, 0x2c0, 0x2c7,
+ 0x2ce, 0x2d5, 0x2dc, 0x2e2, 0x2e9, 0x2f0, 0x2f7, 0x2fe, 0x305, 0x30c,
+ 0x313, 0x31b, 0x322, 0x329, 0x330, 0x338, 0x33f, 0x346, 0x34e, 0x355,
+ 0x35c, 0x364, 0x36c, 0x373, 0x37b, 0x382, 0x38a, 0x392, 0x399, 0x3a1,
+ 0x3a9, 0x3b1, 0x3b9, 0x3c1, 0x3c9, 0x3d1, 0x3d9, 0x3e1, 0x3e9, 0x3f1,
+ 0x3f9, 0x402, 0x40a, 0x412, 0x41a, 0x423, 0x42b, 0x434, 0x43c, 0x445,
+ 0x44d, 0x456, 0x45e, 0x467, 0x470, 0x478, 0x481, 0x48a, 0x493, 0x49c,
+ 0x4a5, 0x4ae, 0x4b7, 0x4c0, 0x4c9, 0x4d2, 0x4db, 0x4e4, 0x4ed, 0x4f7,
+ 0x500, 0x509, 0x513, 0x51c, 0x525, 0x52f, 0x538, 0x542, 0x54c, 0x555,
+ 0x55f, 0x569, 0x572, 0x57c, 0x586, 0x590, 0x59a, 0x5a4, 0x5ad, 0x5b7,
+ 0x5c2, 0x5cc, 0x5d6, 0x5e0, 0x5ea, 0x5f4, 0x5ff, 0x609, 0x613, 0x61e,
+ 0x628, 0x632, 0x63d, 0x647, 0x652, 0x65d, 0x667, 0x672, 0x67d, 0x687,
+ 0x692, 0x69d, 0x6a8, 0x6b3, 0x6be, 0x6c9, 0x6d4, 0x6df, 0x6ea, 0x6f5,
+ 0x700, 0x70c, 0x717, 0x722, 0x72d, 0x739, 0x744, 0x750, 0x75b, 0x767,
+ 0x772, 0x77e, 0x789, 0x795, 0x7a1, 0x7ad, 0x7b8, 0x7c4, 0x7d0, 0x7dc,
+ 0x7e8, 0x7f4, 0x800, 0x80c, 0x818, 0x824, 0x831, 0x83d, 0x849, 0x855,
+ 0x862, 0x86e, 0x87b, 0x887, 0x893, 0x8a0, 0x8ad, 0x8b9, 0x8c6, 0x8d3,
+ 0x8df, 0x8ec, 0x8f9, 0x906, 0x913, 0x920, 0x92d, 0x93a, 0x947, 0x954,
+ 0x961, 0x96e, 0x97b, 0x988, 0x996, 0x9a3, 0x9b0, 0x9be, 0x9cb, 0x9d9,
+ 0x9e6, 0x9f4, 0xa02, 0xa0f, 0xa1d, 0xa2b, 0xa38, 0xa46, 0xa54, 0xa62,
+ 0xa70, 0xa7e, 0xa8c, 0xa9a, 0xaa8, 0xab6, 0xac4, 0xad2, 0xae1, 0xaef,
+ 0xafd, 0xb0c, 0xb1a, 0xb29, 0xb37, 0xb46, 0xb54, 0xb63, 0xb71, 0xb80,
+ 0xb8f, 0xb9e, 0xbac, 0xbbb, 0xbca, 0xbd9, 0xbe8, 0xbf7, 0xc06, 0xc15,
+ 0xc24, 0xc34, 0xc43, 0xc52, 0xc61, 0xc71, 0xc80, 0xc90, 0xc9f, 0xcaf,
+ 0xcbe, 0xcce, 0xcdd, 0xced, 0xcfd, 0xd0c, 0xd1c, 0xd2c, 0xd3c, 0xd4c,
+ 0xd5c, 0xd6c, 0xd7c, 0xd8c, 0xd9c, 0xdac, 0xdbc, 0xdcd, 0xddd, 0xded,
+ 0xdfe, 0xe0e, 0xe1f, 0xe2f, 0xe40, 0xe50, 0xe61, 0xe71, 0xe82, 0xe93,
+ 0xea4, 0xeb4, 0xec5, 0xed6, 0xee7, 0xef8, 0xf09, 0xf1a, 0xf2b, 0xf3d,
+ 0xf4e, 0xf5f, 0xf70, 0xf82, 0xf93, 0xfa4, 0xfb6, 0xfc7, 0xfd9, 0xfea,
+ 0xffc, 0x100e, 0x101f, 0x1031, 0x1043, 0x1055, 0x1067, 0x1078, 0x108a,
+ 0x109c, 0x10ae, 0x10c0, 0x10d3, 0x10e5, 0x10f7, 0x1109, 0x111b, 0x112e,
+ 0x1140, 0x1153, 0x1165, 0x1178, 0x118a, 0x119d, 0x11af, 0x11c2, 0x11d5,
+ 0x11e7, 0x11fa, 0x120d, 0x1220, 0x1233, 0x1246, 0x1259, 0x126c, 0x127f,
+ 0x1292, 0x12a5, 0x12b8, 0x12cc, 0x12df, 0x12f2, 0x1306, 0x1319, 0x132d,
+ 0x1340, 0x1354, 0x1367, 0x137b, 0x138f, 0x13a2, 0x13b6, 0x13ca, 0x13de,
+ 0x13f2, 0x1406, 0x141a, 0x142e, 0x1442, 0x1456, 0x146a, 0x147e, 0x1492,
+ 0x14a7, 0x14bb, 0x14cf, 0x14e4, 0x14f8, 0x150d, 0x1521, 0x1536, 0x154a,
+ 0x155f, 0x1574, 0x1588, 0x159d, 0x15b2, 0x15c7, 0x15dc, 0x15f1, 0x1606,
+ 0x161b, 0x1630, 0x1645, 0x165a, 0x1670, 0x1685, 0x169a, 0x16b0, 0x16c5,
+ 0x16da, 0x16f0, 0x1705, 0x171b, 0x1731, 0x1746, 0x175c, 0x1772, 0x1788,
+ 0x179d, 0x17b3, 0x17c9, 0x17df, 0x17f5, 0x180b, 0x1821, 0x1837, 0x184e,
+ 0x1864, 0x187a, 0x1890, 0x18a7, 0x18bd, 0x18d4, 0x18ea, 0x1901, 0x1917,
+ 0x192e, 0x1944, 0x195b, 0x1972, 0x1989, 0x19a0, 0x19b6, 0x19cd, 0x19e4,
+ 0x19fb, 0x1a12, 0x1a29, 0x1a41, 0x1a58, 0x1a6f, 0x1a86, 0x1a9e, 0x1ab5,
+ 0x1acc, 0x1ae4, 0x1afb, 0x1b13, 0x1b2a, 0x1b42, 0x1b5a, 0x1b72, 0x1b89,
+ 0x1ba1, 0x1bb9, 0x1bd1, 0x1be9, 0x1c01, 0x1c19, 0x1c31, 0x1c49, 0x1c61,
+ 0x1c79, 0x1c92, 0x1caa, 0x1cc2, 0x1cdb, 0x1cf3, 0x1d0b, 0x1d24, 0x1d3d,
+ 0x1d55, 0x1d6e, 0x1d86, 0x1d9f, 0x1db8, 0x1dd1, 0x1dea, 0x1e03, 0x1e1c,
+ 0x1e35, 0x1e4e, 0x1e67, 0x1e80, 0x1e99, 0x1eb2, 0x1ecb, 0x1ee5, 0x1efe,
+ 0x1f18, 0x1f31, 0x1f4a, 0x1f64, 0x1f7e, 0x1f97, 0x1fb1, 0x1fcb, 0x1fe4,
+ 0x1ffe, 0x2018, 0x2032, 0x204c, 0x2066, 0x2080, 0x209a, 0x20b4, 0x20ce,
+ 0x20e8, 0x2103, 0x211d, 0x2137, 0x2152, 0x216c, 0x2187, 0x21a1, 0x21bc,
+ 0x21d6, 0x21f1, 0x220c, 0x2226, 0x2241, 0x225c, 0x2277, 0x2292, 0x22ad,
+ 0x22c8, 0x22e3, 0x22fe, 0x2319, 0x2334, 0x2350, 0x236b, 0x2386, 0x23a2,
+ 0x23bd, 0x23d9, 0x23f4, 0x2410, 0x242b, 0x2447, 0x2463, 0x247e, 0x249a,
+ 0x24b6, 0x24d2, 0x24ee, 0x250a, 0x2526, 0x2542, 0x255e, 0x257a, 0x2596,
+ 0x25b3, 0x25cf, 0x25eb, 0x2608, 0x2624, 0x2640, 0x265d, 0x267a, 0x2696,
+ 0x26b3, 0x26d0, 0x26ec, 0x2709, 0x2726, 0x2743, 0x2760, 0x277d, 0x279a,
+ 0x27b7, 0x27d4, 0x27f1, 0x280e, 0x282b, 0x2849, 0x2866, 0x2884, 0x28a1,
+ 0x28be, 0x28dc, 0x28fa, 0x2917, 0x2935, 0x2953, 0x2970, 0x298e, 0x29ac,
+ 0x29ca, 0x29e8, 0x2a06, 0x2a24, 0x2a42, 0x2a60, 0x2a7e, 0x2a9c, 0x2abb,
+ 0x2ad9, 0x2af7, 0x2b16, 0x2b34, 0x2b53, 0x2b71, 0x2b90, 0x2bae, 0x2bcd,
+ 0x2bec, 0x2c0a, 0x2c29, 0x2c48, 0x2c67, 0x2c86, 0x2ca5, 0x2cc4, 0x2ce3,
+ 0x2d02, 0x2d21, 0x2d41, 0x2d60, 0x2d7f, 0x2d9f, 0x2dbe, 0x2dde, 0x2dfd,
+ 0x2e1d, 0x2e3c, 0x2e5c, 0x2e7c, 0x2e9b, 0x2ebb, 0x2edb, 0x2efb, 0x2f1b,
+ 0x2f3b, 0x2f5b, 0x2f7b, 0x2f9b, 0x2fbb, 0x2fdb, 0x2ffc, 0x301c, 0x303c,
+ 0x305d, 0x307d, 0x309e, 0x30be, 0x30df, 0x30ff, 0x3120, 0x3141, 0x3161,
+ 0x3182, 0x31a3, 0x31c4, 0x31e5, 0x3206, 0x3227, 0x3248, 0x3269, 0x328a,
+ 0x32ac, 0x32cd, 0x32ee, 0x3310, 0x3331, 0x3353, 0x3374, 0x3396, 0x33b7,
+ 0x33d9, 0x33fb, 0x341c, 0x343e, 0x3460, 0x3482, 0x34a4, 0x34c6, 0x34e8,
+ 0x350a, 0x352c, 0x354e, 0x3571, 0x3593, 0x35b5, 0x35d7, 0x35fa, 0x361c,
+ 0x363f, 0x3661, 0x3684, 0x36a7, 0x36c9, 0x36ec, 0x370f, 0x3732, 0x3755,
+ 0x3778, 0x379b, 0x37be, 0x37e1, 0x3804, 0x3827, 0x384a, 0x386d, 0x3891,
+ 0x38b4, 0x38d7, 0x38fb, 0x391e, 0x3942, 0x3966, 0x3989, 0x39ad, 0x39d1,
+ 0x39f4, 0x3a18, 0x3a3c, 0x3a60, 0x3a84, 0x3aa8, 0x3acc, 0x3af0, 0x3b14,
+ 0x3b39, 0x3b5d, 0x3b81, 0x3ba6, 0x3bca, 0x3bee, 0x3c13, 0x3c37, 0x3c5c,
+ 0x3c81, 0x3ca5, 0x3cca, 0x3cef, 0x3d14, 0x3d39, 0x3d5e, 0x3d83, 0x3da8,
+ 0x3dcd, 0x3df2, 0x3e17, 0x3e3c, 0x3e61, 0x3e87, 0x3eac, 0x3ed2, 0x3ef7,
+ 0x3f1c, 0x3f42, 0x3f68, 0x3f8d, 0x3fb3, 0x3fd9, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x0},
+ {0x3, 0x0, 0x3, 0x7, 0xa, 0xe, 0x11, 0x15, 0x18, 0x1c, 0x20, 0x23,
+ 0x27, 0x2a, 0x2e, 0x31, 0x35, 0x38, 0x3c, 0x40, 0x43, 0x47, 0x4a, 0x4e,
+ 0x51, 0x55, 0x58, 0x5c, 0x60, 0x63, 0x67, 0x6a, 0x6e, 0x71, 0x75, 0x78,
+ 0x7c, 0x80, 0x83, 0x87, 0x8a, 0x8e, 0x91, 0x95, 0x99, 0x9c, 0xa0, 0xa3,
+ 0xa7, 0xaa, 0xae, 0xb1, 0xb5, 0xb9, 0xbc, 0xc0, 0xc3, 0xc7, 0xca, 0xce,
+ 0xd1, 0xd5, 0xd9, 0xdc, 0xe0, 0xe3, 0xe7, 0xea, 0xee, 0xf1, 0xf5, 0xf9,
+ 0xfc, 0x100, 0x103, 0x107, 0x10a, 0x10e, 0x112, 0x115, 0x119, 0x11c,
+ 0x120, 0x123, 0x126, 0x12a, 0x12d, 0x131, 0x134, 0x138, 0x13c, 0x13f,
+ 0x143, 0x147, 0x14b, 0x14e, 0x152, 0x156, 0x15a, 0x15e, 0x162, 0x166,
+ 0x16a, 0x16e, 0x172, 0x176, 0x17a, 0x17e, 0x182, 0x186, 0x18a, 0x18f,
+ 0x193, 0x197, 0x19b, 0x1a0, 0x1a4, 0x1a8, 0x1ad, 0x1b1, 0x1b5, 0x1ba,
+ 0x1be, 0x1c3, 0x1c7, 0x1cc, 0x1d0, 0x1d5, 0x1d9, 0x1de, 0x1e3, 0x1e7,
+ 0x1ec, 0x1f1, 0x1f6, 0x1fa, 0x1ff, 0x204, 0x209, 0x20e, 0x213, 0x217,
+ 0x21c, 0x221, 0x226, 0x22b, 0x230, 0x236, 0x23b, 0x240, 0x245, 0x24a,
+ 0x24f, 0x255, 0x25a, 0x25f, 0x264, 0x26a, 0x26f, 0x274, 0x27a, 0x27f,
+ 0x285, 0x28a, 0x290, 0x295, 0x29b, 0x2a0, 0x2a6, 0x2ac, 0x2b1, 0x2b7,
+ 0x2bd, 0x2c2, 0x2c8, 0x2ce, 0x2d4, 0x2da, 0x2df, 0x2e5, 0x2eb, 0x2f1,
+ 0x2f7, 0x2fd, 0x303, 0x309, 0x30f, 0x315, 0x31c, 0x322, 0x328, 0x32e,
+ 0x334, 0x33b, 0x341, 0x347, 0x34d, 0x354, 0x35a, 0x361, 0x367, 0x36d,
+ 0x374, 0x37a, 0x381, 0x388, 0x38e, 0x395, 0x39b, 0x3a2, 0x3a9, 0x3b0,
+ 0x3b6, 0x3bd, 0x3c4, 0x3cb, 0x3d2, 0x3d8, 0x3df, 0x3e6, 0x3ed, 0x3f4,
+ 0x3fb, 0x402, 0x409, 0x411, 0x418, 0x41f, 0x426, 0x42d, 0x434, 0x43c,
+ 0x443, 0x44a, 0x452, 0x459, 0x460, 0x468, 0x46f, 0x477, 0x47e, 0x486,
+ 0x48d, 0x495, 0x49c, 0x4a4, 0x4ac, 0x4b3, 0x4bb, 0x4c3, 0x4cb, 0x4d3,
+ 0x4da, 0x4e2, 0x4ea, 0x4f2, 0x4fa, 0x502, 0x50a, 0x512, 0x51a, 0x522,
+ 0x52a, 0x532, 0x53a, 0x543, 0x54b, 0x553, 0x55b, 0x564, 0x56c, 0x574,
+ 0x57d, 0x585, 0x58d, 0x596, 0x59e, 0x5a7, 0x5af, 0x5b8, 0x5c1, 0x5c9,
+ 0x5d2, 0x5db, 0x5e3, 0x5ec, 0x5f5, 0x5fe, 0x606, 0x60f, 0x618, 0x621,
+ 0x62a, 0x633, 0x63c, 0x645, 0x64e, 0x657, 0x660, 0x669, 0x672, 0x67b,
+ 0x685, 0x68e, 0x697, 0x6a0, 0x6aa, 0x6b3, 0x6bd, 0x6c6, 0x6cf, 0x6d9,
+ 0x6e2, 0x6ec, 0x6f5, 0x6ff, 0x709, 0x712, 0x71c, 0x726, 0x72f, 0x739,
+ 0x743, 0x74d, 0x756, 0x760, 0x76a, 0x774, 0x77e, 0x788, 0x792, 0x79c,
+ 0x7a6, 0x7b0, 0x7ba, 0x7c4, 0x7cf, 0x7d9, 0x7e3, 0x7ed, 0x7f7, 0x802,
+ 0x80c, 0x816, 0x821, 0x82b, 0x836, 0x840, 0x84b, 0x855, 0x860, 0x86a,
+ 0x875, 0x880, 0x88a, 0x895, 0x8a0, 0x8ab, 0x8b5, 0x8c0, 0x8cb, 0x8d6,
+ 0x8e1, 0x8ec, 0x8f7, 0x902, 0x90d, 0x918, 0x923, 0x92e, 0x939, 0x944,
+ 0x950, 0x95b, 0x966, 0x971, 0x97d, 0x988, 0x993, 0x99f, 0x9aa, 0x9b6,
+ 0x9c1, 0x9cd, 0x9d8, 0x9e4, 0x9f0, 0x9fb, 0xa07, 0xa13, 0xa1e, 0xa2a,
+ 0xa36, 0xa42, 0xa4e, 0xa59, 0xa65, 0xa71, 0xa7d, 0xa89, 0xa95, 0xaa1,
+ 0xaad, 0xab9, 0xac6, 0xad2, 0xade, 0xaea, 0xaf6, 0xb03, 0xb0f, 0xb1b,
+ 0xb28, 0xb34, 0xb41, 0xb4d, 0xb5a, 0xb66, 0xb73, 0xb7f, 0xb8c, 0xb98,
+ 0xba5, 0xbb2, 0xbbf, 0xbcb, 0xbd8, 0xbe5, 0xbf2, 0xbff, 0xc0c, 0xc19,
+ 0xc25, 0xc32, 0xc40, 0xc4d, 0xc5a, 0xc67, 0xc74, 0xc81, 0xc8e, 0xc9c,
+ 0xca9, 0xcb6, 0xcc3, 0xcd1, 0xcde, 0xcec, 0xcf9, 0xd07, 0xd14, 0xd22,
+ 0xd2f, 0xd3d, 0xd4a, 0xd58, 0xd66, 0xd73, 0xd81, 0xd8f, 0xd9d, 0xdab,
+ 0xdb8, 0xdc6, 0xdd4, 0xde2, 0xdf0, 0xdfe, 0xe0c, 0xe1a, 0xe29, 0xe37,
+ 0xe45, 0xe53, 0xe61, 0xe70, 0xe7e, 0xe8c, 0xe9a, 0xea9, 0xeb7, 0xec6,
+ 0xed4, 0xee3, 0xef1, 0xf00, 0xf0e, 0xf1d, 0xf2c, 0xf3a, 0xf49, 0xf58,
+ 0xf67, 0xf75, 0xf84, 0xf93, 0xfa2, 0xfb1, 0xfc0, 0xfcf, 0xfde, 0xfed,
+ 0xffc, 0x100b, 0x101a, 0x102a, 0x1039, 0x1048, 0x1057, 0x1067, 0x1076,
+ 0x1085, 0x1095, 0x10a4, 0x10b4, 0x10c3, 0x10d3, 0x10e2, 0x10f2, 0x1101,
+ 0x1111, 0x1121, 0x1130, 0x1140, 0x1150, 0x1160, 0x116f, 0x117f, 0x118f,
+ 0x119f, 0x11af, 0x11bf, 0x11cf, 0x11df, 0x11ef, 0x11ff, 0x120f, 0x121f,
+ 0x1230, 0x1240, 0x1250, 0x1260, 0x1271, 0x1281, 0x1291, 0x12a2, 0x12b2,
+ 0x12c3, 0x12d3, 0x12e4, 0x12f4, 0x1305, 0x1316, 0x1326, 0x1337, 0x1348,
+ 0x1359, 0x1369, 0x137a, 0x138b, 0x139c, 0x13ad, 0x13be, 0x13cf, 0x13e0,
+ 0x13f1, 0x1402, 0x1413, 0x1424, 0x1435, 0x1446, 0x1458, 0x1469, 0x147a,
+ 0x148b, 0x149d, 0x14ae, 0x14c0, 0x14d1, 0x14e3, 0x14f4, 0x1506, 0x1517,
+ 0x1529, 0x153a, 0x154c, 0x155e, 0x156f, 0x1581, 0x1593, 0x15a5, 0x15b7,
+ 0x15c9, 0x15db, 0x15ec, 0x15fe, 0x1610, 0x1623, 0x1635, 0x1647, 0x1659,
+ 0x166b, 0x167d, 0x168f, 0x16a2, 0x16b4, 0x16c6, 0x16d9, 0x16eb, 0x16fe,
+ 0x1710, 0x1722, 0x1735, 0x1748, 0x175a, 0x176d, 0x177f, 0x1792, 0x17a5,
+ 0x17b8, 0x17ca, 0x17dd, 0x17f0, 0x1803, 0x1816, 0x1829, 0x183c, 0x184f,
+ 0x1862, 0x1875, 0x1888, 0x189b, 0x18ae, 0x18c1, 0x18d5, 0x18e8, 0x18fb,
+ 0x190e, 0x1922, 0x1935, 0x1949, 0x195c, 0x196f, 0x1983, 0x1996, 0x19aa,
+ 0x19be, 0x19d1, 0x19e5, 0x19f9, 0x1a0c, 0x1a20, 0x1a34, 0x1a48, 0x1a5c,
+ 0x1a70, 0x1a84, 0x1a97, 0x1aab, 0x1ac0, 0x1ad4, 0x1ae8, 0x1afc, 0x1b10,
+ 0x1b24, 0x1b38, 0x1b4d, 0x1b61, 0x1b75, 0x1b8a, 0x1b9e, 0x1bb2, 0x1bc7,
+ 0x1bdb, 0x1bf0, 0x1c04, 0x1c19, 0x1c2e, 0x1c42, 0x1c57, 0x1c6c, 0x1c80,
+ 0x1c95, 0x1caa, 0x1cbf, 0x1cd4, 0x1ce8, 0x1cfd, 0x1d12, 0x1d27, 0x1d3c,
+ 0x1d51, 0x1d67, 0x1d7c, 0x1d91, 0x1da6, 0x1dbb, 0x1dd1, 0x1de6, 0x1dfb,
+ 0x1e10, 0x1e26, 0x1e3b, 0x1e51, 0x1e66, 0x1e7c, 0x1e91, 0x1ea7, 0x1ebd,
+ 0x1ed2, 0x1ee8, 0x1efe, 0x1f13, 0x1f29, 0x1f3f, 0x1f55, 0x1f6b, 0x1f81,
+ 0x1f96, 0x1fac, 0x1fc2, 0x1fd9, 0x1fef, 0x2005, 0x201b, 0x2031, 0x2047,
+ 0x205d, 0x2074, 0x208a, 0x20a0, 0x20b7, 0x20cd, 0x20e4, 0x20fa, 0x2111,
+ 0x2127, 0x213e, 0x2154, 0x216b, 0x2182, 0x2198, 0x21af, 0x21c6, 0x21dd,
+ 0x21f3, 0x220a, 0x2221, 0x2238, 0x224f, 0x2266, 0x227d, 0x2294, 0x22ab,
+ 0x22c2, 0x22da, 0x22f1, 0x2308, 0x231f, 0x2337, 0x234e, 0x2365, 0x237d,
+ 0x2394, 0x23ac, 0x23c3, 0x23db, 0x23f2, 0x240a, 0x2421, 0x2439, 0x2451,
+ 0x2469, 0x2480, 0x2498, 0x24b0, 0x24c8, 0x24e0, 0x24f8, 0x2510, 0x2528,
+ 0x2540, 0x2558, 0x2570, 0x2588, 0x25a0, 0x25b8, 0x25d0, 0x25e9, 0x2601,
+ 0x2619, 0x2632, 0x264a, 0x2663, 0x267b, 0x2693, 0x26ac, 0x26c5, 0x26dd,
+ 0x26f6, 0x270e, 0x2727, 0x2740, 0x2759, 0x2771, 0x278a, 0x27a3, 0x27bc,
+ 0x27d5, 0x27ee, 0x2807, 0x2820, 0x2839, 0x2852, 0x286b, 0x2884, 0x289e,
+ 0x28b7, 0x28d0, 0x28e9, 0x2903, 0x291c, 0x2936, 0x294f, 0x2968, 0x2982,
+ 0x299c, 0x29b5, 0x29cf, 0x29e8, 0x2a02, 0x2a1c, 0x2a35, 0x2a4f, 0x2a69,
+ 0x2a83, 0x2a9d, 0x2ab7, 0x2ad1, 0x2aeb, 0x2b05, 0x2b1f, 0x2b39, 0x2b53,
+ 0x2b6d, 0x2b87, 0x2ba1, 0x2bbc, 0x2bd6, 0x2bf0, 0x2c0b, 0x2c25, 0x2c3f,
+ 0x2c5a, 0x2c74, 0x2c8f, 0x2ca9, 0x2cc4, 0x2cdf, 0x2cf9, 0x2d14, 0x2d2f,
+ 0x2d49, 0x2d64, 0x2d7f, 0x2d9a, 0x2db5, 0x2dd0, 0x2deb, 0x2e06, 0x2e21,
+ 0x2e3c, 0x2e57, 0x2e72, 0x2e8d, 0x2ea8, 0x2ec4, 0x2edf, 0x2efa, 0x2f16,
+ 0x2f31, 0x2f4c, 0x2f68, 0x2f83, 0x2f9f, 0x2fba, 0x2fd6, 0x2ff1, 0x300d,
+ 0x3029, 0x3044, 0x3060, 0x307c, 0x3098, 0x30b4, 0x30d0, 0x30eb, 0x3107,
+ 0x3123, 0x313f, 0x315b, 0x3178, 0x3194, 0x31b0, 0x31cc, 0x31e8, 0x3205,
+ 0x3221, 0x323d, 0x325a, 0x3276, 0x3292, 0x32af, 0x32cb, 0x32e8, 0x3304,
+ 0x3321, 0x333e, 0x335a, 0x3377, 0x3394, 0x33b1, 0x33cd, 0x33ea, 0x3407,
+ 0x3424, 0x3441, 0x345e, 0x347b, 0x3498, 0x34b5, 0x34d2, 0x34ef, 0x350d,
+ 0x352a, 0x3547, 0x3564, 0x3582, 0x359f, 0x35bc, 0x35da, 0x35f7, 0x3615,
+ 0x3632, 0x3650, 0x366e, 0x368b, 0x36a9, 0x36c7, 0x36e4, 0x3702, 0x3720,
+ 0x373e, 0x375c, 0x377a, 0x3798, 0x37b6, 0x37d4, 0x37f2, 0x3810, 0x382e,
+ 0x384c, 0x386a, 0x3888, 0x38a7, 0x38c5, 0x38e3, 0x3902, 0x3920, 0x393f,
+ 0x395d, 0x397c, 0x399a, 0x39b9, 0x39d7, 0x39f6, 0x3a15, 0x3a33, 0x3a52,
+ 0x3a71, 0x3a90, 0x3aaf, 0x3acd, 0x3aec, 0x3b0b, 0x3b2a, 0x3b49, 0x3b68,
+ 0x3b87, 0x3ba7, 0x3bc6, 0x3be5, 0x3c04, 0x3c24, 0x3c43, 0x3c62, 0x3c82,
+ 0x3ca1, 0x3cc0, 0x3ce0, 0x3cff, 0x3d1f, 0x3d3f, 0x3d5e, 0x3d7e, 0x3d9e,
+ 0x3dbd, 0x3ddd, 0x3dfd, 0x3e1d, 0x3e3d, 0x3e5d, 0x3e7c, 0x3e9c, 0x3ebc,
+ 0x3edc, 0x3efd, 0x3f1d, 0x3f3d, 0x3f5d, 0x3f7d, 0x3f9e, 0x3fbe, 0x3fde,
+ 0x3fff, 0x0},
+ {0x3, 0x0, 0xfa0, 0x13a2, 0x160c, 0x17a3, 0x18d8, 0x1a0c, 0x1ac9,
+ 0x1ba3, 0x1c4d, 0x1cd8, 0x1d70, 0x1e0c, 0x1e67, 0x1ec9, 0x1f33, 0x1fa3,
+ 0x200e, 0x204d, 0x2091, 0x20d8, 0x2122, 0x2170, 0x21c2, 0x220c, 0x2238,
+ 0x2267, 0x2297, 0x22c9, 0x22fd, 0x2333, 0x236a, 0x23a3, 0x23df, 0x240e,
+ 0x242d, 0x244d, 0x246e, 0x2491, 0x24b4, 0x24d8, 0x24fc, 0x2522, 0x2549,
+ 0x2570, 0x2599, 0x25c2, 0x25ed, 0x260c, 0x2622, 0x2638, 0x264f, 0x2667,
+ 0x267f, 0x2697, 0x26b0, 0x26c9, 0x26e3, 0x26fd, 0x2717, 0x2733, 0x274e,
+ 0x276a, 0x2787, 0x27a3, 0x27c1, 0x27df, 0x27fd, 0x280e, 0x281d, 0x282d,
+ 0x283d, 0x284d, 0x285e, 0x286e, 0x287f, 0x2891, 0x28a2, 0x28b4, 0x28c5,
+ 0x28d8, 0x28ea, 0x28fc, 0x290f, 0x2922, 0x2935, 0x2949, 0x295d, 0x2970,
+ 0x2985, 0x2999, 0x29ae, 0x29c2, 0x29d7, 0x29ed, 0x2a01, 0x2a0c, 0x2a17,
+ 0x2a22, 0x2a2d, 0x2a38, 0x2a44, 0x2a4f, 0x2a5b, 0x2a67, 0x2a73, 0x2a7f,
+ 0x2a8b, 0x2a97, 0x2aa3, 0x2ab0, 0x2abc, 0x2ac9, 0x2ad6, 0x2ae3, 0x2af0,
+ 0x2afd, 0x2b0a, 0x2b17, 0x2b25, 0x2b33, 0x2b40, 0x2b4e, 0x2b5c, 0x2b6a,
+ 0x2b78, 0x2b87, 0x2b95, 0x2ba3, 0x2bb2, 0x2bc1, 0x2bd0, 0x2bdf, 0x2bee,
+ 0x2bfd, 0x2c06, 0x2c0e, 0x2c15, 0x2c1d, 0x2c25, 0x2c2d, 0x2c35, 0x2c3d,
+ 0x2c45, 0x2c4d, 0x2c55, 0x2c5e, 0x2c66, 0x2c6e, 0x2c77, 0x2c7f, 0x2c88,
+ 0x2c91, 0x2c99, 0x2ca2, 0x2cab, 0x2cb4, 0x2cbd, 0x2cc5, 0x2cce, 0x2cd8,
+ 0x2ce1, 0x2cea, 0x2cf3, 0x2cfc, 0x2d06, 0x2d0f, 0x2d19, 0x2d22, 0x2d2c,
+ 0x2d35, 0x2d3f, 0x2d49, 0x2d53, 0x2d5d, 0x2d66, 0x2d70, 0x2d7a, 0x2d85,
+ 0x2d8f, 0x2d99, 0x2da3, 0x2dae, 0x2db8, 0x2dc2, 0x2dcd, 0x2dd7, 0x2de2,
+ 0x2ded, 0x2df7, 0x2e01, 0x2e06, 0x2e0c, 0x2e11, 0x2e17, 0x2e1c, 0x2e22,
+ 0x2e27, 0x2e2d, 0x2e33, 0x2e38, 0x2e3e, 0x2e44, 0x2e49, 0x2e4f, 0x2e55,
+ 0x2e5b, 0x2e61, 0x2e67, 0x2e6d, 0x2e73, 0x2e79, 0x2e7f, 0x2e85, 0x2e8b,
+ 0x2e91, 0x2e97, 0x2e9d, 0x2ea3, 0x2eaa, 0x2eb0, 0x2eb6, 0x2ebc, 0x2ec3,
+ 0x2ec9, 0x2ecf, 0x2ed6, 0x2edc, 0x2ee3, 0x2ee9, 0x2ef0, 0x2ef6, 0x2efd,
+ 0x2f03, 0x2f0a, 0x2f11, 0x2f17, 0x2f1e, 0x2f25, 0x2f2c, 0x2f33, 0x2f39,
+ 0x2f40, 0x2f47, 0x2f4e, 0x2f55, 0x2f5c, 0x2f63, 0x2f6a, 0x2f71, 0x2f78,
+ 0x2f7f, 0x2f87, 0x2f8e, 0x2f95, 0x2f9c, 0x2fa3, 0x2fab, 0x2fb2, 0x2fb9,
+ 0x2fc1, 0x2fc8, 0x2fd0, 0x2fd7, 0x2fdf, 0x2fe6, 0x2fee, 0x2ff5, 0x2ffd,
+ 0x3002, 0x3006, 0x300a, 0x300e, 0x3011, 0x3015, 0x3019, 0x301d, 0x3021,
+ 0x3025, 0x3029, 0x302d, 0x3031, 0x3035, 0x3039, 0x303d, 0x3041, 0x3045,
+ 0x3049, 0x304d, 0x3051, 0x3055, 0x305a, 0x305e, 0x3062, 0x3066, 0x306a,
+ 0x306e, 0x3073, 0x3077, 0x307b, 0x307f, 0x3084, 0x3088, 0x308c, 0x3091,
+ 0x3095, 0x3099, 0x309e, 0x30a2, 0x30a6, 0x30ab, 0x30af, 0x30b4, 0x30b8,
+ 0x30bd, 0x30c1, 0x30c5, 0x30ca, 0x30ce, 0x30d3, 0x30d8, 0x30dc, 0x30e1,
+ 0x30e5, 0x30ea, 0x30ee, 0x30f3, 0x30f8, 0x30fc, 0x3101, 0x3106, 0x310a,
+ 0x310f, 0x3114, 0x3119, 0x311d, 0x3122, 0x3127, 0x312c, 0x3131, 0x3135,
+ 0x313a, 0x313f, 0x3144, 0x3149, 0x314e, 0x3153, 0x3158, 0x315d, 0x3161,
+ 0x3166, 0x316b, 0x3170, 0x3175, 0x317a, 0x3180, 0x3185, 0x318a, 0x318f,
+ 0x3194, 0x3199, 0x319e, 0x31a3, 0x31a8, 0x31ae, 0x31b3, 0x31b8, 0x31bd,
+ 0x31c2, 0x31c8, 0x31cd, 0x31d2, 0x31d7, 0x31dd, 0x31e2, 0x31e7, 0x31ed,
+ 0x31f2, 0x31f7, 0x31fd, 0x3201, 0x3204, 0x3206, 0x3209, 0x320c, 0x320e,
+ 0x3211, 0x3214, 0x3217, 0x3219, 0x321c, 0x321f, 0x3222, 0x3225, 0x3227,
+ 0x322a, 0x322d, 0x3230, 0x3233, 0x3235, 0x3238, 0x323b, 0x323e, 0x3241,
+ 0x3244, 0x3247, 0x3249, 0x324c, 0x324f, 0x3252, 0x3255, 0x3258, 0x325b,
+ 0x325e, 0x3261, 0x3264, 0x3267, 0x326a, 0x326d, 0x3270, 0x3273, 0x3276,
+ 0x3279, 0x327c, 0x327f, 0x3282, 0x3285, 0x3288, 0x328b, 0x328e, 0x3291,
+ 0x3294, 0x3297, 0x329a, 0x329d, 0x32a0, 0x32a3, 0x32a6, 0x32aa, 0x32ad,
+ 0x32b0, 0x32b3, 0x32b6, 0x32b9, 0x32bd, 0x32c0, 0x32c3, 0x32c6, 0x32ca,
+ 0x32cd, 0x32d0, 0x32d4, 0x32d7, 0x32db, 0x32de, 0x32e1, 0x32e5, 0x32e8,
+ 0x32ec, 0x32ef, 0x32f3, 0x32f7, 0x32fa, 0x32fe, 0x3302, 0x3305, 0x3309,
+ 0x330d, 0x3310, 0x3314, 0x3318, 0x331c, 0x3320, 0x3324, 0x3328, 0x332b,
+ 0x332f, 0x3333, 0x3337, 0x333b, 0x3340, 0x3344, 0x3348, 0x334c, 0x3350,
+ 0x3354, 0x3358, 0x335d, 0x3361, 0x3365, 0x336a, 0x336e, 0x3372, 0x3377,
+ 0x337b, 0x3380, 0x3384, 0x3389, 0x338d, 0x3392, 0x3396, 0x339b, 0x33a0,
+ 0x33a4, 0x33a9, 0x33ae, 0x33b3, 0x33b7, 0x33bc, 0x33c1, 0x33c6, 0x33cb,
+ 0x33d0, 0x33d5, 0x33da, 0x33df, 0x33e4, 0x33e9, 0x33ef, 0x33f4, 0x33f9,
+ 0x33fe, 0x3402, 0x3404, 0x3407, 0x340a, 0x340c, 0x340f, 0x3412, 0x3415,
+ 0x3417, 0x341a, 0x341d, 0x3420, 0x3423, 0x3426, 0x3429, 0x342b, 0x342e,
+ 0x3431, 0x3434, 0x3437, 0x343a, 0x343d, 0x3440, 0x3444, 0x3447, 0x344a,
+ 0x344d, 0x3450, 0x3453, 0x3456, 0x345a, 0x345d, 0x3460, 0x3463, 0x3467,
+ 0x346a, 0x346d, 0x3471, 0x3474, 0x3477, 0x347b, 0x347e, 0x3482, 0x3485,
+ 0x3489, 0x348c, 0x3490, 0x3493, 0x3497, 0x349b, 0x349e, 0x34a2, 0x34a6,
+ 0x34a9, 0x34ad, 0x34b1, 0x34b5, 0x34b9, 0x34bc, 0x34c0, 0x34c4, 0x34c8,
+ 0x34cc, 0x34d0, 0x34d4, 0x34d8, 0x34dc, 0x34e0, 0x34e4, 0x34e8, 0x34ec,
+ 0x34f1, 0x34f5, 0x34f9, 0x34fd, 0x3502, 0x3506, 0x350a, 0x350f, 0x3513,
+ 0x3517, 0x351c, 0x3520, 0x3525, 0x3529, 0x352e, 0x3533, 0x3537, 0x353c,
+ 0x3541, 0x3545, 0x354a, 0x354f, 0x3554, 0x3558, 0x355d, 0x3562, 0x3567,
+ 0x356c, 0x3571, 0x3576, 0x357b, 0x3580, 0x3585, 0x358a, 0x3590, 0x3595,
+ 0x359a, 0x359f, 0x35a5, 0x35aa, 0x35b0, 0x35b5, 0x35ba, 0x35c0, 0x35c5,
+ 0x35cb, 0x35d1, 0x35d6, 0x35dc, 0x35e2, 0x35e7, 0x35ed, 0x35f3, 0x35f9,
+ 0x35ff, 0x3602, 0x3605, 0x3608, 0x360b, 0x360e, 0x3611, 0x3614, 0x3617,
+ 0x361a, 0x361e, 0x3621, 0x3624, 0x3627, 0x362a, 0x362e, 0x3631, 0x3634,
+ 0x3637, 0x363b, 0x363e, 0x3642, 0x3645, 0x3648, 0x364c, 0x364f, 0x3653,
+ 0x3656, 0x365a, 0x365d, 0x3661, 0x3664, 0x3668, 0x366c, 0x366f, 0x3673,
+ 0x3677, 0x367a, 0x367e, 0x3682, 0x3686, 0x368a, 0x368d, 0x3691, 0x3695,
+ 0x3699, 0x369d, 0x36a1, 0x36a5, 0x36a9, 0x36ad, 0x36b1, 0x36b5, 0x36ba,
+ 0x36be, 0x36c2, 0x36c6, 0x36ca, 0x36cf, 0x36d3, 0x36d7, 0x36dc, 0x36e0,
+ 0x36e4, 0x36e9, 0x36ed, 0x36f2, 0x36f6, 0x36fb, 0x36ff, 0x3704, 0x3709,
+ 0x370d, 0x3712, 0x3717, 0x371b, 0x3720, 0x3725, 0x372a, 0x372f, 0x3734,
+ 0x3739, 0x373e, 0x3743, 0x3748, 0x374d, 0x3752, 0x3757, 0x375c, 0x3761,
+ 0x3767, 0x376c, 0x3771, 0x3776, 0x377c, 0x3781, 0x3787, 0x378c, 0x3792,
+ 0x3797, 0x379d, 0x37a2, 0x37a8, 0x37ae, 0x37b3, 0x37b9, 0x37bf, 0x37c5,
+ 0x37cb, 0x37d1, 0x37d7, 0x37dd, 0x37e3, 0x37e9, 0x37ef, 0x37f5, 0x37fb,
+ 0x3800, 0x3804, 0x3807, 0x380a, 0x380d, 0x3810, 0x3813, 0x3817, 0x381a,
+ 0x381d, 0x3821, 0x3824, 0x3827, 0x382b, 0x382e, 0x3831, 0x3835, 0x3838,
+ 0x383c, 0x383f, 0x3843, 0x3846, 0x384a, 0x384e, 0x3851, 0x3855, 0x3859,
+ 0x385c, 0x3860, 0x3864, 0x3867, 0x386b, 0x386f, 0x3873, 0x3877, 0x387b,
+ 0x387f, 0x3883, 0x3887, 0x388a, 0x388f, 0x3893, 0x3897, 0x389b, 0x389f,
+ 0x38a3, 0x38a7, 0x38ab, 0x38b0, 0x38b4, 0x38b8, 0x38bc, 0x38c1, 0x38c5,
+ 0x38c9, 0x38ce, 0x38d2, 0x38d7, 0x38db, 0x38e0, 0x38e4, 0x38e9, 0x38ee,
+ 0x38f2, 0x38f7, 0x38fc, 0x3900, 0x3905, 0x390a, 0x390f, 0x3914, 0x3919,
+ 0x391d, 0x3922, 0x3927, 0x392c, 0x3931, 0x3937, 0x393c, 0x3941, 0x3946,
+ 0x394b, 0x3950, 0x3956, 0x395b, 0x3960, 0x3966, 0x396b, 0x3971, 0x3976,
+ 0x397c, 0x3981, 0x3987, 0x398c, 0x3992, 0x3998, 0x399e, 0x39a3, 0x39a9,
+ 0x39af, 0x39b5, 0x39bb, 0x39c1, 0x39c7, 0x39cd, 0x39d3, 0x39d9, 0x39df,
+ 0x39e5, 0x39ec, 0x39f2, 0x39f8, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x3},
+ {0x3, 0x0, 0xea8, 0x12aa, 0x1500, 0x16ab, 0x1815, 0x1900, 0x1a0b,
+ 0x1aab, 0x1b60, 0x1c15, 0x1c85, 0x1d00, 0x1d86, 0x1e0b, 0x1e58, 0x1eab,
+ 0x1f03, 0x1f60, 0x1fc3, 0x2015, 0x204c, 0x2085, 0x20c2, 0x2100, 0x2142,
+ 0x2186, 0x21cc, 0x220b, 0x2231, 0x2258, 0x2281, 0x22ab, 0x22d6, 0x2303,
+ 0x2331, 0x2360, 0x2391, 0x23c3, 0x23f6, 0x2415, 0x2430, 0x244c, 0x2468,
+ 0x2485, 0x24a3, 0x24c2, 0x24e1, 0x2500, 0x2521, 0x2542, 0x2563, 0x2586,
+ 0x25a9, 0x25cc, 0x25f1, 0x260b, 0x261e, 0x2631, 0x2644, 0x2658, 0x266c,
+ 0x2681, 0x2696, 0x26ab, 0x26c0, 0x26d6, 0x26ec, 0x2703, 0x271a, 0x2731,
+ 0x2748, 0x2760, 0x2779, 0x2791, 0x27aa, 0x27c3, 0x27dd, 0x27f6, 0x2808,
+ 0x2815, 0x2823, 0x2830, 0x283e, 0x284c, 0x285a, 0x2868, 0x2877, 0x2885,
+ 0x2894, 0x28a3, 0x28b2, 0x28c2, 0x28d1, 0x28e1, 0x28f0, 0x2900, 0x2910,
+ 0x2921, 0x2931, 0x2942, 0x2952, 0x2963, 0x2974, 0x2986, 0x2997, 0x29a9,
+ 0x29bb, 0x29cc, 0x29df, 0x29f1, 0x2a01, 0x2a0b, 0x2a14, 0x2a1e, 0x2a27,
+ 0x2a31, 0x2a3a, 0x2a44, 0x2a4e, 0x2a58, 0x2a62, 0x2a6c, 0x2a76, 0x2a81,
+ 0x2a8b, 0x2a96, 0x2aa0, 0x2aab, 0x2ab6, 0x2ac0, 0x2acb, 0x2ad6, 0x2ae1,
+ 0x2aec, 0x2af8, 0x2b03, 0x2b0e, 0x2b1a, 0x2b25, 0x2b31, 0x2b3d, 0x2b48,
+ 0x2b54, 0x2b60, 0x2b6c, 0x2b79, 0x2b85, 0x2b91, 0x2b9d, 0x2baa, 0x2bb6,
+ 0x2bc3, 0x2bd0, 0x2bdd, 0x2bea, 0x2bf6, 0x2c02, 0x2c08, 0x2c0f, 0x2c15,
+ 0x2c1c, 0x2c23, 0x2c2a, 0x2c30, 0x2c37, 0x2c3e, 0x2c45, 0x2c4c, 0x2c53,
+ 0x2c5a, 0x2c61, 0x2c68, 0x2c70, 0x2c77, 0x2c7e, 0x2c85, 0x2c8d, 0x2c94,
+ 0x2c9c, 0x2ca3, 0x2cab, 0x2cb2, 0x2cba, 0x2cc2, 0x2cc9, 0x2cd1, 0x2cd9,
+ 0x2ce1, 0x2ce8, 0x2cf0, 0x2cf8, 0x2d00, 0x2d08, 0x2d10, 0x2d18, 0x2d21,
+ 0x2d29, 0x2d31, 0x2d39, 0x2d42, 0x2d4a, 0x2d52, 0x2d5b, 0x2d63, 0x2d6c,
+ 0x2d74, 0x2d7d, 0x2d86, 0x2d8e, 0x2d97, 0x2da0, 0x2da9, 0x2db2, 0x2dbb,
+ 0x2dc3, 0x2dcc, 0x2dd5, 0x2ddf, 0x2de8, 0x2df1, 0x2dfa, 0x2e01, 0x2e06,
+ 0x2e0b, 0x2e0f, 0x2e14, 0x2e19, 0x2e1e, 0x2e22, 0x2e27, 0x2e2c, 0x2e31,
+ 0x2e36, 0x2e3a, 0x2e3f, 0x2e44, 0x2e49, 0x2e4e, 0x2e53, 0x2e58, 0x2e5d,
+ 0x2e62, 0x2e67, 0x2e6c, 0x2e71, 0x2e76, 0x2e7c, 0x2e81, 0x2e86, 0x2e8b,
+ 0x2e90, 0x2e96, 0x2e9b, 0x2ea0, 0x2ea6, 0x2eab, 0x2eb0, 0x2eb6, 0x2ebb,
+ 0x2ec0, 0x2ec6, 0x2ecb, 0x2ed1, 0x2ed6, 0x2edc, 0x2ee1, 0x2ee7, 0x2eec,
+ 0x2ef2, 0x2ef8, 0x2efd, 0x2f03, 0x2f09, 0x2f0e, 0x2f14, 0x2f1a, 0x2f20,
+ 0x2f25, 0x2f2b, 0x2f31, 0x2f37, 0x2f3d, 0x2f43, 0x2f48, 0x2f4e, 0x2f54,
+ 0x2f5a, 0x2f60, 0x2f66, 0x2f6c, 0x2f72, 0x2f79, 0x2f7f, 0x2f85, 0x2f8b,
+ 0x2f91, 0x2f97, 0x2f9d, 0x2fa4, 0x2faa, 0x2fb0, 0x2fb6, 0x2fbd, 0x2fc3,
+ 0x2fc9, 0x2fd0, 0x2fd6, 0x2fdd, 0x2fe3, 0x2fea, 0x2ff0, 0x2ff6, 0x2ffd,
+ 0x3002, 0x3005, 0x3008, 0x300b, 0x300f, 0x3012, 0x3015, 0x3019, 0x301c,
+ 0x301f, 0x3023, 0x3026, 0x302a, 0x302d, 0x3030, 0x3034, 0x3037, 0x303b,
+ 0x303e, 0x3042, 0x3045, 0x3049, 0x304c, 0x3050, 0x3053, 0x3057, 0x305a,
+ 0x305e, 0x3061, 0x3065, 0x3068, 0x306c, 0x3070, 0x3073, 0x3077, 0x307b,
+ 0x307e, 0x3082, 0x3085, 0x3089, 0x308d, 0x3091, 0x3094, 0x3098, 0x309c,
+ 0x309f, 0x30a3, 0x30a7, 0x30ab, 0x30ae, 0x30b2, 0x30b6, 0x30ba, 0x30be,
+ 0x30c2, 0x30c5, 0x30c9, 0x30cd, 0x30d1, 0x30d5, 0x30d9, 0x30dd, 0x30e1,
+ 0x30e4, 0x30e8, 0x30ec, 0x30f0, 0x30f4, 0x30f8, 0x30fc, 0x3100, 0x3104,
+ 0x3108, 0x310c, 0x3110, 0x3114, 0x3118, 0x311d, 0x3121, 0x3125, 0x3129,
+ 0x312d, 0x3131, 0x3135, 0x3139, 0x313d, 0x3142, 0x3146, 0x314a, 0x314e,
+ 0x3152, 0x3157, 0x315b, 0x315f, 0x3163, 0x3168, 0x316c, 0x3170, 0x3174,
+ 0x3179, 0x317d, 0x3181, 0x3186, 0x318a, 0x318e, 0x3193, 0x3197, 0x319c,
+ 0x31a0, 0x31a4, 0x31a9, 0x31ad, 0x31b2, 0x31b6, 0x31bb, 0x31bf, 0x31c3,
+ 0x31c8, 0x31cc, 0x31d1, 0x31d5, 0x31da, 0x31df, 0x31e3, 0x31e8, 0x31ec,
+ 0x31f1, 0x31f5, 0x31fa, 0x31ff, 0x3201, 0x3204, 0x3206, 0x3208, 0x320b,
+ 0x320d, 0x320f, 0x3212, 0x3214, 0x3216, 0x3219, 0x321b, 0x321e, 0x3220,
+ 0x3222, 0x3225, 0x3227, 0x3229, 0x322c, 0x322e, 0x3231, 0x3233, 0x3236,
+ 0x3238, 0x323a, 0x323d, 0x323f, 0x3242, 0x3244, 0x3247, 0x3249, 0x324c,
+ 0x324e, 0x3251, 0x3253, 0x3256, 0x3258, 0x325b, 0x325d, 0x3260, 0x3262,
+ 0x3265, 0x3267, 0x326a, 0x326c, 0x326f, 0x3271, 0x3274, 0x3276, 0x3279,
+ 0x327c, 0x327e, 0x3281, 0x3283, 0x3286, 0x3289, 0x328b, 0x328e, 0x3290,
+ 0x3293, 0x3296, 0x3298, 0x329b, 0x329e, 0x32a0, 0x32a3, 0x32a6, 0x32a8,
+ 0x32ab, 0x32ae, 0x32b0, 0x32b3, 0x32b6, 0x32b8, 0x32bb, 0x32be, 0x32c1,
+ 0x32c4, 0x32c6, 0x32c9, 0x32cc, 0x32cf, 0x32d2, 0x32d5, 0x32d8, 0x32da,
+ 0x32dd, 0x32e0, 0x32e3, 0x32e6, 0x32e9, 0x32ec, 0x32ef, 0x32f2, 0x32f6,
+ 0x32f9, 0x32fc, 0x32ff, 0x3302, 0x3305, 0x3308, 0x330c, 0x330f, 0x3312,
+ 0x3315, 0x3318, 0x331c, 0x331f, 0x3322, 0x3326, 0x3329, 0x332c, 0x3330,
+ 0x3333, 0x3337, 0x333a, 0x333e, 0x3341, 0x3345, 0x3348, 0x334c, 0x334f,
+ 0x3353, 0x3356, 0x335a, 0x335e, 0x3361, 0x3365, 0x3369, 0x336c, 0x3370,
+ 0x3374, 0x3378, 0x337c, 0x337f, 0x3383, 0x3387, 0x338b, 0x338f, 0x3393,
+ 0x3397, 0x339b, 0x339f, 0x33a3, 0x33a7, 0x33ab, 0x33af, 0x33b3, 0x33b7,
+ 0x33bb, 0x33bf, 0x33c4, 0x33c8, 0x33cc, 0x33d0, 0x33d5, 0x33d9, 0x33dd,
+ 0x33e2, 0x33e6, 0x33eb, 0x33ef, 0x33f3, 0x33f8, 0x33fc, 0x3400, 0x3402,
+ 0x3405, 0x3407, 0x3409, 0x340c, 0x340e, 0x3410, 0x3413, 0x3415, 0x3418,
+ 0x341a, 0x341c, 0x341f, 0x3421, 0x3424, 0x3426, 0x3429, 0x342b, 0x342e,
+ 0x3430, 0x3433, 0x3435, 0x3438, 0x343a, 0x343d, 0x3440, 0x3442, 0x3445,
+ 0x3448, 0x344a, 0x344d, 0x3450, 0x3452, 0x3455, 0x3458, 0x345b, 0x345d,
+ 0x3460, 0x3463, 0x3466, 0x3469, 0x346b, 0x346e, 0x3471, 0x3474, 0x3477,
+ 0x347a, 0x347d, 0x3480, 0x3483, 0x3486, 0x3489, 0x348c, 0x348f, 0x3492,
+ 0x3495, 0x3498, 0x349b, 0x349e, 0x34a2, 0x34a5, 0x34a8, 0x34ab, 0x34ae,
+ 0x34b2, 0x34b5, 0x34b8, 0x34bb, 0x34bf, 0x34c2, 0x34c5, 0x34c9, 0x34cc,
+ 0x34cf, 0x34d3, 0x34d6, 0x34da, 0x34dd, 0x34e1, 0x34e4, 0x34e8, 0x34eb,
+ 0x34ef, 0x34f2, 0x34f6, 0x34fa, 0x34fd, 0x3501, 0x3505, 0x3508, 0x350c,
+ 0x3510, 0x3514, 0x3517, 0x351b, 0x351f, 0x3523, 0x3527, 0x352b, 0x352f,
+ 0x3532, 0x3536, 0x353a, 0x353e, 0x3542, 0x3546, 0x354b, 0x354f, 0x3553,
+ 0x3557, 0x355b, 0x355f, 0x3563, 0x3568, 0x356c, 0x3570, 0x3574, 0x3579,
+ 0x357d, 0x3581, 0x3586, 0x358a, 0x358f, 0x3593, 0x3598, 0x359c, 0x35a1,
+ 0x35a5, 0x35aa, 0x35ae, 0x35b3, 0x35b8, 0x35bc, 0x35c1, 0x35c6, 0x35cb,
+ 0x35cf, 0x35d4, 0x35d9, 0x35de, 0x35e3, 0x35e8, 0x35ed, 0x35f2, 0x35f7,
+ 0x35fc, 0x3600, 0x3603, 0x3605, 0x3608, 0x360a, 0x360d, 0x3610, 0x3612,
+ 0x3615, 0x3618, 0x361a, 0x361d, 0x3620, 0x3622, 0x3625, 0x3628, 0x362b,
+ 0x362d, 0x3630, 0x3633, 0x3636, 0x3639, 0x363b, 0x363e, 0x3641, 0x3644,
+ 0x3647, 0x364a, 0x364d, 0x3650, 0x3653, 0x3656, 0x3659, 0x365c, 0x365f,
+ 0x3662, 0x3665, 0x3668, 0x366b, 0x366e, 0x3672, 0x3675, 0x3678, 0x367b,
+ 0x367e, 0x3682, 0x3685, 0x3688, 0x368b, 0x368f, 0x3692, 0x3695, 0x3699,
+ 0x369c, 0x36a0, 0x36a3, 0x36a6, 0x36aa, 0x36ad, 0x36b1, 0x36b4, 0x36b8,
+ 0x36bb, 0x36bf, 0x36c3, 0x36c6, 0x36ca, 0x36cd, 0x36d1, 0x36d5, 0x36d9,
+ 0x36dc, 0x36e0, 0x36e4, 0x36e8, 0x36eb, 0x36ef, 0x36f3, 0x36f7, 0x36fb,
+ 0x36ff, 0x3703, 0x3707, 0x370b, 0x370f, 0x3713, 0x3717, 0x371b, 0x371f,
+ 0x3723, 0x3727, 0x372b, 0x372f, 0x3734, 0x3738, 0x373c, 0x3740, 0x3745,
+ 0x3749, 0x374d, 0x3752, 0x3756, 0x375b, 0x375f, 0x3764, 0x3768, 0x376d,
+ 0x3771, 0x3776, 0x377a, 0x377f, 0x3783, 0x3788, 0x378d, 0x3792, 0x3796,
+ 0x379b, 0x37a0, 0x37a5, 0x37aa, 0x37ae, 0x37b3, 0x37b8, 0x37bd, 0x37c2,
+ 0x37c7, 0x37cc, 0x37d1, 0x37d6, 0x37dc, 0x37e1, 0x37e6, 0x37eb, 0x37f0,
+ 0x37f6, 0x37fb, 0x3800, 0x3802, 0x3805, 0x3808, 0x380b, 0x380d, 0x3810,
+ 0x3813, 0x3816, 0x3818, 0x381b, 0x381e, 0x3821, 0x3824, 0x3827, 0x382a,
+ 0x382c, 0x382f, 0x3832, 0x3835, 0x3838, 0x383b, 0x383e, 0x3841, 0x3844,
+ 0x3847, 0x384a, 0x384d, 0x3851, 0x3854, 0x3857, 0x385a, 0x385d, 0x3860,
+ 0x3864, 0x3867, 0x386a, 0x386d, 0x3870, 0x3874, 0x3877, 0x387a, 0x387e,
+ 0x3881, 0x3885, 0x3888, 0x388b, 0x388f, 0x3892, 0x3896, 0x3899, 0x389d,
+ 0x38a0, 0x38a4, 0x38a7, 0x38ab, 0x38af, 0x38b2, 0x38b6, 0x38ba, 0x38bd,
+ 0x38c1, 0x38c5, 0x38c8, 0x38cc, 0x38d0, 0x38d4, 0x38d8, 0x38dc, 0x38df,
+ 0x38e3, 0x38e7, 0x38eb, 0x38ef, 0x38f3, 0x38f7, 0x38fb, 0x38ff, 0x3903,
+ 0x3907, 0x390c, 0x3910, 0x3914, 0x3918, 0x391c, 0x3920, 0x3925, 0x3929,
+ 0x392d, 0x3932, 0x3936, 0x393a, 0x393f, 0x3943, 0x3948, 0x394c, 0x3951,
+ 0x3955, 0x395a, 0x395e, 0x3963, 0x3967, 0x396c, 0x3971, 0x3975, 0x397a,
+ 0x397f, 0x3984, 0x3989, 0x398d, 0x3992, 0x3997, 0x399c, 0x39a1, 0x39a6,
+ 0x39ab, 0x39b0, 0x39b5, 0x39ba, 0x39bf, 0x39c4, 0x39c9, 0x39cf, 0x39d4,
+ 0x39d9, 0x39de, 0x39e4, 0x39e9, 0x39ee, 0x39f4, 0x39f9, 0x39ff, 0x3},
+ {0x3, 0x0, 0x1, 0x2, 0x4, 0x5, 0x7, 0x8, 0xa, 0xb, 0xd, 0xe, 0xf, 0x11,
+ 0x12, 0x14, 0x15, 0x17, 0x18, 0x1a, 0x1b, 0x1c, 0x1e, 0x1f, 0x21, 0x22,
+ 0x24, 0x25, 0x27, 0x28, 0x29, 0x2b, 0x2c, 0x2e, 0x2f, 0x31, 0x32, 0x34,
+ 0x35, 0x37, 0x38, 0x3a, 0x3b, 0x3d, 0x3f, 0x40, 0x42, 0x44, 0x45, 0x47,
+ 0x49, 0x4b, 0x4d, 0x4f, 0x51, 0x52, 0x54, 0x56, 0x58, 0x5b, 0x5d, 0x5f,
+ 0x61, 0x63, 0x65, 0x67, 0x6a, 0x6c, 0x6e, 0x70, 0x73, 0x75, 0x78, 0x7a,
+ 0x7c, 0x7f, 0x81, 0x84, 0x87, 0x89, 0x8c, 0x8f, 0x91, 0x94, 0x97, 0x99,
+ 0x9c, 0x9f, 0xa2, 0xa5, 0xa8, 0xab, 0xae, 0xb1, 0xb4, 0xb7, 0xba, 0xbd,
+ 0xc0, 0xc4, 0xc7, 0xca, 0xcd, 0xd1, 0xd4, 0xd7, 0xdb, 0xde, 0xe2, 0xe5,
+ 0xe9, 0xec, 0xf0, 0xf4, 0xf7, 0xfb, 0xff, 0x103, 0x106, 0x10a, 0x10e,
+ 0x112, 0x116, 0x11a, 0x11e, 0x122, 0x126, 0x12a, 0x12e, 0x132, 0x137,
+ 0x13b, 0x13f, 0x143, 0x148, 0x14c, 0x150, 0x155, 0x159, 0x15e, 0x162,
+ 0x167, 0x16b, 0x170, 0x175, 0x179, 0x17e, 0x183, 0x188, 0x18d, 0x192,
+ 0x196, 0x19b, 0x1a0, 0x1a5, 0x1aa, 0x1b0, 0x1b5, 0x1ba, 0x1bf, 0x1c4,
+ 0x1c9, 0x1cf, 0x1d4, 0x1d9, 0x1df, 0x1e4, 0x1ea, 0x1ef, 0x1f5, 0x1fa,
+ 0x200, 0x206, 0x20b, 0x211, 0x217, 0x21d, 0x223, 0x228, 0x22e, 0x234,
+ 0x23a, 0x240, 0x246, 0x24c, 0x253, 0x259, 0x25f, 0x265, 0x26b, 0x272,
+ 0x278, 0x27f, 0x285, 0x28b, 0x292, 0x298, 0x29f, 0x2a6, 0x2ac, 0x2b3,
+ 0x2ba, 0x2c1, 0x2c7, 0x2ce, 0x2d5, 0x2dc, 0x2e3, 0x2ea, 0x2f1, 0x2f8,
+ 0x2ff, 0x306, 0x30e, 0x315, 0x31c, 0x323, 0x32b, 0x332, 0x33a, 0x341,
+ 0x349, 0x350, 0x358, 0x35f, 0x367, 0x36f, 0x376, 0x37e, 0x386, 0x38e,
+ 0x396, 0x39e, 0x3a6, 0x3ae, 0x3b6, 0x3be, 0x3c6, 0x3ce, 0x3d6, 0x3df,
+ 0x3e7, 0x3ef, 0x3f8, 0x400, 0x409, 0x411, 0x41a, 0x422, 0x42b, 0x434,
+ 0x43c, 0x445, 0x44e, 0x457, 0x45f, 0x468, 0x471, 0x47a, 0x483, 0x48c,
+ 0x496, 0x49f, 0x4a8, 0x4b1, 0x4bb, 0x4c4, 0x4cd, 0x4d7, 0x4e0, 0x4ea,
+ 0x4f3, 0x4fd, 0x506, 0x510, 0x51a, 0x523, 0x52d, 0x537, 0x541, 0x54b,
+ 0x555, 0x55f, 0x569, 0x573, 0x57d, 0x587, 0x592, 0x59c, 0x5a6, 0x5b0,
+ 0x5bb, 0x5c5, 0x5d0, 0x5da, 0x5e5, 0x5ef, 0x5fa, 0x605, 0x610, 0x61a,
+ 0x625, 0x630, 0x63b, 0x646, 0x651, 0x65c, 0x667, 0x672, 0x67d, 0x689,
+ 0x694, 0x69f, 0x6aa, 0x6b6, 0x6c1, 0x6cd, 0x6d8, 0x6e4, 0x6f0, 0x6fb,
+ 0x707, 0x713, 0x71e, 0x72a, 0x736, 0x742, 0x74e, 0x75a, 0x766, 0x772,
+ 0x77e, 0x78b, 0x797, 0x7a3, 0x7af, 0x7bc, 0x7c8, 0x7d5, 0x7e1, 0x7ee,
+ 0x7fa, 0x807, 0x814, 0x821, 0x82d, 0x83a, 0x847, 0x854, 0x861, 0x86e,
+ 0x87b, 0x888, 0x895, 0x8a3, 0x8b0, 0x8bd, 0x8ca, 0x8d8, 0x8e5, 0x8f3,
+ 0x900, 0x90e, 0x91b, 0x929, 0x937, 0x945, 0x952, 0x960, 0x96e, 0x97c,
+ 0x98a, 0x998, 0x9a6, 0x9b4, 0x9c3, 0x9d1, 0x9df, 0x9ed, 0x9fc, 0xa0a,
+ 0xa19, 0xa27, 0xa36, 0xa44, 0xa53, 0xa62, 0xa70, 0xa7f, 0xa8e, 0xa9d,
+ 0xaac, 0xabb, 0xaca, 0xad9, 0xae8, 0xaf7, 0xb07, 0xb16, 0xb25, 0xb35,
+ 0xb44, 0xb53, 0xb63, 0xb73, 0xb82, 0xb92, 0xba2, 0xbb1, 0xbc1, 0xbd1,
+ 0xbe1, 0xbf1, 0xc01, 0xc11, 0xc21, 0xc31, 0xc41, 0xc52, 0xc62, 0xc72,
+ 0xc83, 0xc93, 0xca4, 0xcb4, 0xcc5, 0xcd5, 0xce6, 0xcf7, 0xd08, 0xd18,
+ 0xd29, 0xd3a, 0xd4b, 0xd5c, 0xd6d, 0xd7e, 0xd90, 0xda1, 0xdb2, 0xdc3,
+ 0xdd5, 0xde6, 0xdf8, 0xe09, 0xe1b, 0xe2d, 0xe3e, 0xe50, 0xe62, 0xe74,
+ 0xe85, 0xe97, 0xea9, 0xebb, 0xece, 0xee0, 0xef2, 0xf04, 0xf16, 0xf29,
+ 0xf3b, 0xf4e, 0xf60, 0xf73, 0xf85, 0xf98, 0xfab, 0xfbd, 0xfd0, 0xfe3,
+ 0xff6, 0x1009, 0x101c, 0x102f, 0x1042, 0x1055, 0x1068, 0x107c, 0x108f,
+ 0x10a2, 0x10b6, 0x10c9, 0x10dd, 0x10f0, 0x1104, 0x1117, 0x112b, 0x113f,
+ 0x1153, 0x1167, 0x117b, 0x118f, 0x11a3, 0x11b7, 0x11cb, 0x11df, 0x11f3,
+ 0x1208, 0x121c, 0x1230, 0x1245, 0x1259, 0x126e, 0x1282, 0x1297, 0x12ac,
+ 0x12c1, 0x12d5, 0x12ea, 0x12ff, 0x1314, 0x1329, 0x133e, 0x1353, 0x1369,
+ 0x137e, 0x1393, 0x13a8, 0x13be, 0x13d3, 0x13e9, 0x13fe, 0x1414, 0x142a,
+ 0x143f, 0x1455, 0x146b, 0x1481, 0x1497, 0x14ad, 0x14c3, 0x14d9, 0x14ef,
+ 0x1505, 0x151b, 0x1532, 0x1548, 0x155f, 0x1575, 0x158c, 0x15a2, 0x15b9,
+ 0x15cf, 0x15e6, 0x15fd, 0x1614, 0x162b, 0x1642, 0x1659, 0x1670, 0x1687,
+ 0x169e, 0x16b5, 0x16cc, 0x16e4, 0x16fb, 0x1713, 0x172a, 0x1742, 0x1759,
+ 0x1771, 0x1789, 0x17a0, 0x17b8, 0x17d0, 0x17e8, 0x1800, 0x1818, 0x1830,
+ 0x1848, 0x1860, 0x1879, 0x1891, 0x18a9, 0x18c2, 0x18da, 0x18f3, 0x190b,
+ 0x1924, 0x193d, 0x1956, 0x196e, 0x1987, 0x19a0, 0x19b9, 0x19d2, 0x19eb,
+ 0x1a04, 0x1a1e, 0x1a37, 0x1a50, 0x1a69, 0x1a83, 0x1a9c, 0x1ab6, 0x1acf,
+ 0x1ae9, 0x1b03, 0x1b1d, 0x1b36, 0x1b50, 0x1b6a, 0x1b84, 0x1b9e, 0x1bb8,
+ 0x1bd2, 0x1bed, 0x1c07, 0x1c21, 0x1c3c, 0x1c56, 0x1c70, 0x1c8b, 0x1ca6,
+ 0x1cc0, 0x1cdb, 0x1cf6, 0x1d11, 0x1d2b, 0x1d46, 0x1d61, 0x1d7c, 0x1d97,
+ 0x1db3, 0x1dce, 0x1de9, 0x1e04, 0x1e20, 0x1e3b, 0x1e57, 0x1e72, 0x1e8e,
+ 0x1eaa, 0x1ec5, 0x1ee1, 0x1efd, 0x1f19, 0x1f35, 0x1f51, 0x1f6d, 0x1f89,
+ 0x1fa5, 0x1fc1, 0x1fde, 0x1ffa, 0x2017, 0x2033, 0x2050, 0x206c, 0x2089,
+ 0x20a5, 0x20c2, 0x20df, 0x20fc, 0x2119, 0x2136, 0x2153, 0x2170, 0x218d,
+ 0x21aa, 0x21c8, 0x21e5, 0x2202, 0x2220, 0x223d, 0x225b, 0x2279, 0x2296,
+ 0x22b4, 0x22d2, 0x22f0, 0x230e, 0x232c, 0x234a, 0x2368, 0x2386, 0x23a4,
+ 0x23c2, 0x23e1, 0x23ff, 0x241d, 0x243c, 0x245a, 0x2479, 0x2498, 0x24b7,
+ 0x24d5, 0x24f4, 0x2513, 0x2532, 0x2551, 0x2570, 0x258f, 0x25af, 0x25ce,
+ 0x25ed, 0x260d, 0x262c, 0x264b, 0x266b, 0x268b, 0x26aa, 0x26ca, 0x26ea,
+ 0x270a, 0x272a, 0x274a, 0x276a, 0x278a, 0x27aa, 0x27ca, 0x27ea, 0x280b,
+ 0x282b, 0x284c, 0x286c, 0x288d, 0x28ad, 0x28ce, 0x28ef, 0x2910, 0x2930,
+ 0x2951, 0x2972, 0x2993, 0x29b5, 0x29d6, 0x29f7, 0x2a18, 0x2a3a, 0x2a5b,
+ 0x2a7c, 0x2a9e, 0x2ac0, 0x2ae1, 0x2b03, 0x2b25, 0x2b47, 0x2b68, 0x2b8a,
+ 0x2bac, 0x2bcf, 0x2bf1, 0x2c13, 0x2c35, 0x2c57, 0x2c7a, 0x2c9c, 0x2cbf,
+ 0x2ce1, 0x2d04, 0x2d27, 0x2d49, 0x2d6c, 0x2d8f, 0x2db2, 0x2dd5, 0x2df8,
+ 0x2e1b, 0x2e3e, 0x2e62, 0x2e85, 0x2ea8, 0x2ecc, 0x2eef, 0x2f13, 0x2f36,
+ 0x2f5a, 0x2f7e, 0x2fa1, 0x2fc5, 0x2fe9, 0x300d, 0x3031, 0x3055, 0x3079,
+ 0x309e, 0x30c2, 0x30e6, 0x310b, 0x312f, 0x3154, 0x3178, 0x319d, 0x31c1,
+ 0x31e6, 0x320b, 0x3230, 0x3255, 0x327a, 0x329f, 0x32c4, 0x32e9, 0x330f,
+ 0x3334, 0x3359, 0x337f, 0x33a4, 0x33ca, 0x33ef, 0x3415, 0x343b, 0x3461,
+ 0x3487, 0x34ad, 0x34d3, 0x34f9, 0x351f, 0x3545, 0x356b, 0x3592, 0x35b8,
+ 0x35de, 0x3605, 0x362b, 0x3652, 0x3679, 0x36a0, 0x36c6, 0x36ed, 0x3714,
+ 0x373b, 0x3762, 0x3789, 0x37b1, 0x37d8, 0x37ff, 0x3827, 0x384e, 0x3876,
+ 0x389d, 0x38c5, 0x38ed, 0x3914, 0x393c, 0x3964, 0x398c, 0x39b4, 0x39dc,
+ 0x3a04, 0x3a2c, 0x3a55, 0x3a7d, 0x3aa5, 0x3ace, 0x3af6, 0x3b1f, 0x3b48,
+ 0x3b70, 0x3b99, 0x3bc2, 0x3beb, 0x3c14, 0x3c3d, 0x3c66, 0x3c8f, 0x3cb8,
+ 0x3ce2, 0x3d0b, 0x3d35, 0x3d5e, 0x3d88, 0x3db1, 0x3ddb, 0x3e05, 0x3e2e,
+ 0x3e58, 0x3e82, 0x3eac, 0x3ed6, 0x3f00, 0x3f2b, 0x3f55, 0x3f7f, 0x3faa,
+ 0x3fd4, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x0},
+ {0x3, 0x0, 0x1, 0x2, 0x3, 0x4, 0x6, 0x7, 0x8, 0x9, 0xb, 0xc, 0xd, 0xe,
+ 0x10, 0x11, 0x12, 0x13, 0x15, 0x16, 0x17, 0x18, 0x1a, 0x1b, 0x1c, 0x1d,
+ 0x1e, 0x20, 0x21, 0x22, 0x23, 0x25, 0x26, 0x27, 0x28, 0x2a, 0x2b, 0x2c,
+ 0x2d, 0x2f, 0x30, 0x31, 0x32, 0x34, 0x35, 0x36, 0x37, 0x39, 0x3a, 0x3c,
+ 0x3d, 0x3e, 0x40, 0x41, 0x43, 0x44, 0x46, 0x47, 0x49, 0x4a, 0x4c, 0x4d,
+ 0x4f, 0x51, 0x52, 0x54, 0x56, 0x57, 0x59, 0x5b, 0x5d, 0x5f, 0x60, 0x62,
+ 0x64, 0x66, 0x68, 0x6a, 0x6c, 0x6e, 0x70, 0x72, 0x74, 0x76, 0x78, 0x7a,
+ 0x7c, 0x7e, 0x80, 0x82, 0x85, 0x87, 0x89, 0x8b, 0x8e, 0x90, 0x92, 0x94,
+ 0x97, 0x99, 0x9c, 0x9e, 0xa0, 0xa3, 0xa5, 0xa8, 0xaa, 0xad, 0xb0, 0xb2,
+ 0xb5, 0xb7, 0xba, 0xbd, 0xc0, 0xc2, 0xc5, 0xc8, 0xcb, 0xcd, 0xd0, 0xd3,
+ 0xd6, 0xd9, 0xdc, 0xdf, 0xe2, 0xe5, 0xe8, 0xeb, 0xee, 0xf1, 0xf4, 0xf7,
+ 0xfb, 0xfe, 0x101, 0x104, 0x108, 0x10b, 0x10e, 0x111, 0x115, 0x118,
+ 0x11c, 0x11f, 0x123, 0x126, 0x12a, 0x12d, 0x131, 0x134, 0x138, 0x13b,
+ 0x13f, 0x143, 0x146, 0x14a, 0x14e, 0x152, 0x155, 0x159, 0x15d, 0x161,
+ 0x165, 0x169, 0x16d, 0x171, 0x175, 0x179, 0x17d, 0x181, 0x185, 0x189,
+ 0x18d, 0x192, 0x196, 0x19a, 0x19e, 0x1a2, 0x1a7, 0x1ab, 0x1af, 0x1b4,
+ 0x1b8, 0x1bd, 0x1c1, 0x1c6, 0x1ca, 0x1cf, 0x1d3, 0x1d8, 0x1dc, 0x1e1,
+ 0x1e6, 0x1ea, 0x1ef, 0x1f4, 0x1f9, 0x1fe, 0x202, 0x207, 0x20c, 0x211,
+ 0x216, 0x21b, 0x220, 0x225, 0x22a, 0x22f, 0x234, 0x239, 0x23e, 0x244,
+ 0x249, 0x24e, 0x253, 0x258, 0x25e, 0x263, 0x269, 0x26e, 0x273, 0x279,
+ 0x27e, 0x284, 0x289, 0x28f, 0x294, 0x29a, 0x2a0, 0x2a5, 0x2ab, 0x2b1,
+ 0x2b7, 0x2bc, 0x2c2, 0x2c8, 0x2ce, 0x2d4, 0x2da, 0x2e0, 0x2e6, 0x2ec,
+ 0x2f2, 0x2f8, 0x2fe, 0x304, 0x30a, 0x310, 0x316, 0x31d, 0x323, 0x329,
+ 0x32f, 0x336, 0x33c, 0x343, 0x349, 0x350, 0x356, 0x35d, 0x363, 0x36a,
+ 0x370, 0x377, 0x37e, 0x384, 0x38b, 0x392, 0x398, 0x39f, 0x3a6, 0x3ad,
+ 0x3b4, 0x3bb, 0x3c2, 0x3c9, 0x3d0, 0x3d7, 0x3de, 0x3e5, 0x3ec, 0x3f3,
+ 0x3fb, 0x402, 0x409, 0x410, 0x418, 0x41f, 0x426, 0x42e, 0x435, 0x43d,
+ 0x444, 0x44c, 0x453, 0x45b, 0x462, 0x46a, 0x472, 0x479, 0x481, 0x489,
+ 0x491, 0x499, 0x4a0, 0x4a8, 0x4b0, 0x4b8, 0x4c0, 0x4c8, 0x4d0, 0x4d8,
+ 0x4e0, 0x4e8, 0x4f1, 0x4f9, 0x501, 0x509, 0x512, 0x51a, 0x522, 0x52b,
+ 0x533, 0x53b, 0x544, 0x54c, 0x555, 0x55e, 0x566, 0x56f, 0x577, 0x580,
+ 0x589, 0x592, 0x59a, 0x5a3, 0x5ac, 0x5b5, 0x5be, 0x5c7, 0x5d0, 0x5d9,
+ 0x5e2, 0x5eb, 0x5f4, 0x5fd, 0x606, 0x60f, 0x619, 0x622, 0x62b, 0x635,
+ 0x63e, 0x647, 0x651, 0x65a, 0x664, 0x66d, 0x677, 0x680, 0x68a, 0x694,
+ 0x69d, 0x6a7, 0x6b1, 0x6bb, 0x6c4, 0x6ce, 0x6d8, 0x6e2, 0x6ec, 0x6f6,
+ 0x700, 0x70a, 0x714, 0x71e, 0x728, 0x732, 0x73d, 0x747, 0x751, 0x75b,
+ 0x766, 0x770, 0x77a, 0x785, 0x78f, 0x79a, 0x7a4, 0x7af, 0x7ba, 0x7c4,
+ 0x7cf, 0x7da, 0x7e4, 0x7ef, 0x7fa, 0x805, 0x810, 0x81a, 0x825, 0x830,
+ 0x83b, 0x846, 0x851, 0x85d, 0x868, 0x873, 0x87e, 0x889, 0x895, 0x8a0,
+ 0x8ab, 0x8b7, 0x8c2, 0x8cd, 0x8d9, 0x8e4, 0x8f0, 0x8fb, 0x907, 0x913,
+ 0x91e, 0x92a, 0x936, 0x942, 0x94d, 0x959, 0x965, 0x971, 0x97d, 0x989,
+ 0x995, 0x9a1, 0x9ad, 0x9b9, 0x9c5, 0x9d2, 0x9de, 0x9ea, 0x9f6, 0xa03,
+ 0xa0f, 0xa1c, 0xa28, 0xa34, 0xa41, 0xa4d, 0xa5a, 0xa67, 0xa73, 0xa80,
+ 0xa8d, 0xa99, 0xaa6, 0xab3, 0xac0, 0xacd, 0xada, 0xae7, 0xaf4, 0xb01,
+ 0xb0e, 0xb1b, 0xb28, 0xb35, 0xb42, 0xb50, 0xb5d, 0xb6a, 0xb77, 0xb85,
+ 0xb92, 0xba0, 0xbad, 0xbbb, 0xbc8, 0xbd6, 0xbe4, 0xbf1, 0xbff, 0xc0d,
+ 0xc1a, 0xc28, 0xc36, 0xc44, 0xc52, 0xc60, 0xc6e, 0xc7c, 0xc8a, 0xc98,
+ 0xca6, 0xcb4, 0xcc3, 0xcd1, 0xcdf, 0xced, 0xcfc, 0xd0a, 0xd19, 0xd27,
+ 0xd35, 0xd44, 0xd53, 0xd61, 0xd70, 0xd7e, 0xd8d, 0xd9c, 0xdab, 0xdba,
+ 0xdc8, 0xdd7, 0xde6, 0xdf5, 0xe04, 0xe13, 0xe22, 0xe31, 0xe41, 0xe50,
+ 0xe5f, 0xe6e, 0xe7e, 0xe8d, 0xe9c, 0xeac, 0xebb, 0xecb, 0xeda, 0xeea,
+ 0xef9, 0xf09, 0xf19, 0xf28, 0xf38, 0xf48, 0xf58, 0xf68, 0xf77, 0xf87,
+ 0xf97, 0xfa7, 0xfb7, 0xfc7, 0xfd8, 0xfe8, 0xff8, 0x1008, 0x1018,
+ 0x1029, 0x1039, 0x1049, 0x105a, 0x106a, 0x107b, 0x108b, 0x109c, 0x10ad,
+ 0x10bd, 0x10ce, 0x10df, 0x10ef, 0x1100, 0x1111, 0x1122, 0x1133, 0x1144,
+ 0x1155, 0x1166, 0x1177, 0x1188, 0x1199, 0x11aa, 0x11bb, 0x11cd, 0x11de,
+ 0x11ef, 0x1201, 0x1212, 0x1223, 0x1235, 0x1246, 0x1258, 0x126a, 0x127b,
+ 0x128d, 0x129f, 0x12b0, 0x12c2, 0x12d4, 0x12e6, 0x12f8, 0x130a, 0x131c,
+ 0x132e, 0x1340, 0x1352, 0x1364, 0x1376, 0x1388, 0x139b, 0x13ad, 0x13bf,
+ 0x13d2, 0x13e4, 0x13f6, 0x1409, 0x141c, 0x142e, 0x1441, 0x1453, 0x1466,
+ 0x1479, 0x148b, 0x149e, 0x14b1, 0x14c4, 0x14d7, 0x14ea, 0x14fd, 0x1510,
+ 0x1523, 0x1536, 0x1549, 0x155c, 0x1570, 0x1583, 0x1596, 0x15aa, 0x15bd,
+ 0x15d0, 0x15e4, 0x15f7, 0x160b, 0x161e, 0x1632, 0x1646, 0x1659, 0x166d,
+ 0x1681, 0x1695, 0x16a9, 0x16bd, 0x16d1, 0x16e4, 0x16f9, 0x170d, 0x1721,
+ 0x1735, 0x1749, 0x175d, 0x1771, 0x1786, 0x179a, 0x17af, 0x17c3, 0x17d7,
+ 0x17ec, 0x1800, 0x1815, 0x182a, 0x183e, 0x1853, 0x1868, 0x187d, 0x1891,
+ 0x18a6, 0x18bb, 0x18d0, 0x18e5, 0x18fa, 0x190f, 0x1924, 0x1939, 0x194f,
+ 0x1964, 0x1979, 0x198e, 0x19a4, 0x19b9, 0x19cf, 0x19e4, 0x19fa, 0x1a0f,
+ 0x1a25, 0x1a3a, 0x1a50, 0x1a66, 0x1a7b, 0x1a91, 0x1aa7, 0x1abd, 0x1ad3,
+ 0x1ae9, 0x1aff, 0x1b15, 0x1b2b, 0x1b41, 0x1b57, 0x1b6d, 0x1b84, 0x1b9a,
+ 0x1bb0, 0x1bc7, 0x1bdd, 0x1bf4, 0x1c0a, 0x1c21, 0x1c37, 0x1c4e, 0x1c64,
+ 0x1c7b, 0x1c92, 0x1ca9, 0x1cbf, 0x1cd6, 0x1ced, 0x1d04, 0x1d1b, 0x1d32,
+ 0x1d49, 0x1d60, 0x1d78, 0x1d8f, 0x1da6, 0x1dbd, 0x1dd5, 0x1dec, 0x1e03,
+ 0x1e1b, 0x1e32, 0x1e4a, 0x1e61, 0x1e79, 0x1e91, 0x1ea8, 0x1ec0, 0x1ed8,
+ 0x1ef0, 0x1f07, 0x1f1f, 0x1f37, 0x1f4f, 0x1f67, 0x1f7f, 0x1f98, 0x1fb0,
+ 0x1fc8, 0x1fe0, 0x1ff8, 0x2011, 0x2029, 0x2042, 0x205a, 0x2072, 0x208b,
+ 0x20a4, 0x20bc, 0x20d5, 0x20ee, 0x2106, 0x211f, 0x2138, 0x2151, 0x216a,
+ 0x2183, 0x219c, 0x21b5, 0x21ce, 0x21e7, 0x2200, 0x2219, 0x2233, 0x224c,
+ 0x2265, 0x227f, 0x2298, 0x22b2, 0x22cb, 0x22e5, 0x22fe, 0x2318, 0x2331,
+ 0x234b, 0x2365, 0x237f, 0x2399, 0x23b3, 0x23cc, 0x23e6, 0x2401, 0x241b,
+ 0x2435, 0x244f, 0x2469, 0x2483, 0x249e, 0x24b8, 0x24d2, 0x24ed, 0x2507,
+ 0x2522, 0x253c, 0x2557, 0x2571, 0x258c, 0x25a7, 0x25c2, 0x25dc, 0x25f7,
+ 0x2612, 0x262d, 0x2648, 0x2663, 0x267e, 0x2699, 0x26b4, 0x26cf, 0x26eb,
+ 0x2706, 0x2721, 0x273d, 0x2758, 0x2774, 0x278f, 0x27ab, 0x27c6, 0x27e2,
+ 0x27fd, 0x2819, 0x2835, 0x2851, 0x286d, 0x2888, 0x28a4, 0x28c0, 0x28dc,
+ 0x28f8, 0x2915, 0x2931, 0x294d, 0x2969, 0x2985, 0x29a2, 0x29be, 0x29db,
+ 0x29f7, 0x2a14, 0x2a30, 0x2a4d, 0x2a69, 0x2a86, 0x2aa3, 0x2ac0, 0x2adc,
+ 0x2af9, 0x2b16, 0x2b33, 0x2b50, 0x2b6d, 0x2b8a, 0x2ba7, 0x2bc4, 0x2be2,
+ 0x2bff, 0x2c1c, 0x2c3a, 0x2c57, 0x2c74, 0x2c92, 0x2caf, 0x2ccd, 0x2ceb,
+ 0x2d08, 0x2d26, 0x2d44, 0x2d61, 0x2d7f, 0x2d9d, 0x2dbb, 0x2dd9, 0x2df7,
+ 0x2e15, 0x2e33, 0x2e51, 0x2e70, 0x2e8e, 0x2eac, 0x2eca, 0x2ee9, 0x2f07,
+ 0x2f26, 0x2f44, 0x2f63, 0x2f81, 0x2fa0, 0x2fbf, 0x2fdd, 0x2ffc, 0x301b,
+ 0x303a, 0x3059, 0x3078, 0x3097, 0x30b6, 0x30d5, 0x30f4, 0x3113, 0x3132,
+ 0x3152, 0x3171, 0x3190, 0x31b0, 0x31cf, 0x31ef, 0x320e, 0x322e, 0x324e,
+ 0x326d, 0x328d, 0x32ad, 0x32cd, 0x32ec, 0x330c, 0x332c, 0x334c, 0x336c,
+ 0x338c, 0x33ac, 0x33cd, 0x33ed, 0x340d, 0x342e, 0x344e, 0x346e, 0x348f,
+ 0x34af, 0x34d0, 0x34f0, 0x3511, 0x3532, 0x3552, 0x3573, 0x3594, 0x35b5,
+ 0x35d6, 0x35f7, 0x3618, 0x3639, 0x365a, 0x367b, 0x369c, 0x36bd, 0x36df,
+ 0x3700, 0x3721, 0x3743, 0x3764, 0x3786, 0x37a7, 0x37c9, 0x37eb, 0x380c,
+ 0x382e, 0x3850, 0x3872, 0x3894, 0x38b6, 0x38d8, 0x38fa, 0x391c, 0x393e,
+ 0x3960, 0x3982, 0x39a4, 0x39c7, 0x39e9, 0x3a0b, 0x3a2e, 0x3a50, 0x3a73,
+ 0x3a95, 0x3ab8, 0x3adb, 0x3afd, 0x3b20, 0x3b43, 0x3b66, 0x3b89, 0x3bac,
+ 0x3bcf, 0x3bf2, 0x3c15, 0x3c38, 0x3c5b, 0x3c7e, 0x3ca2, 0x3cc5, 0x3ce8,
+ 0x3d0c, 0x3d2f, 0x3d53, 0x3d76, 0x3d9a, 0x3dbe, 0x3de1, 0x3e05, 0x3e29,
+ 0x3e4d, 0x3e71, 0x3e95, 0x3eb9, 0x3edd, 0x3f01, 0x3f25, 0x3f49, 0x3f6d,
+ 0x3f91, 0x3fb6, 0x3fda, 0x3fff, 0x0},
+ {0x3, 0x0, 0x200, 0x600, 0x800, 0x980, 0xaa0, 0xba0, 0xc70, 0xd20,
+ 0xde0, 0xe58, 0xed0, 0xf58, 0xfe0, 0x103c, 0x1090, 0x10e8, 0x1144,
+ 0x11a8, 0x120a, 0x1242, 0x127e, 0x12bc, 0x1300, 0x1346, 0x138e, 0x13dc,
+ 0x1416, 0x1441, 0x146d, 0x149b, 0x14cc, 0x14fe, 0x1532, 0x1569, 0x15a2,
+ 0x15dd, 0x160d, 0x162c, 0x164d, 0x166f, 0x1693, 0x16b7, 0x16dd, 0x1704,
+ 0x172c, 0x1756, 0x1781, 0x17ad, 0x17db, 0x1805, 0x181d, 0x1836, 0x1850,
+ 0x186b, 0x1886, 0x18a2, 0x18bf, 0x18dd, 0x18fb, 0x191b, 0x193b, 0x195c,
+ 0x197e, 0x19a1, 0x19c5, 0x19e9, 0x1a07, 0x1a1b, 0x1a2e, 0x1a43, 0x1a57,
+ 0x1a6d, 0x1a83, 0x1a99, 0x1ab0, 0x1ac7, 0x1adf, 0x1af8, 0x1b11, 0x1b2a,
+ 0x1b45, 0x1b5f, 0x1b7b, 0x1b97, 0x1bb4, 0x1bd1, 0x1bef, 0x1c06, 0x1c16,
+ 0x1c26, 0x1c36, 0x1c47, 0x1c58, 0x1c69, 0x1c7b, 0x1c8d, 0x1c9f, 0x1cb2,
+ 0x1cc5, 0x1cd9, 0x1ced, 0x1d01, 0x1d16, 0x1d2b, 0x1d40, 0x1d56, 0x1d6d,
+ 0x1d83, 0x1d9b, 0x1db2, 0x1dca, 0x1de3, 0x1dfc, 0x1e0a, 0x1e17, 0x1e25,
+ 0x1e32, 0x1e40, 0x1e4e, 0x1e5c, 0x1e6a, 0x1e79, 0x1e88, 0x1e97, 0x1ea7,
+ 0x1eb7, 0x1ec7, 0x1ed7, 0x1ee8, 0x1ef9, 0x1f0a, 0x1f1b, 0x1f2d, 0x1f3f,
+ 0x1f52, 0x1f64, 0x1f77, 0x1f8b, 0x1f9e, 0x1fb2, 0x1fc6, 0x1fdb, 0x1ff0,
+ 0x2002, 0x200d, 0x2018, 0x2024, 0x202f, 0x203a, 0x2046, 0x2052, 0x205e,
+ 0x206a, 0x2077, 0x2084, 0x2091, 0x209e, 0x20ab, 0x20b8, 0x20c6, 0x20d4,
+ 0x20e2, 0x20f1, 0x20ff, 0x210e, 0x211d, 0x212c, 0x213c, 0x214b, 0x215b,
+ 0x216b, 0x217c, 0x218d, 0x219d, 0x21af, 0x21c0, 0x21d2, 0x21e4, 0x21f6,
+ 0x2204, 0x220d, 0x2217, 0x2220, 0x222a, 0x2234, 0x223e, 0x2248, 0x2253,
+ 0x225d, 0x2268, 0x2273, 0x227d, 0x2289, 0x2294, 0x229f, 0x22ab, 0x22b6,
+ 0x22c2, 0x22ce, 0x22db, 0x22e7, 0x22f4, 0x2300, 0x230d, 0x231a, 0x2328,
+ 0x2335, 0x2343, 0x2350, 0x235e, 0x236d, 0x237b, 0x238a, 0x2398, 0x23a7,
+ 0x23b7, 0x23c6, 0x23d5, 0x23e5, 0x23f5, 0x2402, 0x240b, 0x2413, 0x241b,
+ 0x2424, 0x242d, 0x2435, 0x243e, 0x2447, 0x2450, 0x245a, 0x2463, 0x246c,
+ 0x2476, 0x2480, 0x248a, 0x2493, 0x249e, 0x24a8, 0x24b2, 0x24bd, 0x24c7,
+ 0x24d2, 0x24dd, 0x24e8, 0x24f3, 0x24fe, 0x250a, 0x2515, 0x2521, 0x252d,
+ 0x2539, 0x2545, 0x2552, 0x255e, 0x256b, 0x2578, 0x2584, 0x2592, 0x259f,
+ 0x25ac, 0x25ba, 0x25c8, 0x25d6, 0x25e4, 0x25f2, 0x2600, 0x2607, 0x260f,
+ 0x2616, 0x261e, 0x2626, 0x262d, 0x2635, 0x263d, 0x2645, 0x264d, 0x2656,
+ 0x265e, 0x2666, 0x266f, 0x2678, 0x2680, 0x2689, 0x2692, 0x269b, 0x26a5,
+ 0x26ae, 0x26b7, 0x26c1, 0x26ca, 0x26d4, 0x26de, 0x26e8, 0x26f2, 0x26fc,
+ 0x2707, 0x2711, 0x271c, 0x2726, 0x2731, 0x273c, 0x2747, 0x2753, 0x275e,
+ 0x2769, 0x2775, 0x2781, 0x278d, 0x2799, 0x27a5, 0x27b1, 0x27bd, 0x27ca,
+ 0x27d7, 0x27e4, 0x27f1, 0x27fe, 0x2805, 0x280c, 0x2813, 0x281a, 0x2821,
+ 0x2828, 0x282f, 0x2836, 0x283d, 0x2845, 0x284c, 0x2854, 0x285b, 0x2863,
+ 0x286b, 0x2873, 0x287b, 0x2883, 0x288b, 0x2893, 0x289c, 0x28a4, 0x28ad,
+ 0x28b5, 0x28be, 0x28c7, 0x28d0, 0x28d9, 0x28e2, 0x28eb, 0x28f4, 0x28fe,
+ 0x2907, 0x2911, 0x291b, 0x2925, 0x292f, 0x2939, 0x2943, 0x294d, 0x2958,
+ 0x2962, 0x296d, 0x2977, 0x2982, 0x298d, 0x2998, 0x29a4, 0x29af, 0x29bb,
+ 0x29c6, 0x29d2, 0x29de, 0x29ea, 0x29f6, 0x2a01, 0x2a07, 0x2a0d, 0x2a14,
+ 0x2a1a, 0x2a21, 0x2a27, 0x2a2e, 0x2a34, 0x2a3b, 0x2a42, 0x2a49, 0x2a50,
+ 0x2a57, 0x2a5e, 0x2a65, 0x2a6d, 0x2a74, 0x2a7b, 0x2a83, 0x2a8a, 0x2a92,
+ 0x2a9a, 0x2aa2, 0x2aaa, 0x2ab2, 0x2aba, 0x2ac2, 0x2aca, 0x2ad3, 0x2adb,
+ 0x2ae4, 0x2aec, 0x2af5, 0x2afe, 0x2b07, 0x2b10, 0x2b19, 0x2b22, 0x2b2b,
+ 0x2b35, 0x2b3e, 0x2b48, 0x2b52, 0x2b5c, 0x2b65, 0x2b6f, 0x2b7a, 0x2b84,
+ 0x2b8e, 0x2b99, 0x2ba3, 0x2bae, 0x2bb9, 0x2bc4, 0x2bcf, 0x2bda, 0x2be5,
+ 0x2bf0, 0x2bfc, 0x2c03, 0x2c09, 0x2c0f, 0x2c15, 0x2c1b, 0x2c21, 0x2c28,
+ 0x2c2e, 0x2c34, 0x2c3b, 0x2c41, 0x2c48, 0x2c4e, 0x2c55, 0x2c5b, 0x2c62,
+ 0x2c69, 0x2c70, 0x2c77, 0x2c7e, 0x2c85, 0x2c8c, 0x2c94, 0x2c9b, 0x2ca3,
+ 0x2caa, 0x2cb2, 0x2cb9, 0x2cc1, 0x2cc9, 0x2cd1, 0x2cd9, 0x2ce1, 0x2ce9,
+ 0x2cf2, 0x2cfa, 0x2d02, 0x2d0b, 0x2d13, 0x2d1c, 0x2d25, 0x2d2e, 0x2d37,
+ 0x2d40, 0x2d49, 0x2d52, 0x2d5c, 0x2d65, 0x2d6f, 0x2d78, 0x2d82, 0x2d8c,
+ 0x2d96, 0x2da0, 0x2daa, 0x2db4, 0x2dbf, 0x2dc9, 0x2dd4, 0x2dde, 0x2de9,
+ 0x2df4, 0x2dff, 0x2e05, 0x2e0b, 0x2e10, 0x2e16, 0x2e1c, 0x2e22, 0x2e28,
+ 0x2e2d, 0x2e34, 0x2e3a, 0x2e40, 0x2e46, 0x2e4c, 0x2e53, 0x2e59, 0x2e60,
+ 0x2e66, 0x2e6d, 0x2e73, 0x2e7a, 0x2e81, 0x2e88, 0x2e8f, 0x2e96, 0x2e9d,
+ 0x2ea4, 0x2eab, 0x2eb3, 0x2eba, 0x2ec2, 0x2ec9, 0x2ed1, 0x2ed9, 0x2ee0,
+ 0x2ee8, 0x2ef0, 0x2ef8, 0x2f00, 0x2f09, 0x2f11, 0x2f19, 0x2f22, 0x2f2a,
+ 0x2f33, 0x2f3c, 0x2f44, 0x2f4d, 0x2f56, 0x2f5f, 0x2f68, 0x2f72, 0x2f7b,
+ 0x2f85, 0x2f8e, 0x2f98, 0x2fa1, 0x2fab, 0x2fb5, 0x2fbf, 0x2fc9, 0x2fd4,
+ 0x2fde, 0x2fe8, 0x2ff3, 0x2ffe, 0x3004, 0x3009, 0x300f, 0x3014, 0x301a,
+ 0x3020, 0x3025, 0x302b, 0x3031, 0x3037, 0x303d, 0x3043, 0x3049, 0x304f,
+ 0x3055, 0x305c, 0x3062, 0x3068, 0x306f, 0x3075, 0x307c, 0x3083, 0x3089,
+ 0x3090, 0x3097, 0x309e, 0x30a5, 0x30ac, 0x30b3, 0x30ba, 0x30c2, 0x30c9,
+ 0x30d1, 0x30d8, 0x30e0, 0x30e7, 0x30ef, 0x30f7, 0x30ff, 0x3107, 0x310f,
+ 0x3117, 0x311f, 0x3128, 0x3130, 0x3139, 0x3141, 0x314a, 0x3153, 0x315b,
+ 0x3164, 0x316d, 0x3176, 0x3180, 0x3189, 0x3192, 0x319c, 0x31a5, 0x31af,
+ 0x31b9, 0x31c3, 0x31cd, 0x31d7, 0x31e1, 0x31eb, 0x31f6, 0x3200, 0x3205,
+ 0x320a, 0x3210, 0x3215, 0x321b, 0x3220, 0x3226, 0x322c, 0x3232, 0x3237,
+ 0x323d, 0x3243, 0x3249, 0x324f, 0x3255, 0x325b, 0x3262, 0x3268, 0x326e,
+ 0x3275, 0x327b, 0x3282, 0x3288, 0x328f, 0x3296, 0x329d, 0x32a4, 0x32ab,
+ 0x32b2, 0x32b9, 0x32c0, 0x32c7, 0x32cf, 0x32d6, 0x32dd, 0x32e5, 0x32ed,
+ 0x32f4, 0x32fc, 0x3304, 0x330c, 0x3314, 0x331c, 0x3324, 0x332d, 0x3335,
+ 0x333d, 0x3346, 0x334f, 0x3357, 0x3360, 0x3369, 0x3372, 0x337b, 0x3384,
+ 0x338d, 0x3397, 0x33a0, 0x33aa, 0x33b3, 0x33bd, 0x33c7, 0x33d1, 0x33db,
+ 0x33e5, 0x33ef, 0x33fa, 0x3402, 0x3407, 0x340d, 0x3412, 0x3417, 0x341d,
+ 0x3422, 0x3428, 0x342e, 0x3434, 0x3439, 0x343f, 0x3445, 0x344b, 0x3451,
+ 0x3457, 0x345d, 0x3464, 0x346a, 0x3470, 0x3477, 0x347d, 0x3484, 0x348a,
+ 0x3491, 0x3498, 0x349f, 0x34a6, 0x34ad, 0x34b4, 0x34bb, 0x34c2, 0x34c9,
+ 0x34d1, 0x34d8, 0x34df, 0x34e7, 0x34ef, 0x34f6, 0x34fe, 0x3506, 0x350e,
+ 0x3516, 0x351e, 0x3526, 0x352f, 0x3537, 0x3540, 0x3548, 0x3551, 0x355a,
+ 0x3562, 0x356b, 0x3574, 0x357e, 0x3587, 0x3590, 0x359a, 0x35a3, 0x35ad,
+ 0x35b6, 0x35c0, 0x35ca, 0x35d4, 0x35de, 0x35e8, 0x35f3, 0x35fd, 0x3604,
+ 0x3609, 0x360e, 0x3614, 0x3619, 0x361f, 0x3624, 0x362a, 0x3630, 0x3636,
+ 0x363b, 0x3641, 0x3647, 0x364d, 0x3654, 0x365a, 0x3660, 0x3666, 0x366d,
+ 0x3673, 0x367a, 0x3680, 0x3687, 0x368d, 0x3694, 0x369b, 0x36a2, 0x36a9,
+ 0x36b0, 0x36b7, 0x36be, 0x36c6, 0x36cd, 0x36d5, 0x36dc, 0x36e4, 0x36eb,
+ 0x36f3, 0x36fb, 0x3703, 0x370b, 0x3713, 0x371b, 0x3724, 0x372c, 0x3734,
+ 0x373d, 0x3746, 0x374e, 0x3757, 0x3760, 0x3769, 0x3772, 0x377b, 0x3785,
+ 0x378e, 0x3798, 0x37a1, 0x37ab, 0x37b5, 0x37bf, 0x37c9, 0x37d3, 0x37dd,
+ 0x37e7, 0x37f2, 0x37fd, 0x3803, 0x3809, 0x380e, 0x3814, 0x3819, 0x381f,
+ 0x3825, 0x382a, 0x3830, 0x3836, 0x383c, 0x3842, 0x3848, 0x384e, 0x3855,
+ 0x385b, 0x3861, 0x3868, 0x386e, 0x3875, 0x387b, 0x3882, 0x3889, 0x3890,
+ 0x3897, 0x389e, 0x38a5, 0x38ac, 0x38b3, 0x38ba, 0x38c2, 0x38c9, 0x38d1,
+ 0x38d8, 0x38e0, 0x38e8, 0x38f0, 0x38f8, 0x3900, 0x3908, 0x3910, 0x3919,
+ 0x3921, 0x3929, 0x3932, 0x393b, 0x3944, 0x394c, 0x3955, 0x395f, 0x3968,
+ 0x3971, 0x397a, 0x3984, 0x398e, 0x3997, 0x39a1, 0x39ab, 0x39b5, 0x39bf,
+ 0x39c9, 0x39d4, 0x39de, 0x39e9, 0x39f4, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x3},
+ {0x3, 0x0, 0x4, 0x8, 0xc, 0x10, 0x14, 0x18, 0x1c, 0x20, 0x24, 0x28,
+ 0x2c, 0x30, 0x34, 0x38, 0x3c, 0x41, 0x45, 0x49, 0x4d, 0x51, 0x55, 0x59,
+ 0x5d, 0x61, 0x65, 0x69, 0x6d, 0x71, 0x75, 0x79, 0x7d, 0x82, 0x86, 0x8a,
+ 0x8e, 0x92, 0x96, 0x9a, 0x9e, 0xa2, 0xa6, 0xaa, 0xae, 0xb2, 0xb6, 0xba,
+ 0xbe, 0xc3, 0xc7, 0xcb, 0xcf, 0xd3, 0xd7, 0xdb, 0xdf, 0xe3, 0xe7, 0xeb,
+ 0xef, 0xf3, 0xf7, 0xfb, 0xff, 0x104, 0x108, 0x10c, 0x110, 0x114, 0x118,
+ 0x11c, 0x120, 0x124, 0x127, 0x12b, 0x12f, 0x134, 0x138, 0x13c, 0x140,
+ 0x144, 0x149, 0x14d, 0x151, 0x156, 0x15a, 0x15f, 0x163, 0x168, 0x16c,
+ 0x171, 0x175, 0x17a, 0x17f, 0x183, 0x188, 0x18d, 0x192, 0x197, 0x19b,
+ 0x1a0, 0x1a5, 0x1aa, 0x1af, 0x1b4, 0x1b9, 0x1be, 0x1c3, 0x1c9, 0x1ce,
+ 0x1d3, 0x1d8, 0x1dd, 0x1e3, 0x1e8, 0x1ed, 0x1f3, 0x1f8, 0x1fe, 0x203,
+ 0x209, 0x20e, 0x214, 0x21a, 0x21f, 0x225, 0x22b, 0x230, 0x236, 0x23c,
+ 0x242, 0x248, 0x24e, 0x254, 0x25a, 0x260, 0x266, 0x26c, 0x272, 0x278,
+ 0x27e, 0x285, 0x28b, 0x291, 0x297, 0x29e, 0x2a4, 0x2ab, 0x2b1, 0x2b8,
+ 0x2be, 0x2c5, 0x2cb, 0x2d2, 0x2d9, 0x2df, 0x2e6, 0x2ed, 0x2f4, 0x2fa,
+ 0x301, 0x308, 0x30f, 0x316, 0x31d, 0x324, 0x32b, 0x332, 0x339, 0x341,
+ 0x348, 0x34f, 0x356, 0x35e, 0x365, 0x36c, 0x374, 0x37b, 0x383, 0x38a,
+ 0x392, 0x399, 0x3a1, 0x3a8, 0x3b0, 0x3b8, 0x3c0, 0x3c7, 0x3cf, 0x3d7,
+ 0x3df, 0x3e7, 0x3ef, 0x3f7, 0x3ff, 0x407, 0x40f, 0x417, 0x41f, 0x427,
+ 0x430, 0x438, 0x440, 0x449, 0x451, 0x459, 0x462, 0x46a, 0x473, 0x47b,
+ 0x484, 0x48d, 0x495, 0x49e, 0x4a7, 0x4af, 0x4b8, 0x4c1, 0x4ca, 0x4d3,
+ 0x4dc, 0x4e5, 0x4ee, 0x4f7, 0x500, 0x509, 0x512, 0x51b, 0x525, 0x52e,
+ 0x537, 0x540, 0x54a, 0x553, 0x55d, 0x566, 0x570, 0x579, 0x583, 0x58c,
+ 0x596, 0x5a0, 0x5a9, 0x5b3, 0x5bd, 0x5c7, 0x5d1, 0x5db, 0x5e5, 0x5ef,
+ 0x5f9, 0x603, 0x60d, 0x617, 0x621, 0x62b, 0x635, 0x640, 0x64a, 0x654,
+ 0x65f, 0x669, 0x674, 0x67e, 0x689, 0x693, 0x69e, 0x6a8, 0x6b3, 0x6be,
+ 0x6c8, 0x6d3, 0x6de, 0x6e9, 0x6f4, 0x6ff, 0x70a, 0x715, 0x720, 0x72b,
+ 0x736, 0x741, 0x74c, 0x757, 0x763, 0x76e, 0x779, 0x785, 0x790, 0x79c,
+ 0x7a7, 0x7b3, 0x7be, 0x7ca, 0x7d5, 0x7e1, 0x7ed, 0x7f8, 0x804, 0x810,
+ 0x81c, 0x828, 0x834, 0x840, 0x84c, 0x858, 0x864, 0x870, 0x87c, 0x888,
+ 0x894, 0x8a1, 0x8ad, 0x8b9, 0x8c6, 0x8d2, 0x8df, 0x8eb, 0x8f8, 0x904,
+ 0x911, 0x91d, 0x92a, 0x937, 0x944, 0x950, 0x95d, 0x96a, 0x977, 0x984,
+ 0x991, 0x99e, 0x9ab, 0x9b8, 0x9c5, 0x9d2, 0x9e0, 0x9ed, 0x9fa, 0xa07,
+ 0xa15, 0xa22, 0xa30, 0xa3d, 0xa4b, 0xa58, 0xa66, 0xa73, 0xa81, 0xa8f,
+ 0xa9d, 0xaaa, 0xab8, 0xac6, 0xad4, 0xae2, 0xaf0, 0xafe, 0xb0c, 0xb1a,
+ 0xb28, 0xb36, 0xb44, 0xb53, 0xb61, 0xb6f, 0xb7e, 0xb8c, 0xb9a, 0xba9,
+ 0xbb7, 0xbc6, 0xbd5, 0xbe3, 0xbf2, 0xc01, 0xc0f, 0xc1e, 0xc2d, 0xc3c,
+ 0xc4b, 0xc5a, 0xc69, 0xc78, 0xc87, 0xc96, 0xca5, 0xcb4, 0xcc3, 0xcd3,
+ 0xce2, 0xcf1, 0xd01, 0xd10, 0xd1f, 0xd2f, 0xd3f, 0xd4e, 0xd5e, 0xd6d,
+ 0xd7d, 0xd8d, 0xd9c, 0xdac, 0xdbc, 0xdcc, 0xddc, 0xdec, 0xdfc, 0xe0c,
+ 0xe1c, 0xe2c, 0xe3c, 0xe4c, 0xe5d, 0xe6d, 0xe7d, 0xe8e, 0xe9e, 0xeae,
+ 0xebf, 0xecf, 0xee0, 0xef1, 0xf01, 0xf12, 0xf23, 0xf33, 0xf44, 0xf55,
+ 0xf66, 0xf77, 0xf88, 0xf99, 0xfaa, 0xfbb, 0xfcc, 0xfdd, 0xfee, 0xfff,
+ 0x1011, 0x1022, 0x1033, 0x1045, 0x1056, 0x1068, 0x1079, 0x108b, 0x109c,
+ 0x10ae, 0x10c0, 0x10d1, 0x10e3, 0x10f5, 0x1107, 0x1119, 0x112b, 0x113c,
+ 0x114e, 0x1160, 0x1173, 0x1185, 0x1197, 0x11a9, 0x11bb, 0x11ce, 0x11e0,
+ 0x11f2, 0x1205, 0x1217, 0x1229, 0x123c, 0x124f, 0x1261, 0x1274, 0x1286,
+ 0x1299, 0x12ac, 0x12bf, 0x12d2, 0x12e4, 0x12f7, 0x130a, 0x131d, 0x1330,
+ 0x1343, 0x1357, 0x136a, 0x137d, 0x1390, 0x13a3, 0x13b7, 0x13ca, 0x13de,
+ 0x13f1, 0x1405, 0x1418, 0x142c, 0x143f, 0x1453, 0x1467, 0x147a, 0x148e,
+ 0x14a2, 0x14b6, 0x14ca, 0x14de, 0x14f2, 0x1506, 0x151a, 0x152e, 0x1542,
+ 0x1556, 0x156a, 0x157f, 0x1593, 0x15a7, 0x15bc, 0x15d0, 0x15e5, 0x15f9,
+ 0x160e, 0x1622, 0x1637, 0x164c, 0x1660, 0x1675, 0x168a, 0x169f, 0x16b4,
+ 0x16c9, 0x16de, 0x16f3, 0x1708, 0x171d, 0x1732, 0x1747, 0x175c, 0x1772,
+ 0x1787, 0x179c, 0x17b2, 0x17c7, 0x17dc, 0x17f2, 0x1808, 0x181d, 0x1833,
+ 0x1848, 0x185e, 0x1874, 0x188a, 0x18a0, 0x18b5, 0x18cb, 0x18e1, 0x18f7,
+ 0x190d, 0x1923, 0x193a, 0x1950, 0x1966, 0x197c, 0x1993, 0x19a9, 0x19bf,
+ 0x19d6, 0x19ec, 0x1a03, 0x1a19, 0x1a30, 0x1a47, 0x1a5d, 0x1a74, 0x1a8b,
+ 0x1aa2, 0x1ab8, 0x1acf, 0x1ae6, 0x1afd, 0x1b14, 0x1b2b, 0x1b42, 0x1b59,
+ 0x1b71, 0x1b88, 0x1b9f, 0x1bb6, 0x1bce, 0x1be5, 0x1bfd, 0x1c14, 0x1c2c,
+ 0x1c43, 0x1c5b, 0x1c72, 0x1c8a, 0x1ca2, 0x1cba, 0x1cd2, 0x1ce9, 0x1d01,
+ 0x1d19, 0x1d31, 0x1d49, 0x1d61, 0x1d79, 0x1d92, 0x1daa, 0x1dc2, 0x1dda,
+ 0x1df3, 0x1e0b, 0x1e23, 0x1e3c, 0x1e54, 0x1e6d, 0x1e86, 0x1e9e, 0x1eb7,
+ 0x1ed0, 0x1ee8, 0x1f01, 0x1f1a, 0x1f33, 0x1f4c, 0x1f65, 0x1f7e, 0x1f97,
+ 0x1fb0, 0x1fc9, 0x1fe2, 0x1ffb, 0x2015, 0x202e, 0x2047, 0x2061, 0x207a,
+ 0x2094, 0x20ad, 0x20c7, 0x20e0, 0x20fa, 0x2114, 0x212d, 0x2147, 0x2161,
+ 0x217b, 0x2195, 0x21af, 0x21c9, 0x21e3, 0x21fd, 0x2217, 0x2231, 0x224b,
+ 0x2266, 0x2280, 0x229a, 0x22b5, 0x22cf, 0x22ea, 0x2304, 0x231f, 0x2339,
+ 0x2354, 0x236f, 0x2389, 0x23a4, 0x23bf, 0x23da, 0x23f5, 0x2410, 0x242b,
+ 0x2446, 0x2461, 0x247c, 0x2497, 0x24b2, 0x24ce, 0x24e9, 0x2504, 0x2520,
+ 0x253b, 0x2556, 0x2572, 0x258e, 0x25a9, 0x25c5, 0x25e0, 0x25fc, 0x2618,
+ 0x2634, 0x2650, 0x266c, 0x2687, 0x26a3, 0x26c0, 0x26dc, 0x26f8, 0x2714,
+ 0x2730, 0x274c, 0x2769, 0x2785, 0x27a1, 0x27be, 0x27da, 0x27f7, 0x2813,
+ 0x2830, 0x284d, 0x2869, 0x2886, 0x28a3, 0x28c0, 0x28dc, 0x28f9, 0x2916,
+ 0x2933, 0x2950, 0x296d, 0x298b, 0x29a8, 0x29c5, 0x29e2, 0x2a00, 0x2a1d,
+ 0x2a3a, 0x2a58, 0x2a75, 0x2a93, 0x2ab0, 0x2ace, 0x2aec, 0x2b09, 0x2b27,
+ 0x2b45, 0x2b63, 0x2b81, 0x2b9e, 0x2bbc, 0x2bda, 0x2bf8, 0x2c17, 0x2c35,
+ 0x2c53, 0x2c71, 0x2c8f, 0x2cae, 0x2ccc, 0x2ceb, 0x2d09, 0x2d27, 0x2d46,
+ 0x2d65, 0x2d83, 0x2da2, 0x2dc1, 0x2ddf, 0x2dfe, 0x2e1d, 0x2e3c, 0x2e5b,
+ 0x2e7a, 0x2e99, 0x2eb8, 0x2ed7, 0x2ef6, 0x2f15, 0x2f35, 0x2f54, 0x2f73,
+ 0x2f93, 0x2fb2, 0x2fd2, 0x2ff1, 0x3011, 0x3030, 0x3050, 0x3070, 0x308f,
+ 0x30af, 0x30cf, 0x30ef, 0x310f, 0x312f, 0x314f, 0x316f, 0x318f, 0x31af,
+ 0x31cf, 0x31f0, 0x3210, 0x3230, 0x3251, 0x3271, 0x3291, 0x32b2, 0x32d2,
+ 0x32f3, 0x3314, 0x3334, 0x3355, 0x3376, 0x3397, 0x33b8, 0x33d8, 0x33f9,
+ 0x341a, 0x343b, 0x345d, 0x347e, 0x349f, 0x34c0, 0x34e1, 0x3503, 0x3524,
+ 0x3545, 0x3567, 0x3588, 0x35aa, 0x35cb, 0x35ed, 0x360f, 0x3631, 0x3652,
+ 0x3674, 0x3696, 0x36b8, 0x36da, 0x36fc, 0x371e, 0x3740, 0x3762, 0x3784,
+ 0x37a6, 0x37c9, 0x37eb, 0x380d, 0x3830, 0x3852, 0x3875, 0x3897, 0x38ba,
+ 0x38dc, 0x38ff, 0x3922, 0x3945, 0x3967, 0x398a, 0x39ad, 0x39d0, 0x39f3,
+ 0x3a16, 0x3a39, 0x3a5c, 0x3a7f, 0x3aa3, 0x3ac6, 0x3ae9, 0x3b0d, 0x3b30,
+ 0x3b53, 0x3b77, 0x3b9a, 0x3bbe, 0x3be2, 0x3c05, 0x3c29, 0x3c4d, 0x3c71,
+ 0x3c94, 0x3cb8, 0x3cdc, 0x3d00, 0x3d24, 0x3d48, 0x3d6c, 0x3d91, 0x3db5,
+ 0x3dd9, 0x3dfd, 0x3e22, 0x3e46, 0x3e6b, 0x3e8f, 0x3eb4, 0x3ed8, 0x3efd,
+ 0x3f21, 0x3f46, 0x3f6b, 0x3f90, 0x3fb5, 0x3fda, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x0},
+ {0x3, 0x0, 0xf78, 0x137a, 0x15ea, 0x177a, 0x18b7, 0x19ea, 0x1aaa,
+ 0x1b7a, 0x1c33, 0x1cb7, 0x1d4a, 0x1dea, 0x1e4c, 0x1eaa, 0x1f0e, 0x1f7a,
+ 0x1fed, 0x2033, 0x2074, 0x20b7, 0x20ff, 0x214a, 0x2198, 0x21ea, 0x221f,
+ 0x224c, 0x227a, 0x22aa, 0x22db, 0x230e, 0x2343, 0x237a, 0x23b3, 0x23ed,
+ 0x2414, 0x2433, 0x2453, 0x2474, 0x2495, 0x24b7, 0x24db, 0x24ff, 0x2524,
+ 0x254a, 0x2570, 0x2598, 0x25c0, 0x25ea, 0x260a, 0x261f, 0x2635, 0x264c,
+ 0x2662, 0x267a, 0x2691, 0x26aa, 0x26c2, 0x26db, 0x26f5, 0x270e, 0x2729,
+ 0x2743, 0x275f, 0x277a, 0x2796, 0x27b3, 0x27d0, 0x27ed, 0x2805, 0x2814,
+ 0x2824, 0x2833, 0x2843, 0x2853, 0x2863, 0x2874, 0x2884, 0x2895, 0x28a6,
+ 0x28b7, 0x28c9, 0x28db, 0x28ed, 0x28ff, 0x2911, 0x2924, 0x2937, 0x294a,
+ 0x295d, 0x2970, 0x2984, 0x2998, 0x29ac, 0x29c0, 0x29d5, 0x29ea, 0x29ff,
+ 0x2a0a, 0x2a14, 0x2a1f, 0x2a2a, 0x2a35, 0x2a40, 0x2a4c, 0x2a57, 0x2a62,
+ 0x2a6e, 0x2a7a, 0x2a85, 0x2a91, 0x2a9d, 0x2aaa, 0x2ab6, 0x2ac2, 0x2acf,
+ 0x2adb, 0x2ae8, 0x2af5, 0x2b01, 0x2b0e, 0x2b1c, 0x2b29, 0x2b36, 0x2b43,
+ 0x2b51, 0x2b5f, 0x2b6c, 0x2b7a, 0x2b88, 0x2b96, 0x2ba5, 0x2bb3, 0x2bc1,
+ 0x2bd0, 0x2bde, 0x2bed, 0x2bfc, 0x2c05, 0x2c0d, 0x2c14, 0x2c1c, 0x2c24,
+ 0x2c2b, 0x2c33, 0x2c3b, 0x2c43, 0x2c4b, 0x2c53, 0x2c5b, 0x2c63, 0x2c6b,
+ 0x2c74, 0x2c7c, 0x2c84, 0x2c8d, 0x2c95, 0x2c9e, 0x2ca6, 0x2caf, 0x2cb7,
+ 0x2cc0, 0x2cc9, 0x2cd2, 0x2cdb, 0x2ce4, 0x2ced, 0x2cf6, 0x2cff, 0x2d08,
+ 0x2d11, 0x2d1a, 0x2d24, 0x2d2d, 0x2d37, 0x2d40, 0x2d4a, 0x2d53, 0x2d5d,
+ 0x2d67, 0x2d70, 0x2d7a, 0x2d84, 0x2d8e, 0x2d98, 0x2da2, 0x2dac, 0x2db6,
+ 0x2dc0, 0x2dcb, 0x2dd5, 0x2ddf, 0x2dea, 0x2df4, 0x2dff, 0x2e04, 0x2e0a,
+ 0x2e0f, 0x2e14, 0x2e1a, 0x2e1f, 0x2e25, 0x2e2a, 0x2e30, 0x2e35, 0x2e3b,
+ 0x2e40, 0x2e46, 0x2e4c, 0x2e51, 0x2e57, 0x2e5d, 0x2e62, 0x2e68, 0x2e6e,
+ 0x2e74, 0x2e7a, 0x2e80, 0x2e85, 0x2e8b, 0x2e91, 0x2e97, 0x2e9d, 0x2ea3,
+ 0x2eaa, 0x2eb0, 0x2eb6, 0x2ebc, 0x2ec2, 0x2ec8, 0x2ecf, 0x2ed5, 0x2edb,
+ 0x2ee1, 0x2ee8, 0x2eee, 0x2ef5, 0x2efb, 0x2f01, 0x2f08, 0x2f0e, 0x2f15,
+ 0x2f1c, 0x2f22, 0x2f29, 0x2f2f, 0x2f36, 0x2f3d, 0x2f43, 0x2f4a, 0x2f51,
+ 0x2f58, 0x2f5f, 0x2f66, 0x2f6c, 0x2f73, 0x2f7a, 0x2f81, 0x2f88, 0x2f8f,
+ 0x2f96, 0x2f9d, 0x2fa5, 0x2fac, 0x2fb3, 0x2fba, 0x2fc1, 0x2fc8, 0x2fd0,
+ 0x2fd7, 0x2fde, 0x2fe6, 0x2fed, 0x2ff5, 0x2ffc, 0x3001, 0x3005, 0x3009,
+ 0x300d, 0x3011, 0x3014, 0x3018, 0x301c, 0x3020, 0x3024, 0x3028, 0x302b,
+ 0x302f, 0x3033, 0x3037, 0x303b, 0x303f, 0x3043, 0x3047, 0x304b, 0x304f,
+ 0x3053, 0x3057, 0x305b, 0x305f, 0x3063, 0x3067, 0x306b, 0x306f, 0x3074,
+ 0x3078, 0x307c, 0x3080, 0x3084, 0x3088, 0x308d, 0x3091, 0x3095, 0x3099,
+ 0x309e, 0x30a2, 0x30a6, 0x30aa, 0x30af, 0x30b3, 0x30b7, 0x30bc, 0x30c0,
+ 0x30c5, 0x30c9, 0x30cd, 0x30d2, 0x30d6, 0x30db, 0x30df, 0x30e4, 0x30e8,
+ 0x30ed, 0x30f1, 0x30f6, 0x30fa, 0x30ff, 0x3103, 0x3108, 0x310d, 0x3111,
+ 0x3116, 0x311a, 0x311f, 0x3124, 0x3128, 0x312d, 0x3132, 0x3137, 0x313b,
+ 0x3140, 0x3145, 0x314a, 0x314e, 0x3153, 0x3158, 0x315d, 0x3162, 0x3167,
+ 0x316b, 0x3170, 0x3175, 0x317a, 0x317f, 0x3184, 0x3189, 0x318e, 0x3193,
+ 0x3198, 0x319d, 0x31a2, 0x31a7, 0x31ac, 0x31b1, 0x31b6, 0x31bb, 0x31c0,
+ 0x31c5, 0x31cb, 0x31d0, 0x31d5, 0x31da, 0x31df, 0x31e4, 0x31ea, 0x31ef,
+ 0x31f4, 0x31f9, 0x31ff, 0x3202, 0x3204, 0x3207, 0x320a, 0x320c, 0x320f,
+ 0x3212, 0x3214, 0x3217, 0x321a, 0x321c, 0x321f, 0x3222, 0x3225, 0x3227,
+ 0x322a, 0x322d, 0x3230, 0x3232, 0x3235, 0x3238, 0x323b, 0x323e, 0x3240,
+ 0x3243, 0x3246, 0x3249, 0x324c, 0x324e, 0x3251, 0x3254, 0x3257, 0x325a,
+ 0x325d, 0x3260, 0x3262, 0x3265, 0x3268, 0x326b, 0x326e, 0x3271, 0x3274,
+ 0x3277, 0x327a, 0x327d, 0x3280, 0x3282, 0x3285, 0x3288, 0x328b, 0x328e,
+ 0x3291, 0x3294, 0x3297, 0x329a, 0x329d, 0x32a0, 0x32a3, 0x32a6, 0x32aa,
+ 0x32ad, 0x32b0, 0x32b3, 0x32b6, 0x32b9, 0x32bc, 0x32bf, 0x32c2, 0x32c6,
+ 0x32c9, 0x32cc, 0x32cf, 0x32d3, 0x32d6, 0x32d9, 0x32dd, 0x32e0, 0x32e4,
+ 0x32e7, 0x32ea, 0x32ee, 0x32f1, 0x32f5, 0x32f8, 0x32fc, 0x32ff, 0x3303,
+ 0x3307, 0x330a, 0x330e, 0x3312, 0x3315, 0x3319, 0x331d, 0x3321, 0x3324,
+ 0x3328, 0x332c, 0x3330, 0x3334, 0x3338, 0x333c, 0x3340, 0x3344, 0x3348,
+ 0x334c, 0x3350, 0x3354, 0x3358, 0x335c, 0x3360, 0x3365, 0x3369, 0x336d,
+ 0x3371, 0x3376, 0x337a, 0x337e, 0x3383, 0x3387, 0x338c, 0x3390, 0x3394,
+ 0x3399, 0x339e, 0x33a2, 0x33a7, 0x33ab, 0x33b0, 0x33b5, 0x33b9, 0x33be,
+ 0x33c3, 0x33c8, 0x33cd, 0x33d2, 0x33d6, 0x33db, 0x33e0, 0x33e5, 0x33ea,
+ 0x33ef, 0x33f5, 0x33fa, 0x33ff, 0x3402, 0x3404, 0x3407, 0x340a, 0x340c,
+ 0x340f, 0x3412, 0x3414, 0x3417, 0x341a, 0x341d, 0x341f, 0x3422, 0x3425,
+ 0x3428, 0x342b, 0x342e, 0x3430, 0x3433, 0x3436, 0x3439, 0x343c, 0x343f,
+ 0x3442, 0x3445, 0x3448, 0x344b, 0x344e, 0x3451, 0x3455, 0x3458, 0x345b,
+ 0x345e, 0x3461, 0x3464, 0x3468, 0x346b, 0x346e, 0x3471, 0x3475, 0x3478,
+ 0x347b, 0x347f, 0x3482, 0x3486, 0x3489, 0x348d, 0x3490, 0x3493, 0x3497,
+ 0x349b, 0x349e, 0x34a2, 0x34a5, 0x34a9, 0x34ad, 0x34b0, 0x34b4, 0x34b8,
+ 0x34bc, 0x34bf, 0x34c3, 0x34c7, 0x34cb, 0x34cf, 0x34d3, 0x34d6, 0x34da,
+ 0x34de, 0x34e2, 0x34e6, 0x34ea, 0x34ee, 0x34f3, 0x34f7, 0x34fb, 0x34ff,
+ 0x3503, 0x3507, 0x350c, 0x3510, 0x3514, 0x3519, 0x351d, 0x3521, 0x3526,
+ 0x352a, 0x352f, 0x3533, 0x3538, 0x353c, 0x3541, 0x3545, 0x354a, 0x354f,
+ 0x3553, 0x3558, 0x355d, 0x3562, 0x3566, 0x356b, 0x3570, 0x3575, 0x357a,
+ 0x357f, 0x3584, 0x3589, 0x358e, 0x3593, 0x3598, 0x359d, 0x35a3, 0x35a8,
+ 0x35ad, 0x35b2, 0x35b8, 0x35bd, 0x35c2, 0x35c8, 0x35cd, 0x35d3, 0x35d8,
+ 0x35de, 0x35e4, 0x35e9, 0x35ef, 0x35f4, 0x35fa, 0x3600, 0x3603, 0x3606,
+ 0x3609, 0x360b, 0x360e, 0x3611, 0x3614, 0x3617, 0x361b, 0x361e, 0x3621,
+ 0x3624, 0x3627, 0x362a, 0x362d, 0x3630, 0x3634, 0x3637, 0x363a, 0x363d,
+ 0x3641, 0x3644, 0x3647, 0x364b, 0x364e, 0x3651, 0x3655, 0x3658, 0x365c,
+ 0x365f, 0x3663, 0x3666, 0x366a, 0x366d, 0x3671, 0x3675, 0x3678, 0x367c,
+ 0x3680, 0x3683, 0x3687, 0x368b, 0x368e, 0x3692, 0x3696, 0x369a, 0x369e,
+ 0x36a2, 0x36a6, 0x36aa, 0x36ae, 0x36b2, 0x36b6, 0x36ba, 0x36be, 0x36c2,
+ 0x36c6, 0x36ca, 0x36ce, 0x36d2, 0x36d7, 0x36db, 0x36df, 0x36e3, 0x36e8,
+ 0x36ec, 0x36f0, 0x36f5, 0x36f9, 0x36fe, 0x3702, 0x3707, 0x370b, 0x3710,
+ 0x3715, 0x3719, 0x371e, 0x3723, 0x3727, 0x372c, 0x3731, 0x3736, 0x373a,
+ 0x373f, 0x3744, 0x3749, 0x374e, 0x3753, 0x3758, 0x375d, 0x3762, 0x3767,
+ 0x376d, 0x3772, 0x3777, 0x377c, 0x3782, 0x3787, 0x378c, 0x3792, 0x3797,
+ 0x379c, 0x37a2, 0x37a7, 0x37ad, 0x37b3, 0x37b8, 0x37be, 0x37c4, 0x37c9,
+ 0x37cf, 0x37d5, 0x37db, 0x37e1, 0x37e7, 0x37ec, 0x37f2, 0x37f8, 0x37ff,
+ 0x3802, 0x3805, 0x3808, 0x380b, 0x380e, 0x3812, 0x3815, 0x3818, 0x381b,
+ 0x381e, 0x3822, 0x3825, 0x3828, 0x382c, 0x382f, 0x3832, 0x3836, 0x3839,
+ 0x383c, 0x3840, 0x3843, 0x3847, 0x384a, 0x384e, 0x3851, 0x3855, 0x3858,
+ 0x385c, 0x3860, 0x3863, 0x3867, 0x386b, 0x386e, 0x3872, 0x3876, 0x387a,
+ 0x387e, 0x3881, 0x3885, 0x3889, 0x388d, 0x3891, 0x3895, 0x3899, 0x389d,
+ 0x38a1, 0x38a5, 0x38a9, 0x38ad, 0x38b1, 0x38b6, 0x38ba, 0x38be, 0x38c2,
+ 0x38c7, 0x38cb, 0x38cf, 0x38d4, 0x38d8, 0x38dc, 0x38e1, 0x38e5, 0x38ea,
+ 0x38ee, 0x38f3, 0x38f7, 0x38fc, 0x3901, 0x3905, 0x390a, 0x390f, 0x3913,
+ 0x3918, 0x391d, 0x3922, 0x3927, 0x392c, 0x3931, 0x3936, 0x393b, 0x3940,
+ 0x3945, 0x394a, 0x394f, 0x3954, 0x3959, 0x395e, 0x3964, 0x3969, 0x396e,
+ 0x3974, 0x3979, 0x397e, 0x3984, 0x3989, 0x398f, 0x3994, 0x399a, 0x39a0,
+ 0x39a5, 0x39ab, 0x39b1, 0x39b6, 0x39bc, 0x39c2, 0x39c8, 0x39ce, 0x39d4,
+ 0x39da, 0x39e0, 0x39e6, 0x39ec, 0x39f2, 0x39f8, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x3},
+};
+
+static const u32 dcss_cscbs[5][29] = {
+ {0x8000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0},
+ {0x3, 0x503c, 0x2a1c, 0x58a, 0x8d7, 0x7598, 0x174, 0x219, 0xb42,
+ 0x7288, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3fff, 0x3fff, 0x3fff, 0x1, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0xffc0000, 0xffc0000, 0xffc0000},
+ {0x3, 0x53c5, 0x26a5, 0x57a, 0x93a, 0x7539, 0x170, 0x231, 0xc84,
+ 0x712f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3fff, 0x3fff, 0x3fff, 0x1, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0xffc0000, 0xffc0000, 0xffc0000},
+ {0x3, 0x42d2, 0xfffffd30, 0x0, 0x0, 0x4000, 0x0, 0x0, 0xc2, 0x3f3f,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3fff, 0x3fff, 0x3fff, 0xe, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x3fff, 0x3fff, 0x3fff},
+ {0x3, 0x3d4d, 0x2b4, 0x0, 0x0, 0x4000, 0x0, 0x0, 0xffffff3e, 0x40c4,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3fff, 0x3fff, 0x3fff, 0xe, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x3fff, 0x3fff, 0x3fff},
+};
+
+static const u16 dcss_oluts[11][1024] = {
+ {0x155, 0xe, 0x13ef, 0x1, 0x4f, 0x536, 0x3fff, 0x0, 0x5, 0x23, 0xa7,
+ 0x2a7, 0xa2f, 0x2758, 0x3fff, 0x0, 0x0, 0x2, 0x9, 0x17, 0x35, 0x73,
+ 0xef, 0x1e2, 0x3b8, 0x749, 0xe3d, 0x1bf7, 0x378e, 0x3fff, 0x3fff, 0x0,
+ 0x0, 0x0, 0x1, 0x2, 0x3, 0x6, 0xb, 0x12, 0x1c, 0x2b, 0x41, 0x60, 0x8b,
+ 0xc8, 0x11e, 0x195, 0x23c, 0x324, 0x467, 0x629, 0x89d, 0xc0a, 0x10d8,
+ 0x179b, 0x2128, 0x2ebb, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x2, 0x3, 0x4, 0x6, 0x7, 0xa,
+ 0xd, 0x10, 0x14, 0x19, 0x20, 0x27, 0x30, 0x3b, 0x47, 0x57, 0x69, 0x7f,
+ 0x98, 0xb7, 0xdb, 0x106, 0x138, 0x174, 0x1ba, 0x20d, 0x26f, 0x2e3,
+ 0x36b, 0x40c, 0x4ca, 0x5aa, 0x6b3, 0x7ec, 0x95d, 0xb12, 0xd18, 0xf7c,
+ 0x1253, 0x15b1, 0x19b1, 0x1e73, 0x241e, 0x2ae0, 0x32f2, 0x3c9a, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
+ 0x1, 0x1, 0x1, 0x2, 0x2, 0x3, 0x3, 0x4, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9,
+ 0xa, 0xc, 0xd, 0xf, 0x11, 0x13, 0x15, 0x18, 0x1b, 0x1e, 0x21, 0x25,
+ 0x29, 0x2e, 0x32, 0x38, 0x3e, 0x44, 0x4b, 0x53, 0x5b, 0x64, 0x6e, 0x79,
+ 0x85, 0x92, 0xa0, 0xaf, 0xbf, 0xd1, 0xe5, 0xfa, 0x111, 0x12b, 0x146,
+ 0x164, 0x184, 0x1a7, 0x1ce, 0x1f7, 0x224, 0x255, 0x28a, 0x2c4, 0x303,
+ 0x347, 0x391, 0x3e1, 0x439, 0x498, 0x4ff, 0x56f, 0x5e8, 0x66d, 0x6fc,
+ 0x799, 0x842, 0x8fb, 0x9c4, 0xa9e, 0xb8c, 0xc8e, 0xda7, 0xed9, 0x1027,
+ 0x1191, 0x131d, 0x14cb, 0x16a0, 0x18a0, 0x1ace, 0x1d2e, 0x1fc6, 0x229b,
+ 0x25b2, 0x2912, 0x2cc2, 0x30ca, 0x3533, 0x3a06, 0x3f4d, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1,
+ 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x3, 0x3, 0x3, 0x3, 0x4,
+ 0x4, 0x4, 0x5, 0x5, 0x5, 0x6, 0x6, 0x7, 0x7, 0x8, 0x8, 0x9, 0x9, 0xa,
+ 0xb, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x10, 0x11, 0x13, 0x14, 0x15, 0x16,
+ 0x17, 0x19, 0x1a, 0x1c, 0x1d, 0x1f, 0x20, 0x22, 0x24, 0x26, 0x28, 0x2a,
+ 0x2c, 0x2f, 0x31, 0x34, 0x36, 0x39, 0x3c, 0x3f, 0x42, 0x46, 0x49, 0x4d,
+ 0x51, 0x55, 0x59, 0x5d, 0x62, 0x67, 0x6c, 0x71, 0x76, 0x7c, 0x82, 0x88,
+ 0x8e, 0x95, 0x9c, 0xa3, 0xab, 0xb3, 0xbb, 0xc4, 0xcd, 0xd6, 0xe0, 0xea,
+ 0xf5, 0x100, 0x10b, 0x118, 0x124, 0x131, 0x13f, 0x14d, 0x15c, 0x16c,
+ 0x17c, 0x18d, 0x19e, 0x1b1, 0x1c4, 0x1d8, 0x1ec, 0x202, 0x218, 0x230,
+ 0x248, 0x262, 0x27d, 0x298, 0x2b5, 0x2d3, 0x2f3, 0x313, 0x335, 0x359,
+ 0x37e, 0x3a4, 0x3cd, 0x3f7, 0x422, 0x450, 0x47f, 0x4b1, 0x4e4, 0x51a,
+ 0x552, 0x58c, 0x5c9, 0x608, 0x64b, 0x68f, 0x6d7, 0x722, 0x770, 0x7c2,
+ 0x817, 0x86f, 0x8cb, 0x92c, 0x990, 0x9f9, 0xa66, 0xad8, 0xb4e, 0xbca,
+ 0xc4b, 0xcd2, 0xd5f, 0xdf1, 0xe8a, 0xf2a, 0xfd1, 0x107e, 0x1134,
+ 0x11f1, 0x12b7, 0x1385, 0x145c, 0x153d, 0x1627, 0x171c, 0x181c, 0x1927,
+ 0x1a3e, 0x1b61, 0x1c91, 0x1dcf, 0x1f1b, 0x2075, 0x21df, 0x235a, 0x24e5,
+ 0x2683, 0x2833, 0x29f6, 0x2bce, 0x2dbc, 0x2fc0, 0x31db, 0x340f, 0x365d,
+ 0x38c6, 0x3b4c, 0x3def, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1,
+ 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4,
+ 0x4, 0x4, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x6, 0x6, 0x6, 0x6, 0x7, 0x7,
+ 0x7, 0x7, 0x8, 0x8, 0x8, 0x8, 0x9, 0x9, 0x9, 0xa, 0xa, 0xa, 0xb, 0xb,
+ 0xb, 0xc, 0xc, 0xc, 0xd, 0xd, 0xe, 0xe, 0xe, 0xf, 0xf, 0x10, 0x10,
+ 0x11, 0x11, 0x12, 0x12, 0x13, 0x13, 0x14, 0x15, 0x15, 0x16, 0x16, 0x17,
+ 0x18, 0x18, 0x19, 0x1a, 0x1a, 0x1b, 0x1c, 0x1d, 0x1d, 0x1e, 0x1f, 0x20,
+ 0x21, 0x22, 0x23, 0x24, 0x24, 0x25, 0x26, 0x27, 0x29, 0x2a, 0x2b, 0x2c,
+ 0x2d, 0x2e, 0x2f, 0x31, 0x32, 0x33, 0x34, 0x36, 0x37, 0x39, 0x3a, 0x3b,
+ 0x3d, 0x3e, 0x40, 0x42, 0x43, 0x45, 0x47, 0x48, 0x4a, 0x4c, 0x4e, 0x50,
+ 0x52, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e, 0x61, 0x63, 0x65, 0x68, 0x6a,
+ 0x6d, 0x6f, 0x72, 0x75, 0x77, 0x7a, 0x7d, 0x80, 0x83, 0x86, 0x89, 0x8d,
+ 0x90, 0x93, 0x97, 0x9a, 0x9e, 0xa1, 0xa5, 0xa9, 0xad, 0xb1, 0xb5, 0xb9,
+ 0xbd, 0xc1, 0xc6, 0xca, 0xcf, 0xd4, 0xd8, 0xdd, 0xe2, 0xe7, 0xed, 0xf2,
+ 0xf7, 0xfd, 0x103, 0x108, 0x10e, 0x114, 0x11b, 0x121, 0x127, 0x12e,
+ 0x135, 0x13b, 0x142, 0x14a, 0x151, 0x158, 0x160, 0x168, 0x170, 0x178,
+ 0x180, 0x188, 0x191, 0x19a, 0x1a3, 0x1ac, 0x1b5, 0x1bf, 0x1c9, 0x1d3,
+ 0x1dd, 0x1e7, 0x1f2, 0x1fc, 0x207, 0x213, 0x21e, 0x22a, 0x236, 0x242,
+ 0x24f, 0x25b, 0x268, 0x276, 0x283, 0x291, 0x29f, 0x2ae, 0x2bd, 0x2cc,
+ 0x2db, 0x2eb, 0x2fb, 0x30b, 0x31c, 0x32d, 0x33e, 0x350, 0x362, 0x374,
+ 0x387, 0x39b, 0x3ae, 0x3c2, 0x3d7, 0x3ec, 0x401, 0x417, 0x42d, 0x444,
+ 0x45b, 0x473, 0x48b, 0x4a4, 0x4bd, 0x4d7, 0x4f1, 0x50c, 0x528, 0x544,
+ 0x560, 0x57d, 0x59b, 0x5ba, 0x5d9, 0x5f8, 0x619, 0x63a, 0x65c, 0x67e,
+ 0x6a1, 0x6c5, 0x6ea, 0x70f, 0x735, 0x75c, 0x784, 0x7ad, 0x7d7, 0x801,
+ 0x82c, 0x859, 0x886, 0x8b4, 0x8e3, 0x913, 0x944, 0x977, 0x9aa, 0x9de,
+ 0xa14, 0xa4a, 0xa82, 0xabb, 0xaf5, 0xb30, 0xb6d, 0xbab, 0xbea, 0xc2b,
+ 0xc6d, 0xcb0, 0xcf5, 0xd3b, 0xd83, 0xdcc, 0xe17, 0xe63, 0xeb2, 0xf01,
+ 0xf53, 0xfa6, 0xffb, 0x1052, 0x10ab, 0x1106, 0x1162, 0x11c1, 0x1222,
+ 0x1284, 0x12e9, 0x1350, 0x13ba, 0x1425, 0x1493, 0x1504, 0x1576, 0x15ec,
+ 0x1664, 0x16de, 0x175b, 0x17db, 0x185e, 0x18e3, 0x196b, 0x19f7, 0x1a85,
+ 0x1b17, 0x1bac, 0x1c44, 0x1cdf, 0x1d7e, 0x1e20, 0x1ec6, 0x1f70, 0x201d,
+ 0x20ce, 0x2183, 0x223d, 0x22fa, 0x23bb, 0x2481, 0x254b, 0x261a, 0x26ed,
+ 0x27c5, 0x28a2, 0x2983, 0x2a6a, 0x2b56, 0x2c47, 0x2d3e, 0x2e3a, 0x2f3c,
+ 0x3044, 0x3152, 0x3266, 0x3380, 0x34a0, 0x35c7, 0x36f5, 0x3829, 0x3965,
+ 0x3aa8, 0x3bf2, 0x3d44, 0x3e9d, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x6},
+ {0x2c59, 0x2416, 0x3322, 0x1dd0, 0x287e, 0x2ff0, 0x3679, 0x188b,
+ 0x2131, 0x2661, 0x2a78, 0x2e2b, 0x3186, 0x34c8, 0x3836, 0x141d, 0x1b81,
+ 0x1fa3, 0x22a3, 0x2531, 0x277a, 0x2988, 0x2b6b, 0x2d33, 0x2eed, 0x30a3,
+ 0x325a, 0x3416, 0x35b5, 0x374e, 0x38f8, 0xff8, 0x1698, 0x1a32, 0x1ca3,
+ 0x1eab, 0x206e, 0x2210, 0x2355, 0x2497, 0x25e9, 0x26e2, 0x2816, 0x28f9,
+ 0x2a17, 0x2ae8, 0x2c01, 0x2cbe, 0x2dbb, 0x2e85, 0x2f65, 0x3047, 0x310d,
+ 0x3209, 0x32b7, 0x339e, 0x3469, 0x3536, 0x3624, 0x36db, 0x37d2, 0x388f,
+ 0x3971, 0xbe0, 0x124a, 0x1572, 0x17b5, 0x1962, 0x1acc, 0x1c29, 0x1d30,
+ 0x1e43, 0x1f20, 0x201b, 0x20ca, 0x21a2, 0x2256, 0x22f8, 0x23bc, 0x2454,
+ 0x24e1, 0x2589, 0x2629, 0x269f, 0x272b, 0x27d0, 0x2848, 0x28b9, 0x293d,
+ 0x29d8, 0x2a45, 0x2aae, 0x2b27, 0x2bb4, 0x2c2b, 0x2c89, 0x2cf6, 0x2d74,
+ 0x2e03, 0x2e57, 0x2eb8, 0x2f27, 0x2fa8, 0x301e, 0x3073, 0x30d6, 0x3147,
+ 0x31ca, 0x3230, 0x3287, 0x32eb, 0x335e, 0x33e3, 0x343e, 0x3497, 0x34fd,
+ 0x3573, 0x35fc, 0x364d, 0x36a8, 0x3712, 0x378d, 0x380e, 0x3861, 0x38c1,
+ 0x3932, 0x39b5, 0x700, 0xe30, 0x1120, 0x132c, 0x14bb, 0x1621, 0x171f,
+ 0x182e, 0x18f2, 0x19de, 0x1a7c, 0x1b23, 0x1be6, 0x1c64, 0x1ce7, 0x1d7d,
+ 0x1e14, 0x1e75, 0x1ee3, 0x1f60, 0x1feb, 0x2043, 0x209b, 0x20fc, 0x2168,
+ 0x21e0, 0x2232, 0x227b, 0x22cc, 0x2325, 0x2387, 0x23f3, 0x2434, 0x2475,
+ 0x24bb, 0x2508, 0x255d, 0x25b8, 0x260e, 0x2644, 0x2680, 0x26c0, 0x2706,
+ 0x2752, 0x27a4, 0x27fd, 0x282f, 0x2863, 0x289b, 0x28d8, 0x291a, 0x2962,
+ 0x29af, 0x2a01, 0x2a2e, 0x2a5e, 0x2a92, 0x2aca, 0x2b07, 0x2b48, 0x2b8f,
+ 0x2bda, 0x2c16, 0x2c41, 0x2c70, 0x2ca3, 0x2cda, 0x2d14, 0x2d53, 0x2d97,
+ 0x2de0, 0x2e17, 0x2e41, 0x2e6e, 0x2e9e, 0x2ed2, 0x2f0a, 0x2f46, 0x2f86,
+ 0x2fcb, 0x300a, 0x3032, 0x305d, 0x308b, 0x30bc, 0x30f1, 0x3129, 0x3166,
+ 0x31a8, 0x31ee, 0x321c, 0x3245, 0x3270, 0x329f, 0x32d1, 0x3306, 0x3340,
+ 0x337e, 0x33c0, 0x3404, 0x342a, 0x3453, 0x347f, 0x34af, 0x34e2, 0x3519,
+ 0x3554, 0x3593, 0x35d8, 0x3610, 0x3638, 0x3662, 0x3690, 0x36c1, 0x36f6,
+ 0x372f, 0x376d, 0x37af, 0x37f7, 0x3821, 0x384b, 0x3878, 0x38a8, 0x38dc,
+ 0x3914, 0x3951, 0x3992, 0x39d9, 0x200, 0xa20, 0xd00, 0xf00, 0x1084,
+ 0x11d0, 0x12b6, 0x13ae, 0x1469, 0x1513, 0x15d7, 0x165b, 0x16da, 0x1768,
+ 0x1803, 0x185b, 0x18bd, 0x1929, 0x199f, 0x1a10, 0x1a56, 0x1aa3, 0x1af6,
+ 0x1b51, 0x1bb2, 0x1c0e, 0x1c46, 0x1c83, 0x1cc5, 0x1d0b, 0x1d56, 0x1da6,
+ 0x1dfc, 0x1e2b, 0x1e5c, 0x1e90, 0x1ec7, 0x1f01, 0x1f3f, 0x1f81, 0x1fc7,
+ 0x2008, 0x202f, 0x2058, 0x2084, 0x20b2, 0x20e3, 0x2116, 0x214c, 0x2185,
+ 0x21c1, 0x2200, 0x2221, 0x2244, 0x2268, 0x228f, 0x22b7, 0x22e2, 0x230e,
+ 0x233d, 0x236e, 0x23a1, 0x23d7, 0x2407, 0x2425, 0x2444, 0x2464, 0x2486,
+ 0x24a9, 0x24ce, 0x24f4, 0x251d, 0x2547, 0x2573, 0x25a1, 0x25d1, 0x2601,
+ 0x261b, 0x2636, 0x2653, 0x2670, 0x268f, 0x26af, 0x26d1, 0x26f4, 0x2718,
+ 0x273e, 0x2766, 0x278f, 0x27b9, 0x27e6, 0x280a, 0x2822, 0x283b, 0x2855,
+ 0x2870, 0x288d, 0x28aa, 0x28c9, 0x28e8, 0x2909, 0x292c, 0x294f, 0x2975,
+ 0x299b, 0x29c3, 0x29ed, 0x2a0c, 0x2a22, 0x2a39, 0x2a52, 0x2a6b, 0x2a85,
+ 0x2aa0, 0x2abc, 0x2ad9, 0x2af7, 0x2b17, 0x2b38, 0x2b59, 0x2b7d, 0x2ba1,
+ 0x2bc7, 0x2bee, 0x2c0b, 0x2c20, 0x2c36, 0x2c4d, 0x2c64, 0x2c7d, 0x2c96,
+ 0x2cb0, 0x2ccc, 0x2ce8, 0x2d05, 0x2d24, 0x2d43, 0x2d64, 0x2d85, 0x2da9,
+ 0x2dcd, 0x2df3, 0x2e0d, 0x2e21, 0x2e36, 0x2e4c, 0x2e62, 0x2e79, 0x2e92,
+ 0x2eab, 0x2ec5, 0x2ee0, 0x2efb, 0x2f18, 0x2f36, 0x2f55, 0x2f76, 0x2f97,
+ 0x2fb9, 0x2fdd, 0x3001, 0x3014, 0x3028, 0x303d, 0x3052, 0x3068, 0x307f,
+ 0x3097, 0x30af, 0x30c9, 0x30e3, 0x30ff, 0x311b, 0x3138, 0x3157, 0x3176,
+ 0x3197, 0x31b9, 0x31dc, 0x3200, 0x3213, 0x3226, 0x323a, 0x324f, 0x3265,
+ 0x327b, 0x3293, 0x32ab, 0x32c4, 0x32de, 0x32f8, 0x3314, 0x3331, 0x334f,
+ 0x336e, 0x338e, 0x33af, 0x33d2, 0x33f5, 0x340d, 0x3420, 0x3434, 0x3448,
+ 0x345e, 0x3474, 0x348b, 0x34a3, 0x34bb, 0x34d5, 0x34ef, 0x350b, 0x3527,
+ 0x3545, 0x3563, 0x3583, 0x35a4, 0x35c6, 0x35ea, 0x3607, 0x361a, 0x362e,
+ 0x3642, 0x3657, 0x366d, 0x3684, 0x369c, 0x36b5, 0x36ce, 0x36e9, 0x3704,
+ 0x3721, 0x373e, 0x375d, 0x377d, 0x379e, 0x37c1, 0x37e4, 0x3804, 0x3818,
+ 0x382c, 0x3840, 0x3856, 0x386c, 0x3883, 0x389b, 0x38b5, 0x38cf, 0x38ea,
+ 0x3906, 0x3923, 0x3941, 0x3961, 0x3981, 0x39a3, 0x39c7, 0x39ec, 0x0,
+ 0x500, 0x8c0, 0xae0, 0xc70, 0xdb0, 0xe98, 0xf78, 0x103c, 0x10d0,
+ 0x1178, 0x1218, 0x127e, 0x12f0, 0x136c, 0x13f2, 0x1442, 0x1491, 0x14e6,
+ 0x1542, 0x15a4, 0x1606, 0x163e, 0x1679, 0x16b9, 0x16fc, 0x1743, 0x178e,
+ 0x17dd, 0x1818, 0x1844, 0x1873, 0x18a4, 0x18d7, 0x190d, 0x1945, 0x1980,
+ 0x19be, 0x19ff, 0x1a21, 0x1a44, 0x1a69, 0x1a8f, 0x1ab7, 0x1ae1, 0x1b0c,
+ 0x1b39, 0x1b68, 0x1b99, 0x1bcc, 0x1c00, 0x1c1b, 0x1c38, 0x1c55, 0x1c74,
+ 0x1c93, 0x1cb4, 0x1cd6, 0x1cf9, 0x1d1d, 0x1d42, 0x1d69, 0x1d91, 0x1dbb,
+ 0x1de6, 0x1e09, 0x1e20, 0x1e37, 0x1e4f, 0x1e69, 0x1e82, 0x1e9d, 0x1eb9,
+ 0x1ed5, 0x1ef2, 0x1f10, 0x1f2f, 0x1f4f, 0x1f70, 0x1f92, 0x1fb5, 0x1fd9,
+ 0x1ffe, 0x2012, 0x2025, 0x2039, 0x204e, 0x2063, 0x2079, 0x208f, 0x20a6,
+ 0x20be, 0x20d6, 0x20ef, 0x2109, 0x2123, 0x213e, 0x215a, 0x2176, 0x2194,
+ 0x21b2, 0x21d0, 0x21f0, 0x2208, 0x2218, 0x2229, 0x223b, 0x224d, 0x225f,
+ 0x2272, 0x2285, 0x2299, 0x22ad, 0x22c2, 0x22d7, 0x22ed, 0x2303, 0x231a,
+ 0x2331, 0x2349, 0x2361, 0x237a, 0x2394, 0x23ae, 0x23c9, 0x23e5, 0x2400,
+ 0x240f, 0x241d, 0x242c, 0x243c, 0x244c, 0x245c, 0x246c, 0x247d, 0x248e,
+ 0x24a0, 0x24b2, 0x24c4, 0x24d7, 0x24eb, 0x24fe, 0x2512, 0x2527, 0x253c,
+ 0x2552, 0x2568, 0x257e, 0x2595, 0x25ac, 0x25c4, 0x25dd, 0x25f6, 0x2607,
+ 0x2614, 0x2622, 0x262f, 0x263d, 0x264c, 0x265a, 0x2669, 0x2678, 0x2687,
+ 0x2697, 0x26a7, 0x26b8, 0x26c8, 0x26d9, 0x26eb, 0x26fd, 0x270f, 0x2722,
+ 0x2734, 0x2748, 0x275c, 0x2770, 0x2784, 0x2799, 0x27af, 0x27c4, 0x27db,
+ 0x27f1, 0x2804, 0x2810, 0x281c, 0x2828, 0x2835, 0x2842, 0x284f, 0x285c,
+ 0x286a, 0x2877, 0x2886, 0x2894, 0x28a3, 0x28b2, 0x28c1, 0x28d0, 0x28e0,
+ 0x28f1, 0x2901, 0x2912, 0x2923, 0x2935, 0x2946, 0x2959, 0x296b, 0x297e,
+ 0x2991, 0x29a5, 0x29b9, 0x29cd, 0x29e2, 0x29f7, 0x2a06, 0x2a11, 0x2a1c,
+ 0x2a28, 0x2a33, 0x2a3f, 0x2a4b, 0x2a58, 0x2a64, 0x2a71, 0x2a7e, 0x2a8b,
+ 0x2a99, 0x2aa7, 0x2ab5, 0x2ac3, 0x2ad2, 0x2ae1, 0x2af0, 0x2aff, 0x2b0f,
+ 0x2b1f, 0x2b2f, 0x2b40, 0x2b51, 0x2b62, 0x2b74, 0x2b86, 0x2b98, 0x2baa,
+ 0x2bbd, 0x2bd0, 0x2be4, 0x2bf8, 0x2c06, 0x2c10, 0x2c1b, 0x2c26, 0x2c31,
+ 0x2c3c, 0x2c47, 0x2c53, 0x2c5e, 0x2c6a, 0x2c77, 0x2c83, 0x2c90, 0x2c9d,
+ 0x2caa, 0x2cb7, 0x2cc5, 0x2cd3, 0x2ce1, 0x2cef, 0x2cfe, 0x2d0d, 0x2d1c,
+ 0x2d2b, 0x2d3b, 0x2d4b, 0x2d5b, 0x2d6c, 0x2d7d, 0x2d8e, 0x2da0, 0x2db2,
+ 0x2dc4, 0x2dd6, 0x2de9, 0x2dfc, 0x2e08, 0x2e12, 0x2e1c, 0x2e26, 0x2e30,
+ 0x2e3b, 0x2e46, 0x2e51, 0x2e5c, 0x2e68, 0x2e73, 0x2e7f, 0x2e8b, 0x2e98,
+ 0x2ea4, 0x2eb1, 0x2ebe, 0x2ecb, 0x2ed9, 0x2ee6, 0x2ef4, 0x2f03, 0x2f11,
+ 0x2f20, 0x2f2f, 0x2f3e, 0x2f4e, 0x2f5d, 0x2f6d, 0x2f7e, 0x2f8e, 0x2f9f,
+ 0x2fb1, 0x2fc2, 0x2fd4, 0x2fe6, 0x2ff9, 0x3006, 0x300f, 0x3019, 0x3023,
+ 0x302d, 0x3037, 0x3042, 0x304d, 0x3057, 0x3062, 0x306e, 0x3079, 0x3085,
+ 0x3091, 0x309d, 0x30a9, 0x30b6, 0x30c2, 0x30cf, 0x30dd, 0x30ea, 0x30f8,
+ 0x3106, 0x3114, 0x3122, 0x3131, 0x3140, 0x314f, 0x315e, 0x316e, 0x317e,
+ 0x318f, 0x319f, 0x31b0, 0x31c1, 0x31d3, 0x31e5, 0x31f7, 0x3204, 0x320e,
+ 0x3217, 0x3221, 0x322b, 0x3235, 0x323f, 0x324a, 0x3255, 0x325f, 0x326a,
+ 0x3276, 0x3281, 0x328d, 0x3299, 0x32a5, 0x32b1, 0x32bd, 0x32ca, 0x32d7,
+ 0x32e4, 0x32f2, 0x32ff, 0x330d, 0x331b, 0x332a, 0x3338, 0x3347, 0x3357,
+ 0x3366, 0x3376, 0x3386, 0x3396, 0x33a7, 0x33b8, 0x33c9, 0x33da, 0x33ec,
+ 0x33fe, 0x3408, 0x3412, 0x341b, 0x3425, 0x342f, 0x3439, 0x3443, 0x344e,
+ 0x3458, 0x3463, 0x346e, 0x347a, 0x3485, 0x3491, 0x349d, 0x34a9, 0x34b5,
+ 0x34c2, 0x34ce, 0x34db, 0x34e9, 0x34f6, 0x3504, 0x3512, 0x3520, 0x352f,
+ 0x353d, 0x354c, 0x355c, 0x356b, 0x357b, 0x358b, 0x359c, 0x35ad, 0x35be,
+ 0x35cf, 0x35e1, 0x35f3, 0x3602, 0x360c, 0x3615, 0x361f, 0x3629, 0x3633,
+ 0x363d, 0x3647, 0x3652, 0x365d, 0x3668, 0x3673, 0x367f, 0x368a, 0x3696,
+ 0x36a2, 0x36ae, 0x36bb, 0x36c8, 0x36d5, 0x36e2, 0x36f0, 0x36fd, 0x370b,
+ 0x371a, 0x3728, 0x3737, 0x3746, 0x3755, 0x3765, 0x3775, 0x3785, 0x3796,
+ 0x37a7, 0x37b8, 0x37c9, 0x37db, 0x37ed, 0x3800, 0x3809, 0x3813, 0x381d,
+ 0x3826, 0x3831, 0x383b, 0x3846, 0x3850, 0x385b, 0x3866, 0x3872, 0x387d,
+ 0x3889, 0x3895, 0x38a2, 0x38ae, 0x38bb, 0x38c8, 0x38d5, 0x38e3, 0x38f1,
+ 0x38ff, 0x390d, 0x391b, 0x392a, 0x3939, 0x3949, 0x3959, 0x3969, 0x3979,
+ 0x398a, 0x399b, 0x39ac, 0x39be, 0x39d0, 0x39e2, 0x39f5, 0xc},
+ {0x1630, 0x667, 0x30ff, 0x241, 0xcfd, 0x2226, 0x3fff, 0x105, 0x40a,
+ 0x961, 0x1140, 0x1bd1, 0x2935, 0x3989, 0x3fff, 0x80, 0x193, 0x313,
+ 0x525, 0x7d0, 0xb1a, 0xf09, 0x13a2, 0x18ea, 0x1ee5, 0x2596, 0x2d02,
+ 0x352c, 0x3e17, 0x3fff, 0x3fff, 0x3e, 0xc3, 0x148, 0x1e6, 0x2a6, 0x38a,
+ 0x493, 0x5c2, 0x717, 0x893, 0xa38, 0xc06, 0xdfe, 0x101f, 0x126c,
+ 0x14e4, 0x1788, 0x1a58, 0x1d55, 0x2080, 0x23d9, 0x2760, 0x2b16, 0x2efb,
+ 0x3310, 0x3755, 0x3bca, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x1d,
+ 0x5f, 0xa2, 0xe4, 0x126, 0x16c, 0x1bb, 0x212, 0x273, 0x2dc, 0x34e,
+ 0x3c9, 0x44d, 0x4db, 0x572, 0x613, 0x6be, 0x772, 0x831, 0x8f9, 0x9cb,
+ 0xaa8, 0xb8f, 0xc80, 0xd7c, 0xe82, 0xf93, 0x10ae, 0x11d5, 0x1306,
+ 0x1442, 0x1588, 0x16da, 0x1837, 0x19a0, 0x1b13, 0x1c92, 0x1e1c, 0x1fb1,
+ 0x2152, 0x22fe, 0x24b6, 0x267a, 0x2849, 0x2a24, 0x2c0a, 0x2dfd, 0x2ffc,
+ 0x3206, 0x341c, 0x363f, 0x386d, 0x3aa8, 0x3cef, 0x3f42, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0xc, 0x2d,
+ 0x4e, 0x70, 0x91, 0xb2, 0xd3, 0xf5, 0x116, 0x137, 0x15a, 0x17f, 0x1a6,
+ 0x1d0, 0x1fc, 0x22a, 0x25a, 0x28c, 0x2c0, 0x2f7, 0x330, 0x36c, 0x3a9,
+ 0x3e9, 0x42b, 0x470, 0x4b7, 0x500, 0x54c, 0x59a, 0x5ea, 0x63d, 0x692,
+ 0x6ea, 0x744, 0x7a1, 0x800, 0x862, 0x8c6, 0x92d, 0x996, 0xa02, 0xa70,
+ 0xae1, 0xb54, 0xbca, 0xc43, 0xcbe, 0xd3c, 0xdbc, 0xe40, 0xec5, 0xf4e,
+ 0xfd9, 0x1067, 0x10f7, 0x118a, 0x1220, 0x12b8, 0x1354, 0x13f2, 0x1492,
+ 0x1536, 0x15dc, 0x1685, 0x1731, 0x17df, 0x1890, 0x1944, 0x19fb, 0x1ab5,
+ 0x1b72, 0x1c31, 0x1cf3, 0x1db8, 0x1e80, 0x1f4a, 0x2018, 0x20e8, 0x21bc,
+ 0x2292, 0x236b, 0x2447, 0x2526, 0x2608, 0x26ec, 0x27d4, 0x28be, 0x29ac,
+ 0x2a9c, 0x2b90, 0x2c86, 0x2d7f, 0x2e7c, 0x2f7b, 0x307d, 0x3182, 0x328a,
+ 0x3396, 0x34a4, 0x35b5, 0x36c9, 0x37e1, 0x38fb, 0x3a18, 0x3b39, 0x3c5c,
+ 0x3d83, 0x3eac, 0x3fd9, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x4, 0x14, 0x25, 0x36, 0x46, 0x57, 0x67, 0x78,
+ 0x89, 0x99, 0xaa, 0xbb, 0xcb, 0xdc, 0xec, 0xfd, 0x10e, 0x11e, 0x12e,
+ 0x13f, 0x151, 0x163, 0x176, 0x189, 0x19c, 0x1b1, 0x1c5, 0x1db, 0x1f1,
+ 0x207, 0x21e, 0x235, 0x24d, 0x266, 0x27f, 0x299, 0x2b3, 0x2ce, 0x2e9,
+ 0x305, 0x322, 0x33f, 0x35c, 0x37b, 0x399, 0x3b9, 0x3d9, 0x3f9, 0x41a,
+ 0x43c, 0x45e, 0x481, 0x4a5, 0x4c9, 0x4ed, 0x513, 0x538, 0x55f, 0x586,
+ 0x5ad, 0x5d6, 0x5ff, 0x628, 0x652, 0x67d, 0x6a8, 0x6d4, 0x700, 0x72d,
+ 0x75b, 0x789, 0x7b8, 0x7e8, 0x818, 0x849, 0x87b, 0x8ad, 0x8df, 0x913,
+ 0x947, 0x97b, 0x9b0, 0x9e6, 0xa1d, 0xa54, 0xa8c, 0xac4, 0xafd, 0xb37,
+ 0xb71, 0xbac, 0xbe8, 0xc24, 0xc61, 0xc9f, 0xcdd, 0xd1c, 0xd5c, 0xd9c,
+ 0xddd, 0xe1f, 0xe61, 0xea4, 0xee7, 0xf2b, 0xf70, 0xfb6, 0xffc, 0x1043,
+ 0x108a, 0x10d3, 0x111b, 0x1165, 0x11af, 0x11fa, 0x1246, 0x1292, 0x12df,
+ 0x132d, 0x137b, 0x13ca, 0x141a, 0x146a, 0x14bb, 0x150d, 0x155f, 0x15b2,
+ 0x1606, 0x165a, 0x16b0, 0x1705, 0x175c, 0x17b3, 0x180b, 0x1864, 0x18bd,
+ 0x1917, 0x1972, 0x19cd, 0x1a29, 0x1a86, 0x1ae4, 0x1b42, 0x1ba1, 0x1c01,
+ 0x1c61, 0x1cc2, 0x1d24, 0x1d86, 0x1dea, 0x1e4e, 0x1eb2, 0x1f18, 0x1f7e,
+ 0x1fe4, 0x204c, 0x20b4, 0x211d, 0x2187, 0x21f1, 0x225c, 0x22c8, 0x2334,
+ 0x23a2, 0x2410, 0x247e, 0x24ee, 0x255e, 0x25cf, 0x2640, 0x26b3, 0x2726,
+ 0x279a, 0x280e, 0x2884, 0x28fa, 0x2970, 0x29e8, 0x2a60, 0x2ad9, 0x2b53,
+ 0x2bcd, 0x2c48, 0x2cc4, 0x2d41, 0x2dbe, 0x2e3c, 0x2ebb, 0x2f3b, 0x2fbb,
+ 0x303c, 0x30be, 0x3141, 0x31c4, 0x3248, 0x32cd, 0x3353, 0x33d9, 0x3460,
+ 0x34e8, 0x3571, 0x35fa, 0x3684, 0x370f, 0x379b, 0x3827, 0x38b4, 0x3942,
+ 0x39d1, 0x3a60, 0x3af0, 0x3b81, 0x3c13, 0x3ca5, 0x3d39, 0x3dcd, 0x3e61,
+ 0x3ef7, 0x3f8d, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x0, 0x8, 0x10, 0x18, 0x21, 0x29, 0x31, 0x3a,
+ 0x42, 0x4a, 0x53, 0x5b, 0x63, 0x6c, 0x74, 0x7c, 0x84, 0x8d, 0x95, 0x9d,
+ 0xa6, 0xae, 0xb6, 0xbf, 0xc7, 0xcf, 0xd8, 0xe0, 0xe8, 0xf1, 0xf9,
+ 0x101, 0x109, 0x112, 0x11a, 0x122, 0x12a, 0x132, 0x13b, 0x143, 0x14c,
+ 0x155, 0x15e, 0x167, 0x171, 0x17a, 0x184, 0x18e, 0x197, 0x1a1, 0x1ac,
+ 0x1b6, 0x1c0, 0x1cb, 0x1d5, 0x1e0, 0x1eb, 0x1f6, 0x201, 0x20d, 0x218,
+ 0x224, 0x230, 0x23b, 0x247, 0x254, 0x260, 0x26c, 0x279, 0x286, 0x292,
+ 0x29f, 0x2ad, 0x2ba, 0x2c7, 0x2d5, 0x2e2, 0x2f0, 0x2fe, 0x30c, 0x31b,
+ 0x329, 0x338, 0x346, 0x355, 0x364, 0x373, 0x382, 0x392, 0x3a1, 0x3b1,
+ 0x3c1, 0x3d1, 0x3e1, 0x3f1, 0x402, 0x412, 0x423, 0x434, 0x445, 0x456,
+ 0x467, 0x478, 0x48a, 0x49c, 0x4ae, 0x4c0, 0x4d2, 0x4e4, 0x4f7, 0x509,
+ 0x51c, 0x52f, 0x542, 0x555, 0x569, 0x57c, 0x590, 0x5a4, 0x5b7, 0x5cc,
+ 0x5e0, 0x5f4, 0x609, 0x61e, 0x632, 0x647, 0x65d, 0x672, 0x687, 0x69d,
+ 0x6b3, 0x6c9, 0x6df, 0x6f5, 0x70c, 0x722, 0x739, 0x750, 0x767, 0x77e,
+ 0x795, 0x7ad, 0x7c4, 0x7dc, 0x7f4, 0x80c, 0x824, 0x83d, 0x855, 0x86e,
+ 0x887, 0x8a0, 0x8b9, 0x8d3, 0x8ec, 0x906, 0x920, 0x93a, 0x954, 0x96e,
+ 0x988, 0x9a3, 0x9be, 0x9d9, 0x9f4, 0xa0f, 0xa2b, 0xa46, 0xa62, 0xa7e,
+ 0xa9a, 0xab6, 0xad2, 0xaef, 0xb0c, 0xb29, 0xb46, 0xb63, 0xb80, 0xb9e,
+ 0xbbb, 0xbd9, 0xbf7, 0xc15, 0xc34, 0xc52, 0xc71, 0xc90, 0xcaf, 0xcce,
+ 0xced, 0xd0c, 0xd2c, 0xd4c, 0xd6c, 0xd8c, 0xdac, 0xdcd, 0xded, 0xe0e,
+ 0xe2f, 0xe50, 0xe71, 0xe93, 0xeb4, 0xed6, 0xef8, 0xf1a, 0xf3d, 0xf5f,
+ 0xf82, 0xfa4, 0xfc7, 0xfea, 0x100e, 0x1031, 0x1055, 0x1078, 0x109c,
+ 0x10c0, 0x10e5, 0x1109, 0x112e, 0x1153, 0x1178, 0x119d, 0x11c2, 0x11e7,
+ 0x120d, 0x1233, 0x1259, 0x127f, 0x12a5, 0x12cc, 0x12f2, 0x1319, 0x1340,
+ 0x1367, 0x138f, 0x13b6, 0x13de, 0x1406, 0x142e, 0x1456, 0x147e, 0x14a7,
+ 0x14cf, 0x14f8, 0x1521, 0x154a, 0x1574, 0x159d, 0x15c7, 0x15f1, 0x161b,
+ 0x1645, 0x1670, 0x169a, 0x16c5, 0x16f0, 0x171b, 0x1746, 0x1772, 0x179d,
+ 0x17c9, 0x17f5, 0x1821, 0x184e, 0x187a, 0x18a7, 0x18d4, 0x1901, 0x192e,
+ 0x195b, 0x1989, 0x19b6, 0x19e4, 0x1a12, 0x1a41, 0x1a6f, 0x1a9e, 0x1acc,
+ 0x1afb, 0x1b2a, 0x1b5a, 0x1b89, 0x1bb9, 0x1be9, 0x1c19, 0x1c49, 0x1c79,
+ 0x1caa, 0x1cdb, 0x1d0b, 0x1d3d, 0x1d6e, 0x1d9f, 0x1dd1, 0x1e03, 0x1e35,
+ 0x1e67, 0x1e99, 0x1ecb, 0x1efe, 0x1f31, 0x1f64, 0x1f97, 0x1fcb, 0x1ffe,
+ 0x2032, 0x2066, 0x209a, 0x20ce, 0x2103, 0x2137, 0x216c, 0x21a1, 0x21d6,
+ 0x220c, 0x2241, 0x2277, 0x22ad, 0x22e3, 0x2319, 0x2350, 0x2386, 0x23bd,
+ 0x23f4, 0x242b, 0x2463, 0x249a, 0x24d2, 0x250a, 0x2542, 0x257a, 0x25b3,
+ 0x25eb, 0x2624, 0x265d, 0x2696, 0x26d0, 0x2709, 0x2743, 0x277d, 0x27b7,
+ 0x27f1, 0x282b, 0x2866, 0x28a1, 0x28dc, 0x2917, 0x2953, 0x298e, 0x29ca,
+ 0x2a06, 0x2a42, 0x2a7e, 0x2abb, 0x2af7, 0x2b34, 0x2b71, 0x2bae, 0x2bec,
+ 0x2c29, 0x2c67, 0x2ca5, 0x2ce3, 0x2d21, 0x2d60, 0x2d9f, 0x2dde, 0x2e1d,
+ 0x2e5c, 0x2e9b, 0x2edb, 0x2f1b, 0x2f5b, 0x2f9b, 0x2fdb, 0x301c, 0x305d,
+ 0x309e, 0x30df, 0x3120, 0x3161, 0x31a3, 0x31e5, 0x3227, 0x3269, 0x32ac,
+ 0x32ee, 0x3331, 0x3374, 0x33b7, 0x33fb, 0x343e, 0x3482, 0x34c6, 0x350a,
+ 0x354e, 0x3593, 0x35d7, 0x361c, 0x3661, 0x36a7, 0x36ec, 0x3732, 0x3778,
+ 0x37be, 0x3804, 0x384a, 0x3891, 0x38d7, 0x391e, 0x3966, 0x39ad, 0x39f4,
+ 0x3a3c, 0x3a84, 0x3acc, 0x3b14, 0x3b5d, 0x3ba6, 0x3bee, 0x3c37, 0x3c81,
+ 0x3cca, 0x3d14, 0x3d5e, 0x3da8, 0x3df2, 0x3e3c, 0x3e87, 0x3ed2, 0x3f1c,
+ 0x3f68, 0x3fb3, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x6},
+ {0x1095, 0x4fa, 0x240a, 0x1d9, 0x9d8, 0x1949, 0x30eb, 0xe0, 0x334,
+ 0x72f, 0xcf9, 0x14ae, 0x1e66, 0x2a35, 0x382e, 0x6e, 0x152, 0x27a,
+ 0x409, 0x606, 0x875, 0xb5a, 0xeb7, 0x1291, 0x16eb, 0x1bc7, 0x2127,
+ 0x270e, 0x2d7f, 0x347b, 0x3c04, 0x35, 0xa7, 0x119, 0x193, 0x226, 0x2d4,
+ 0x39b, 0x47e, 0x57d, 0x697, 0x7cf, 0x923, 0xa95, 0xc25, 0xdd4, 0xfa2,
+ 0x118f, 0x139c, 0x15c9, 0x1816, 0x1a84, 0x1d12, 0x1fc2, 0x2294, 0x2588,
+ 0x289e, 0x2bd6, 0x2f31, 0x32af, 0x3650, 0x3a15, 0x3dfd, 0x18, 0x51,
+ 0x8a, 0xc3, 0xfc, 0x134, 0x172, 0x1b5, 0x1ff, 0x24f, 0x2a6, 0x303,
+ 0x367, 0x3d2, 0x443, 0x4bb, 0x53a, 0x5c1, 0x64e, 0x6e2, 0x77e, 0x821,
+ 0x8cb, 0x97d, 0xa36, 0xaf6, 0xbbf, 0xc8e, 0xd66, 0xe45, 0xf2c, 0x101a,
+ 0x1111, 0x120f, 0x1316, 0x1424, 0x153a, 0x1659, 0x177f, 0x18ae, 0x19e5,
+ 0x1b24, 0x1c6c, 0x1dbb, 0x1f13, 0x2074, 0x21dd, 0x234e, 0x24c8, 0x264a,
+ 0x27d5, 0x2968, 0x2b05, 0x2ca9, 0x2e57, 0x300d, 0x31cc, 0x3394, 0x3564,
+ 0x373e, 0x3920, 0x3b0b, 0x3cff, 0x3efd, 0xa, 0x27, 0x43, 0x60, 0x7c,
+ 0x99, 0xb5, 0xd1, 0xee, 0x10a, 0x126, 0x143, 0x162, 0x182, 0x1a4,
+ 0x1c7, 0x1ec, 0x213, 0x23b, 0x264, 0x290, 0x2bd, 0x2eb, 0x31c, 0x34d,
+ 0x381, 0x3b6, 0x3ed, 0x426, 0x460, 0x49c, 0x4da, 0x51a, 0x55b, 0x59e,
+ 0x5e3, 0x62a, 0x672, 0x6bd, 0x709, 0x756, 0x7a6, 0x7f7, 0x84b, 0x8a0,
+ 0x8f7, 0x950, 0x9aa, 0xa07, 0xa65, 0xac6, 0xb28, 0xb8c, 0xbf2, 0xc5a,
+ 0xcc3, 0xd2f, 0xd9d, 0xe0c, 0xe7e, 0xef1, 0xf67, 0xfde, 0x1057, 0x10d3,
+ 0x1150, 0x11cf, 0x1250, 0x12d3, 0x1359, 0x13e0, 0x1469, 0x14f4, 0x1581,
+ 0x1610, 0x16a2, 0x1735, 0x17ca, 0x1862, 0x18fb, 0x1996, 0x1a34, 0x1ad4,
+ 0x1b75, 0x1c19, 0x1cbf, 0x1d67, 0x1e10, 0x1ebd, 0x1f6b, 0x201b, 0x20cd,
+ 0x2182, 0x2238, 0x22f1, 0x23ac, 0x2469, 0x2528, 0x25e9, 0x26ac, 0x2771,
+ 0x2839, 0x2903, 0x29cf, 0x2a9d, 0x2b6d, 0x2c3f, 0x2d14, 0x2deb, 0x2ec4,
+ 0x2f9f, 0x307c, 0x315b, 0x323d, 0x3321, 0x3407, 0x34ef, 0x35da, 0x36c7,
+ 0x37b6, 0x38a7, 0x399a, 0x3a90, 0x3b87, 0x3c82, 0x3d7e, 0x3e7c, 0x3f7d,
+ 0x3, 0x11, 0x20, 0x2e, 0x3c, 0x4a, 0x58, 0x67, 0x75, 0x83, 0x91, 0xa0,
+ 0xae, 0xbc, 0xca, 0xd9, 0xe7, 0xf5, 0x103, 0x112, 0x120, 0x12d, 0x13c,
+ 0x14b, 0x15a, 0x16a, 0x17a, 0x18a, 0x19b, 0x1ad, 0x1be, 0x1d0, 0x1e3,
+ 0x1f6, 0x209, 0x21c, 0x230, 0x245, 0x25a, 0x26f, 0x285, 0x29b, 0x2b1,
+ 0x2c8, 0x2df, 0x2f7, 0x30f, 0x328, 0x341, 0x35a, 0x374, 0x38e, 0x3a9,
+ 0x3c4, 0x3df, 0x3fb, 0x418, 0x434, 0x452, 0x46f, 0x48d, 0x4ac, 0x4cb,
+ 0x4ea, 0x50a, 0x52a, 0x54b, 0x56c, 0x58d, 0x5af, 0x5d2, 0x5f5, 0x618,
+ 0x63c, 0x660, 0x685, 0x6aa, 0x6cf, 0x6f5, 0x71c, 0x743, 0x76a, 0x792,
+ 0x7ba, 0x7e3, 0x80c, 0x836, 0x860, 0x88a, 0x8b5, 0x8e1, 0x90d, 0x939,
+ 0x966, 0x993, 0x9c1, 0x9f0, 0xa1e, 0xa4e, 0xa7d, 0xaad, 0xade, 0xb0f,
+ 0xb41, 0xb73, 0xba5, 0xbd8, 0xc0c, 0xc40, 0xc74, 0xca9, 0xcde, 0xd14,
+ 0xd4a, 0xd81, 0xdb8, 0xdf0, 0xe29, 0xe61, 0xe9a, 0xed4, 0xf0e, 0xf49,
+ 0xf84, 0xfc0, 0xffc, 0x1039, 0x1076, 0x10b4, 0x10f2, 0x1130, 0x116f,
+ 0x11af, 0x11ef, 0x1230, 0x1271, 0x12b2, 0x12f4, 0x1337, 0x137a, 0x13be,
+ 0x1402, 0x1446, 0x148b, 0x14d1, 0x1517, 0x155e, 0x15a5, 0x15ec, 0x1635,
+ 0x167d, 0x16c6, 0x1710, 0x175a, 0x17a5, 0x17f0, 0x183c, 0x1888, 0x18d5,
+ 0x1922, 0x196f, 0x19be, 0x1a0c, 0x1a5c, 0x1aab, 0x1afc, 0x1b4d, 0x1b9e,
+ 0x1bf0, 0x1c42, 0x1c95, 0x1ce8, 0x1d3c, 0x1d91, 0x1de6, 0x1e3b, 0x1e91,
+ 0x1ee8, 0x1f3f, 0x1f96, 0x1fef, 0x2047, 0x20a0, 0x20fa, 0x2154, 0x21af,
+ 0x220a, 0x2266, 0x22c2, 0x231f, 0x237d, 0x23db, 0x2439, 0x2498, 0x24f8,
+ 0x2558, 0x25b8, 0x2619, 0x267b, 0x26dd, 0x2740, 0x27a3, 0x2807, 0x286b,
+ 0x28d0, 0x2936, 0x299c, 0x2a02, 0x2a69, 0x2ad1, 0x2b39, 0x2ba1, 0x2c0b,
+ 0x2c74, 0x2cdf, 0x2d49, 0x2db5, 0x2e21, 0x2e8d, 0x2efa, 0x2f68, 0x2fd6,
+ 0x3044, 0x30b4, 0x3123, 0x3194, 0x3205, 0x3276, 0x32e8, 0x335a, 0x33cd,
+ 0x3441, 0x34b5, 0x352a, 0x359f, 0x3615, 0x368b, 0x3702, 0x377a, 0x37f2,
+ 0x386a, 0x38e3, 0x395d, 0x39d7, 0x3a52, 0x3acd, 0x3b49, 0x3bc6, 0x3c43,
+ 0x3cc0, 0x3d3f, 0x3dbd, 0x3e3d, 0x3ebc, 0x3f3d, 0x3fbe, 0x0, 0x7, 0xe,
+ 0x15, 0x1c, 0x23, 0x2a, 0x31, 0x38, 0x40, 0x47, 0x4e, 0x55, 0x5c, 0x63,
+ 0x6a, 0x71, 0x78, 0x80, 0x87, 0x8e, 0x95, 0x9c, 0xa3, 0xaa, 0xb1, 0xb9,
+ 0xc0, 0xc7, 0xce, 0xd5, 0xdc, 0xe3, 0xea, 0xf1, 0xf9, 0x100, 0x107,
+ 0x10e, 0x115, 0x11c, 0x123, 0x12a, 0x131, 0x138, 0x13f, 0x147, 0x14e,
+ 0x156, 0x15e, 0x166, 0x16e, 0x176, 0x17e, 0x186, 0x18f, 0x197, 0x1a0,
+ 0x1a8, 0x1b1, 0x1ba, 0x1c3, 0x1cc, 0x1d5, 0x1de, 0x1e7, 0x1f1, 0x1fa,
+ 0x204, 0x20e, 0x217, 0x221, 0x22b, 0x236, 0x240, 0x24a, 0x255, 0x25f,
+ 0x26a, 0x274, 0x27f, 0x28a, 0x295, 0x2a0, 0x2ac, 0x2b7, 0x2c2, 0x2ce,
+ 0x2da, 0x2e5, 0x2f1, 0x2fd, 0x309, 0x315, 0x322, 0x32e, 0x33b, 0x347,
+ 0x354, 0x361, 0x36d, 0x37a, 0x388, 0x395, 0x3a2, 0x3b0, 0x3bd, 0x3cb,
+ 0x3d8, 0x3e6, 0x3f4, 0x402, 0x411, 0x41f, 0x42d, 0x43c, 0x44a, 0x459,
+ 0x468, 0x477, 0x486, 0x495, 0x4a4, 0x4b3, 0x4c3, 0x4d3, 0x4e2, 0x4f2,
+ 0x502, 0x512, 0x522, 0x532, 0x543, 0x553, 0x564, 0x574, 0x585, 0x596,
+ 0x5a7, 0x5b8, 0x5c9, 0x5db, 0x5ec, 0x5fe, 0x60f, 0x621, 0x633, 0x645,
+ 0x657, 0x669, 0x67b, 0x68e, 0x6a0, 0x6b3, 0x6c6, 0x6d9, 0x6ec, 0x6ff,
+ 0x712, 0x726, 0x739, 0x74d, 0x760, 0x774, 0x788, 0x79c, 0x7b0, 0x7c4,
+ 0x7d9, 0x7ed, 0x802, 0x816, 0x82b, 0x840, 0x855, 0x86a, 0x880, 0x895,
+ 0x8ab, 0x8c0, 0x8d6, 0x8ec, 0x902, 0x918, 0x92e, 0x944, 0x95b, 0x971,
+ 0x988, 0x99f, 0x9b6, 0x9cd, 0x9e4, 0x9fb, 0xa13, 0xa2a, 0xa42, 0xa59,
+ 0xa71, 0xa89, 0xaa1, 0xab9, 0xad2, 0xaea, 0xb03, 0xb1b, 0xb34, 0xb4d,
+ 0xb66, 0xb7f, 0xb98, 0xbb2, 0xbcb, 0xbe5, 0xbff, 0xc19, 0xc32, 0xc4d,
+ 0xc67, 0xc81, 0xc9c, 0xcb6, 0xcd1, 0xcec, 0xd07, 0xd22, 0xd3d, 0xd58,
+ 0xd73, 0xd8f, 0xdab, 0xdc6, 0xde2, 0xdfe, 0xe1a, 0xe37, 0xe53, 0xe70,
+ 0xe8c, 0xea9, 0xec6, 0xee3, 0xf00, 0xf1d, 0xf3a, 0xf58, 0xf75, 0xf93,
+ 0xfb1, 0xfcf, 0xfed, 0x100b, 0x102a, 0x1048, 0x1067, 0x1085, 0x10a4,
+ 0x10c3, 0x10e2, 0x1101, 0x1121, 0x1140, 0x1160, 0x117f, 0x119f, 0x11bf,
+ 0x11df, 0x11ff, 0x121f, 0x1240, 0x1260, 0x1281, 0x12a2, 0x12c3, 0x12e4,
+ 0x1305, 0x1326, 0x1348, 0x1369, 0x138b, 0x13ad, 0x13cf, 0x13f1, 0x1413,
+ 0x1435, 0x1458, 0x147a, 0x149d, 0x14c0, 0x14e3, 0x1506, 0x1529, 0x154c,
+ 0x156f, 0x1593, 0x15b7, 0x15db, 0x15fe, 0x1623, 0x1647, 0x166b, 0x168f,
+ 0x16b4, 0x16d9, 0x16fe, 0x1722, 0x1748, 0x176d, 0x1792, 0x17b8, 0x17dd,
+ 0x1803, 0x1829, 0x184f, 0x1875, 0x189b, 0x18c1, 0x18e8, 0x190e, 0x1935,
+ 0x195c, 0x1983, 0x19aa, 0x19d1, 0x19f9, 0x1a20, 0x1a48, 0x1a70, 0x1a97,
+ 0x1ac0, 0x1ae8, 0x1b10, 0x1b38, 0x1b61, 0x1b8a, 0x1bb2, 0x1bdb, 0x1c04,
+ 0x1c2e, 0x1c57, 0x1c80, 0x1caa, 0x1cd4, 0x1cfd, 0x1d27, 0x1d51, 0x1d7c,
+ 0x1da6, 0x1dd1, 0x1dfb, 0x1e26, 0x1e51, 0x1e7c, 0x1ea7, 0x1ed2, 0x1efe,
+ 0x1f29, 0x1f55, 0x1f81, 0x1fac, 0x1fd9, 0x2005, 0x2031, 0x205d, 0x208a,
+ 0x20b7, 0x20e4, 0x2111, 0x213e, 0x216b, 0x2198, 0x21c6, 0x21f3, 0x2221,
+ 0x224f, 0x227d, 0x22ab, 0x22da, 0x2308, 0x2337, 0x2365, 0x2394, 0x23c3,
+ 0x23f2, 0x2421, 0x2451, 0x2480, 0x24b0, 0x24e0, 0x2510, 0x2540, 0x2570,
+ 0x25a0, 0x25d0, 0x2601, 0x2632, 0x2663, 0x2693, 0x26c5, 0x26f6, 0x2727,
+ 0x2759, 0x278a, 0x27bc, 0x27ee, 0x2820, 0x2852, 0x2884, 0x28b7, 0x28e9,
+ 0x291c, 0x294f, 0x2982, 0x29b5, 0x29e8, 0x2a1c, 0x2a4f, 0x2a83, 0x2ab7,
+ 0x2aeb, 0x2b1f, 0x2b53, 0x2b87, 0x2bbc, 0x2bf0, 0x2c25, 0x2c5a, 0x2c8f,
+ 0x2cc4, 0x2cf9, 0x2d2f, 0x2d64, 0x2d9a, 0x2dd0, 0x2e06, 0x2e3c, 0x2e72,
+ 0x2ea8, 0x2edf, 0x2f16, 0x2f4c, 0x2f83, 0x2fba, 0x2ff1, 0x3029, 0x3060,
+ 0x3098, 0x30d0, 0x3107, 0x313f, 0x3178, 0x31b0, 0x31e8, 0x3221, 0x325a,
+ 0x3292, 0x32cb, 0x3304, 0x333e, 0x3377, 0x33b1, 0x33ea, 0x3424, 0x345e,
+ 0x3498, 0x34d2, 0x350d, 0x3547, 0x3582, 0x35bc, 0x35f7, 0x3632, 0x366e,
+ 0x36a9, 0x36e4, 0x3720, 0x375c, 0x3798, 0x37d4, 0x3810, 0x384c, 0x3888,
+ 0x38c5, 0x3902, 0x393f, 0x397c, 0x39b9, 0x39f6, 0x3a33, 0x3a71, 0x3aaf,
+ 0x3aec, 0x3b2a, 0x3b68, 0x3ba7, 0x3be5, 0x3c24, 0x3c62, 0x3ca1, 0x3ce0,
+ 0x3d1f, 0x3d5e, 0x3d9e, 0x3ddd, 0x3e1d, 0x3e5d, 0x3e9c, 0x3edc, 0x3f1d,
+ 0x3f5d, 0x3f9e, 0x3fde, 0x6},
+ {0x798, 0x1ce, 0x20ac, 0x72, 0x413, 0xf47, 0x3fff, 0x1c, 0x103, 0x2d4,
+ 0x58e, 0xaaa, 0x1639, 0x3065, 0x3fff, 0x6, 0x40, 0xb3, 0x161, 0x24a,
+ 0x36c, 0x4c9, 0x679, 0x8f9, 0xcbc, 0x1266, 0x1aeb, 0x27bc, 0x3b05,
+ 0x3fff, 0x3fff, 0x1, 0xf, 0x2c, 0x57, 0x91, 0xd9, 0x130, 0x196, 0x20a,
+ 0x28d, 0x31e, 0x3be, 0x46c, 0x529, 0x5fe, 0x701, 0x840, 0x9c6, 0xba6,
+ 0xdf1, 0x10c2, 0x1437, 0x1873, 0x1da6, 0x2406, 0x2bd8, 0x3570, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x0, 0x3, 0xa, 0x15, 0x23, 0x35, 0x4b,
+ 0x64, 0x81, 0xa2, 0xc6, 0xee, 0x119, 0x148, 0x17b, 0x1b2, 0x1ec, 0x229,
+ 0x26b, 0x2b0, 0x2f8, 0x345, 0x395, 0x3e8, 0x43f, 0x49a, 0x4f9, 0x55b,
+ 0x5c5, 0x63a, 0x6bb, 0x74b, 0x7ea, 0x89a, 0x95d, 0xa35, 0xb24, 0xc2d,
+ 0xd53, 0xe98, 0x1000, 0x118f, 0x1348, 0x1531, 0x174f, 0x19a7, 0x1c3f,
+ 0x1f1f, 0x224e, 0x25d5, 0x29bc, 0x2e10, 0x32da, 0x3828, 0x3e08, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x0,
+ 0x0, 0x2, 0x5, 0x8, 0xd, 0x12, 0x18, 0x1f, 0x28, 0x31, 0x3a, 0x45,
+ 0x51, 0x5e, 0x6b, 0x7a, 0x89, 0x99, 0xaa, 0xbd, 0xd0, 0xe4, 0xf8,
+ 0x10e, 0x125, 0x13c, 0x155, 0x16e, 0x189, 0x1a4, 0x1c0, 0x1dd, 0x1fb,
+ 0x21a, 0x239, 0x25a, 0x27c, 0x29e, 0x2c2, 0x2e6, 0x30b, 0x331, 0x358,
+ 0x380, 0x3a9, 0x3d3, 0x3fe, 0x429, 0x456, 0x483, 0x4b2, 0x4e1, 0x511,
+ 0x542, 0x574, 0x5a9, 0x5e1, 0x61b, 0x659, 0x69a, 0x6de, 0x726, 0x771,
+ 0x7c1, 0x814, 0x86c, 0x8c9, 0x92b, 0x991, 0x9fd, 0xa6f, 0xae6, 0xb64,
+ 0xbe9, 0xc74, 0xd07, 0xda1, 0xe44, 0xeef, 0xfa3, 0x1060, 0x1127,
+ 0x11f9, 0x12d6, 0x13be, 0x14b2, 0x15b4, 0x16c2, 0x17df, 0x190b, 0x1a47,
+ 0x1b93, 0x1cf0, 0x1e60, 0x1fe3, 0x217b, 0x2327, 0x24ea, 0x26c5, 0x28b9,
+ 0x2ac7, 0x2cf0, 0x2f37, 0x319b, 0x3420, 0x36c7, 0x3992, 0x3c82, 0x3f99,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x0, 0x0, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x7, 0x9, 0xb, 0xe, 0x11, 0x13,
+ 0x17, 0x1a, 0x1e, 0x21, 0x25, 0x2a, 0x2e, 0x33, 0x38, 0x3d, 0x42, 0x48,
+ 0x4e, 0x54, 0x5a, 0x61, 0x68, 0x6f, 0x76, 0x7d, 0x85, 0x8d, 0x95, 0x9d,
+ 0xa6, 0xaf, 0xb8, 0xc1, 0xcb, 0xd4, 0xde, 0xe9, 0xf3, 0xfe, 0x109,
+ 0x114, 0x11f, 0x12b, 0x136, 0x142, 0x14f, 0x15b, 0x168, 0x175, 0x182,
+ 0x18f, 0x19d, 0x1ab, 0x1b9, 0x1c7, 0x1d6, 0x1e4, 0x1f3, 0x202, 0x212,
+ 0x222, 0x231, 0x242, 0x252, 0x262, 0x273, 0x284, 0x296, 0x2a7, 0x2b9,
+ 0x2cb, 0x2dd, 0x2ef, 0x302, 0x315, 0x328, 0x33b, 0x34f, 0x362, 0x376,
+ 0x38a, 0x39f, 0x3b4, 0x3c8, 0x3de, 0x3f3, 0x409, 0x41e, 0x434, 0x44b,
+ 0x461, 0x478, 0x48f, 0x4a6, 0x4bd, 0x4d5, 0x4ed, 0x505, 0x51d, 0x536,
+ 0x54f, 0x568, 0x581, 0x59c, 0x5b7, 0x5d3, 0x5ef, 0x60c, 0x62a, 0x649,
+ 0x669, 0x689, 0x6aa, 0x6cd, 0x6f0, 0x713, 0x738, 0x75e, 0x785, 0x7ac,
+ 0x7d5, 0x7ff, 0x82a, 0x856, 0x883, 0x8b1, 0x8e1, 0x912, 0x944, 0x977,
+ 0x9ac, 0x9e2, 0xa19, 0xa52, 0xa8c, 0xac8, 0xb05, 0xb44, 0xb85, 0xbc7,
+ 0xc0b, 0xc51, 0xc98, 0xce1, 0xd2d, 0xd7a, 0xdc9, 0xe1a, 0xe6e, 0xec3,
+ 0xf1b, 0xf75, 0xfd1, 0x1030, 0x1091, 0x10f4, 0x115b, 0x11c3, 0x122f,
+ 0x129d, 0x130f, 0x1383, 0x13fa, 0x1474, 0x14f1, 0x1572, 0x15f6, 0x167d,
+ 0x1708, 0x1797, 0x1829, 0x18bf, 0x1959, 0x19f6, 0x1a98, 0x1b3e, 0x1be8,
+ 0x1c97, 0x1d4a, 0x1e02, 0x1ebf, 0x1f81, 0x2047, 0x2113, 0x21e4, 0x22ba,
+ 0x2396, 0x2477, 0x255f, 0x264c, 0x2740, 0x283a, 0x293a, 0x2a41, 0x2b4e,
+ 0x2c63, 0x2d7f, 0x2ea2, 0x2fcd, 0x30ff, 0x323a, 0x337c, 0x34c7, 0x361a,
+ 0x3777, 0x38dc, 0x3a4a, 0x3bc2, 0x3d44, 0x3ecf, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x2, 0x2, 0x3, 0x4, 0x4, 0x5, 0x6, 0x7,
+ 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xf, 0x10, 0x11, 0x13, 0x14, 0x16, 0x17,
+ 0x19, 0x1b, 0x1d, 0x1e, 0x20, 0x22, 0x24, 0x26, 0x29, 0x2b, 0x2d, 0x2f,
+ 0x32, 0x34, 0x37, 0x39, 0x3c, 0x3e, 0x41, 0x44, 0x47, 0x4a, 0x4c, 0x4f,
+ 0x53, 0x56, 0x59, 0x5c, 0x5f, 0x63, 0x66, 0x69, 0x6d, 0x70, 0x74, 0x78,
+ 0x7b, 0x7f, 0x83, 0x87, 0x8b, 0x8f, 0x93, 0x97, 0x9b, 0xa0, 0xa4, 0xa8,
+ 0xad, 0xb1, 0xb6, 0xba, 0xbf, 0xc4, 0xc8, 0xcd, 0xd2, 0xd7, 0xdc, 0xe1,
+ 0xe6, 0xeb, 0xf0, 0xf6, 0xfb, 0x100, 0x106, 0x10b, 0x111, 0x116, 0x11c,
+ 0x122, 0x128, 0x12d, 0x133, 0x139, 0x13f, 0x145, 0x14c, 0x152, 0x158,
+ 0x15e, 0x165, 0x16b, 0x171, 0x178, 0x17f, 0x185, 0x18c, 0x193, 0x199,
+ 0x1a0, 0x1a7, 0x1ae, 0x1b5, 0x1bc, 0x1c3, 0x1cb, 0x1d2, 0x1d9, 0x1e1,
+ 0x1e8, 0x1ef, 0x1f7, 0x1ff, 0x206, 0x20e, 0x216, 0x21e, 0x225, 0x22d,
+ 0x235, 0x23d, 0x246, 0x24e, 0x256, 0x25e, 0x267, 0x26f, 0x277, 0x280,
+ 0x289, 0x291, 0x29a, 0x2a3, 0x2ab, 0x2b4, 0x2bd, 0x2c6, 0x2cf, 0x2d8,
+ 0x2e1, 0x2eb, 0x2f4, 0x2fd, 0x306, 0x310, 0x319, 0x323, 0x32d, 0x336,
+ 0x340, 0x34a, 0x353, 0x35d, 0x367, 0x371, 0x37b, 0x385, 0x390, 0x39a,
+ 0x3a4, 0x3ae, 0x3b9, 0x3c3, 0x3ce, 0x3d8, 0x3e3, 0x3ee, 0x3f8, 0x403,
+ 0x40e, 0x419, 0x424, 0x42f, 0x43a, 0x445, 0x450, 0x45b, 0x467, 0x472,
+ 0x47e, 0x489, 0x495, 0x4a0, 0x4ac, 0x4b7, 0x4c3, 0x4cf, 0x4db, 0x4e7,
+ 0x4f3, 0x4ff, 0x50b, 0x517, 0x523, 0x530, 0x53c, 0x548, 0x555, 0x561,
+ 0x56e, 0x57b, 0x588, 0x595, 0x5a2, 0x5b0, 0x5be, 0x5cc, 0x5da, 0x5e8,
+ 0x5f6, 0x605, 0x614, 0x623, 0x632, 0x641, 0x651, 0x661, 0x671, 0x681,
+ 0x691, 0x6a2, 0x6b3, 0x6c4, 0x6d5, 0x6e7, 0x6f8, 0x70a, 0x71d, 0x72f,
+ 0x742, 0x754, 0x768, 0x77b, 0x78f, 0x7a2, 0x7b7, 0x7cb, 0x7e0, 0x7f4,
+ 0x80a, 0x81f, 0x835, 0x84b, 0x861, 0x878, 0x88f, 0x8a6, 0x8bd, 0x8d5,
+ 0x8ed, 0x905, 0x91e, 0x937, 0x950, 0x96a, 0x984, 0x99e, 0x9b9, 0x9d4,
+ 0x9ef, 0xa0b, 0xa27, 0xa43, 0xa60, 0xa7d, 0xa9b, 0xab9, 0xad7, 0xaf6,
+ 0xb15, 0xb34, 0xb54, 0xb74, 0xb95, 0xbb6, 0xbd8, 0xbfa, 0xc1c, 0xc3f,
+ 0xc62, 0xc86, 0xcaa, 0xccf, 0xcf4, 0xd1a, 0xd40, 0xd66, 0xd8d, 0xdb5,
+ 0xddd, 0xe06, 0xe2f, 0xe58, 0xe83, 0xead, 0xed9, 0xf05, 0xf31, 0xf5e,
+ 0xf8c, 0xfba, 0xfe8, 0x1018, 0x1048, 0x1078, 0x10a9, 0x10db, 0x110e,
+ 0x1141, 0x1175, 0x11a9, 0x11de, 0x1214, 0x124a, 0x1282, 0x12b9, 0x12f2,
+ 0x132b, 0x1365, 0x13a0, 0x13dc, 0x1418, 0x1455, 0x1493, 0x14d2, 0x1511,
+ 0x1552, 0x1593, 0x15d5, 0x1618, 0x165b, 0x16a0, 0x16e5, 0x172b, 0x1773,
+ 0x17bb, 0x1804, 0x184e, 0x1899, 0x18e5, 0x1932, 0x1980, 0x19ce, 0x1a1e,
+ 0x1a6f, 0x1ac1, 0x1b14, 0x1b68, 0x1bbe, 0x1c14, 0x1c6b, 0x1cc4, 0x1d1d,
+ 0x1d78, 0x1dd4, 0x1e31, 0x1e8f, 0x1eef, 0x1f50, 0x1fb2, 0x2015, 0x2079,
+ 0x20df, 0x2146, 0x21af, 0x2219, 0x2284, 0x22f0, 0x235e, 0x23ce, 0x243f,
+ 0x24b1, 0x2524, 0x259a, 0x2610, 0x2689, 0x2702, 0x277e, 0x27fa, 0x2879,
+ 0x28f9, 0x297b, 0x29fe, 0x2a84, 0x2b0a, 0x2b93, 0x2c1d, 0x2ca9, 0x2d37,
+ 0x2dc7, 0x2e59, 0x2eec, 0x2f81, 0x3019, 0x30b2, 0x314d, 0x31ea, 0x3289,
+ 0x332b, 0x33ce, 0x3473, 0x351b, 0x35c5, 0x3671, 0x371f, 0x37cf, 0x3882,
+ 0x3936, 0x39ee, 0x3aa7, 0x3b63, 0x3c21, 0x3ce2, 0x3da5, 0x3e6b, 0x3f34,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x6},
+ {0x32a8, 0x2ea6, 0x361d, 0x2aa0, 0x30fc, 0x344a, 0x3808, 0x2696,
+ 0x2cf8, 0x3012, 0x3208, 0x3374, 0x3517, 0x36eb, 0x38d8, 0x2281, 0x28f0,
+ 0x2c0f, 0x2e06, 0x2f5a, 0x3082, 0x3181, 0x3256, 0x3305, 0x33f8, 0x34a8,
+ 0x359c, 0x367b, 0x3771, 0x3867, 0x395e, 0x1e58, 0x24e1, 0x2808, 0x2a01,
+ 0x2b54, 0x2c7e, 0x2d7d, 0x2e53, 0x2efd, 0x2fbd, 0x3049, 0x30be, 0x313d,
+ 0x31c8, 0x322e, 0x327e, 0x32d5, 0x333a, 0x33b3, 0x3421, 0x3477, 0x34dd,
+ 0x3557, 0x35e8, 0x364a, 0x36b1, 0x372b, 0x37bd, 0x3835, 0x389d, 0x3918,
+ 0x39ab, 0x1a0b, 0x20c2, 0x23f6, 0x25f1, 0x2748, 0x2877, 0x2974, 0x2a4e,
+ 0x2af8, 0x2bb6, 0x2c45, 0x2cba, 0x2d39, 0x2dc3, 0x2e2c, 0x2e7c, 0x2ed1,
+ 0x2f2b, 0x2f8b, 0x2ff0, 0x302d, 0x3065, 0x309f, 0x30dd, 0x311d, 0x315f,
+ 0x31a4, 0x31ec, 0x321b, 0x3242, 0x326a, 0x3293, 0x32be, 0x32ec, 0x331f,
+ 0x3356, 0x3393, 0x33d5, 0x340e, 0x3435, 0x3460, 0x348f, 0x34c2, 0x34fa,
+ 0x3536, 0x3579, 0x35c1, 0x3608, 0x3633, 0x3662, 0x3695, 0x36cd, 0x370b,
+ 0x374d, 0x3796, 0x37e6, 0x381e, 0x384d, 0x3881, 0x38ba, 0x38f7, 0x393a,
+ 0x3984, 0x39d4, 0x1500, 0x1c85, 0x1fc3, 0x21cc, 0x2331, 0x2468, 0x2563,
+ 0x2644, 0x26ec, 0x27aa, 0x283e, 0x28b2, 0x2931, 0x29bb, 0x2a27, 0x2a76,
+ 0x2acb, 0x2b25, 0x2b85, 0x2bea, 0x2c2a, 0x2c61, 0x2c9c, 0x2cd9, 0x2d18,
+ 0x2d5b, 0x2da0, 0x2de8, 0x2e19, 0x2e3f, 0x2e67, 0x2e90, 0x2ebb, 0x2ee7,
+ 0x2f14, 0x2f43, 0x2f72, 0x2fa4, 0x2fd6, 0x3005, 0x301f, 0x303b, 0x3057,
+ 0x3073, 0x3091, 0x30ae, 0x30cd, 0x30ec, 0x310c, 0x312d, 0x314e, 0x3170,
+ 0x3193, 0x31b6, 0x31da, 0x31ff, 0x3212, 0x3225, 0x3238, 0x324c, 0x3260,
+ 0x3274, 0x3289, 0x329e, 0x32b3, 0x32c9, 0x32e0, 0x32f9, 0x3312, 0x332c,
+ 0x3348, 0x3365, 0x3383, 0x33a3, 0x33c4, 0x33e6, 0x3405, 0x3418, 0x342b,
+ 0x3440, 0x3455, 0x346b, 0x3483, 0x349b, 0x34b5, 0x34cf, 0x34eb, 0x3508,
+ 0x3527, 0x3546, 0x3568, 0x358a, 0x35ae, 0x35d4, 0x35fc, 0x3612, 0x3628,
+ 0x363e, 0x3656, 0x366e, 0x3688, 0x36a3, 0x36bf, 0x36dc, 0x36fb, 0x371b,
+ 0x373c, 0x375f, 0x3783, 0x37aa, 0x37d1, 0x37fb, 0x3813, 0x382a, 0x3841,
+ 0x385a, 0x3874, 0x388f, 0x38ab, 0x38c8, 0x38e7, 0x3907, 0x3929, 0x394c,
+ 0x3971, 0x3997, 0x39bf, 0x39e9, 0xea8, 0x1815, 0x1b60, 0x1d86, 0x1f03,
+ 0x204c, 0x2142, 0x2231, 0x22d6, 0x2391, 0x2430, 0x24a3, 0x2521, 0x25a9,
+ 0x261e, 0x266c, 0x26c0, 0x271a, 0x2779, 0x27dd, 0x2823, 0x285a, 0x2894,
+ 0x28d1, 0x2910, 0x2952, 0x2997, 0x29df, 0x2a14, 0x2a3a, 0x2a62, 0x2a8b,
+ 0x2ab6, 0x2ae1, 0x2b0e, 0x2b3d, 0x2b6c, 0x2b9d, 0x2bd0, 0x2c02, 0x2c1c,
+ 0x2c37, 0x2c53, 0x2c70, 0x2c8d, 0x2cab, 0x2cc9, 0x2ce8, 0x2d08, 0x2d29,
+ 0x2d4a, 0x2d6c, 0x2d8e, 0x2db2, 0x2dd5, 0x2dfa, 0x2e0f, 0x2e22, 0x2e36,
+ 0x2e49, 0x2e5d, 0x2e71, 0x2e86, 0x2e9b, 0x2eb0, 0x2ec6, 0x2edc, 0x2ef2,
+ 0x2f09, 0x2f20, 0x2f37, 0x2f4e, 0x2f66, 0x2f7f, 0x2f97, 0x2fb0, 0x2fc9,
+ 0x2fe3, 0x2ffd, 0x300b, 0x3019, 0x3026, 0x3034, 0x3042, 0x3050, 0x305e,
+ 0x306c, 0x307b, 0x3089, 0x3098, 0x30a7, 0x30b6, 0x30c5, 0x30d5, 0x30e4,
+ 0x30f4, 0x3104, 0x3114, 0x3125, 0x3135, 0x3146, 0x3157, 0x3168, 0x3179,
+ 0x318a, 0x319c, 0x31ad, 0x31bf, 0x31d1, 0x31e3, 0x31f5, 0x3204, 0x320d,
+ 0x3216, 0x3220, 0x3229, 0x3233, 0x323d, 0x3247, 0x3251, 0x325b, 0x3265,
+ 0x326f, 0x3279, 0x3283, 0x328e, 0x3298, 0x32a3, 0x32ae, 0x32b8, 0x32c4,
+ 0x32cf, 0x32da, 0x32e6, 0x32f2, 0x32ff, 0x330c, 0x3318, 0x3326, 0x3333,
+ 0x3341, 0x334f, 0x335e, 0x336c, 0x337c, 0x338b, 0x339b, 0x33ab, 0x33bb,
+ 0x33cc, 0x33dd, 0x33ef, 0x3400, 0x3409, 0x3413, 0x341c, 0x3426, 0x3430,
+ 0x343a, 0x3445, 0x3450, 0x345b, 0x3466, 0x3471, 0x347d, 0x3489, 0x3495,
+ 0x34a2, 0x34ae, 0x34bb, 0x34c9, 0x34d6, 0x34e4, 0x34f2, 0x3501, 0x3510,
+ 0x351f, 0x352f, 0x353e, 0x354f, 0x355f, 0x3570, 0x3581, 0x3593, 0x35a5,
+ 0x35b8, 0x35cb, 0x35de, 0x35f2, 0x3603, 0x360d, 0x3618, 0x3622, 0x362d,
+ 0x3639, 0x3644, 0x3650, 0x365c, 0x3668, 0x3675, 0x3682, 0x368f, 0x369c,
+ 0x36aa, 0x36b8, 0x36c6, 0x36d5, 0x36e4, 0x36f3, 0x3703, 0x3713, 0x3723,
+ 0x3734, 0x3745, 0x3756, 0x3768, 0x377a, 0x378d, 0x37a0, 0x37b3, 0x37c7,
+ 0x37dc, 0x37f0, 0x3802, 0x380d, 0x3818, 0x3824, 0x382f, 0x383b, 0x3847,
+ 0x3854, 0x3860, 0x386d, 0x387a, 0x3888, 0x3896, 0x38a4, 0x38b2, 0x38c1,
+ 0x38d0, 0x38df, 0x38ef, 0x38ff, 0x3910, 0x3920, 0x3932, 0x3943, 0x3955,
+ 0x3967, 0x397a, 0x398d, 0x39a1, 0x39b5, 0x39c9, 0x39de, 0x39f4, 0x0,
+ 0x12aa, 0x16ab, 0x1900, 0x1aab, 0x1c15, 0x1d00, 0x1e0b, 0x1eab, 0x1f60,
+ 0x2015, 0x2085, 0x2100, 0x2186, 0x220b, 0x2258, 0x22ab, 0x2303, 0x2360,
+ 0x23c3, 0x2415, 0x244c, 0x2485, 0x24c2, 0x2500, 0x2542, 0x2586, 0x25cc,
+ 0x260b, 0x2631, 0x2658, 0x2681, 0x26ab, 0x26d6, 0x2703, 0x2731, 0x2760,
+ 0x2791, 0x27c3, 0x27f6, 0x2815, 0x2830, 0x284c, 0x2868, 0x2885, 0x28a3,
+ 0x28c2, 0x28e1, 0x2900, 0x2921, 0x2942, 0x2963, 0x2986, 0x29a9, 0x29cc,
+ 0x29f1, 0x2a0b, 0x2a1e, 0x2a31, 0x2a44, 0x2a58, 0x2a6c, 0x2a81, 0x2a96,
+ 0x2aab, 0x2ac0, 0x2ad6, 0x2aec, 0x2b03, 0x2b1a, 0x2b31, 0x2b48, 0x2b60,
+ 0x2b79, 0x2b91, 0x2baa, 0x2bc3, 0x2bdd, 0x2bf6, 0x2c08, 0x2c15, 0x2c23,
+ 0x2c30, 0x2c3e, 0x2c4c, 0x2c5a, 0x2c68, 0x2c77, 0x2c85, 0x2c94, 0x2ca3,
+ 0x2cb2, 0x2cc2, 0x2cd1, 0x2ce1, 0x2cf0, 0x2d00, 0x2d10, 0x2d21, 0x2d31,
+ 0x2d42, 0x2d52, 0x2d63, 0x2d74, 0x2d86, 0x2d97, 0x2da9, 0x2dbb, 0x2dcc,
+ 0x2ddf, 0x2df1, 0x2e01, 0x2e0b, 0x2e14, 0x2e1e, 0x2e27, 0x2e31, 0x2e3a,
+ 0x2e44, 0x2e4e, 0x2e58, 0x2e62, 0x2e6c, 0x2e76, 0x2e81, 0x2e8b, 0x2e96,
+ 0x2ea0, 0x2eab, 0x2eb6, 0x2ec0, 0x2ecb, 0x2ed6, 0x2ee1, 0x2eec, 0x2ef8,
+ 0x2f03, 0x2f0e, 0x2f1a, 0x2f25, 0x2f31, 0x2f3d, 0x2f48, 0x2f54, 0x2f60,
+ 0x2f6c, 0x2f79, 0x2f85, 0x2f91, 0x2f9d, 0x2faa, 0x2fb6, 0x2fc3, 0x2fd0,
+ 0x2fdd, 0x2fea, 0x2ff6, 0x3002, 0x3008, 0x300f, 0x3015, 0x301c, 0x3023,
+ 0x302a, 0x3030, 0x3037, 0x303e, 0x3045, 0x304c, 0x3053, 0x305a, 0x3061,
+ 0x3068, 0x3070, 0x3077, 0x307e, 0x3085, 0x308d, 0x3094, 0x309c, 0x30a3,
+ 0x30ab, 0x30b2, 0x30ba, 0x30c2, 0x30c9, 0x30d1, 0x30d9, 0x30e1, 0x30e8,
+ 0x30f0, 0x30f8, 0x3100, 0x3108, 0x3110, 0x3118, 0x3121, 0x3129, 0x3131,
+ 0x3139, 0x3142, 0x314a, 0x3152, 0x315b, 0x3163, 0x316c, 0x3174, 0x317d,
+ 0x3186, 0x318e, 0x3197, 0x31a0, 0x31a9, 0x31b2, 0x31bb, 0x31c3, 0x31cc,
+ 0x31d5, 0x31df, 0x31e8, 0x31f1, 0x31fa, 0x3201, 0x3206, 0x320b, 0x320f,
+ 0x3214, 0x3219, 0x321e, 0x3222, 0x3227, 0x322c, 0x3231, 0x3236, 0x323a,
+ 0x323f, 0x3244, 0x3249, 0x324e, 0x3253, 0x3258, 0x325d, 0x3262, 0x3267,
+ 0x326c, 0x3271, 0x3276, 0x327c, 0x3281, 0x3286, 0x328b, 0x3290, 0x3296,
+ 0x329b, 0x32a0, 0x32a6, 0x32ab, 0x32b0, 0x32b6, 0x32bb, 0x32c1, 0x32c6,
+ 0x32cc, 0x32d2, 0x32d8, 0x32dd, 0x32e3, 0x32e9, 0x32ef, 0x32f6, 0x32fc,
+ 0x3302, 0x3308, 0x330f, 0x3315, 0x331c, 0x3322, 0x3329, 0x3330, 0x3337,
+ 0x333e, 0x3345, 0x334c, 0x3353, 0x335a, 0x3361, 0x3369, 0x3370, 0x3378,
+ 0x337f, 0x3387, 0x338f, 0x3397, 0x339f, 0x33a7, 0x33af, 0x33b7, 0x33bf,
+ 0x33c8, 0x33d0, 0x33d9, 0x33e2, 0x33eb, 0x33f3, 0x33fc, 0x3402, 0x3407,
+ 0x340c, 0x3410, 0x3415, 0x341a, 0x341f, 0x3424, 0x3429, 0x342e, 0x3433,
+ 0x3438, 0x343d, 0x3442, 0x3448, 0x344d, 0x3452, 0x3458, 0x345d, 0x3463,
+ 0x3469, 0x346e, 0x3474, 0x347a, 0x3480, 0x3486, 0x348c, 0x3492, 0x3498,
+ 0x349e, 0x34a5, 0x34ab, 0x34b2, 0x34b8, 0x34bf, 0x34c5, 0x34cc, 0x34d3,
+ 0x34da, 0x34e1, 0x34e8, 0x34ef, 0x34f6, 0x34fd, 0x3505, 0x350c, 0x3514,
+ 0x351b, 0x3523, 0x352b, 0x3532, 0x353a, 0x3542, 0x354b, 0x3553, 0x355b,
+ 0x3563, 0x356c, 0x3574, 0x357d, 0x3586, 0x358f, 0x3598, 0x35a1, 0x35aa,
+ 0x35b3, 0x35bc, 0x35c6, 0x35cf, 0x35d9, 0x35e3, 0x35ed, 0x35f7, 0x3600,
+ 0x3605, 0x360a, 0x3610, 0x3615, 0x361a, 0x3620, 0x3625, 0x362b, 0x3630,
+ 0x3636, 0x363b, 0x3641, 0x3647, 0x364d, 0x3653, 0x3659, 0x365f, 0x3665,
+ 0x366b, 0x3672, 0x3678, 0x367e, 0x3685, 0x368b, 0x3692, 0x3699, 0x36a0,
+ 0x36a6, 0x36ad, 0x36b4, 0x36bb, 0x36c3, 0x36ca, 0x36d1, 0x36d9, 0x36e0,
+ 0x36e8, 0x36ef, 0x36f7, 0x36ff, 0x3707, 0x370f, 0x3717, 0x371f, 0x3727,
+ 0x372f, 0x3738, 0x3740, 0x3749, 0x3752, 0x375b, 0x3764, 0x376d, 0x3776,
+ 0x377f, 0x3788, 0x3792, 0x379b, 0x37a5, 0x37ae, 0x37b8, 0x37c2, 0x37cc,
+ 0x37d6, 0x37e1, 0x37eb, 0x37f6, 0x3800, 0x3805, 0x380b, 0x3810, 0x3816,
+ 0x381b, 0x3821, 0x3827, 0x382c, 0x3832, 0x3838, 0x383e, 0x3844, 0x384a,
+ 0x3851, 0x3857, 0x385d, 0x3864, 0x386a, 0x3870, 0x3877, 0x387e, 0x3885,
+ 0x388b, 0x3892, 0x3899, 0x38a0, 0x38a7, 0x38af, 0x38b6, 0x38bd, 0x38c5,
+ 0x38cc, 0x38d4, 0x38dc, 0x38e3, 0x38eb, 0x38f3, 0x38fb, 0x3903, 0x390c,
+ 0x3914, 0x391c, 0x3925, 0x392d, 0x3936, 0x393f, 0x3948, 0x3951, 0x395a,
+ 0x3963, 0x396c, 0x3975, 0x397f, 0x3989, 0x3992, 0x399c, 0x39a6, 0x39b0,
+ 0x39ba, 0x39c4, 0x39cf, 0x39d9, 0x39e4, 0x39ee, 0x39f9, 0xc},
+ {0x1329, 0x468, 0x2f5a, 0x12e, 0xa44, 0x1f6d, 0x3fff, 0x65, 0x27f,
+ 0x6fb, 0xe50, 0x18da, 0x26ea, 0x38c5, 0x3fff, 0x2c, 0xba, 0x1c4, 0x35f,
+ 0x59c, 0x888, 0xc31, 0x10a2, 0x15e6, 0x1c07, 0x230e, 0x2b03, 0x33ef,
+ 0x3ddb, 0x3fff, 0x3fff, 0x15, 0x45, 0x8c, 0xf0, 0x175, 0x21d, 0x2ea,
+ 0x3df, 0x4fd, 0x646, 0x7bc, 0x960, 0xb35, 0xd3a, 0xf73, 0x11df, 0x1481,
+ 0x1759, 0x1a69, 0x1db3, 0x2136, 0x24f4, 0x28ef, 0x2d27, 0x319d, 0x3652,
+ 0x3b48, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0xa, 0x21, 0x38, 0x54,
+ 0x78, 0xa2, 0xd4, 0x10e, 0x150, 0x19b, 0x1ef, 0x24c, 0x2b3, 0x323,
+ 0x39e, 0x422, 0x4b1, 0x54b, 0x5ef, 0x69f, 0x75a, 0x821, 0x8f3, 0x9d1,
+ 0xabb, 0xbb1, 0xcb4, 0xdc3, 0xee0, 0x1009, 0x113f, 0x1282, 0x13d3,
+ 0x1532, 0x169e, 0x1818, 0x19a0, 0x1b36, 0x1cdb, 0x1e8e, 0x2050, 0x2220,
+ 0x23ff, 0x25ed, 0x27ea, 0x29f7, 0x2c13, 0x2e3e, 0x3079, 0x32c4, 0x351f,
+ 0x3789, 0x3a04, 0x3c8f, 0x3f2b, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x4, 0xf, 0x1b, 0x27, 0x32, 0x3f, 0x4d,
+ 0x5d, 0x6e, 0x81, 0x97, 0xae, 0xc7, 0xe2, 0xff, 0x11e, 0x13f, 0x162,
+ 0x188, 0x1b0, 0x1d9, 0x206, 0x234, 0x265, 0x298, 0x2ce, 0x306, 0x341,
+ 0x37e, 0x3be, 0x400, 0x445, 0x48c, 0x4d7, 0x523, 0x573, 0x5c5, 0x61a,
+ 0x672, 0x6cd, 0x72a, 0x78b, 0x7ee, 0x854, 0x8bd, 0x929, 0x998, 0xa0a,
+ 0xa7f, 0xaf7, 0xb73, 0xbf1, 0xc72, 0xcf7, 0xd7e, 0xe09, 0xe97, 0xf29,
+ 0xfbd, 0x1055, 0x10f0, 0x118f, 0x1230, 0x12d5, 0x137e, 0x142a, 0x14d9,
+ 0x158c, 0x1642, 0x16fb, 0x17b8, 0x1879, 0x193d, 0x1a04, 0x1acf, 0x1b9e,
+ 0x1c70, 0x1d46, 0x1e20, 0x1efd, 0x1fde, 0x20c2, 0x21aa, 0x2296, 0x2386,
+ 0x2479, 0x2570, 0x266b, 0x276a, 0x286c, 0x2972, 0x2a7c, 0x2b8a, 0x2c9c,
+ 0x2db2, 0x2ecc, 0x2fe9, 0x310b, 0x3230, 0x3359, 0x3487, 0x35b8, 0x36ed,
+ 0x3827, 0x3964, 0x3aa5, 0x3beb, 0x3d35, 0x3e82, 0x3fd4, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x1, 0x7, 0xd,
+ 0x12, 0x18, 0x1e, 0x24, 0x29, 0x2f, 0x35, 0x3b, 0x42, 0x49, 0x51, 0x58,
+ 0x61, 0x6a, 0x73, 0x7c, 0x87, 0x91, 0x9c, 0xa8, 0xb4, 0xc0, 0xcd, 0xdb,
+ 0xe9, 0xf7, 0x106, 0x116, 0x126, 0x137, 0x148, 0x159, 0x16b, 0x17e,
+ 0x192, 0x1a5, 0x1ba, 0x1cf, 0x1e4, 0x1fa, 0x211, 0x228, 0x240, 0x259,
+ 0x272, 0x28b, 0x2a6, 0x2c1, 0x2dc, 0x2f8, 0x315, 0x332, 0x350, 0x36f,
+ 0x38e, 0x3ae, 0x3ce, 0x3ef, 0x411, 0x434, 0x457, 0x47a, 0x49f, 0x4c4,
+ 0x4ea, 0x510, 0x537, 0x55f, 0x587, 0x5b0, 0x5da, 0x605, 0x630, 0x65c,
+ 0x689, 0x6b6, 0x6e4, 0x713, 0x742, 0x772, 0x7a3, 0x7d5, 0x807, 0x83a,
+ 0x86e, 0x8a3, 0x8d8, 0x90e, 0x945, 0x97c, 0x9b4, 0x9ed, 0xa27, 0xa62,
+ 0xa9d, 0xad9, 0xb16, 0xb53, 0xb92, 0xbd1, 0xc11, 0xc52, 0xc93, 0xcd5,
+ 0xd18, 0xd5c, 0xda1, 0xde6, 0xe2d, 0xe74, 0xebb, 0xf04, 0xf4e, 0xf98,
+ 0xfe3, 0x102f, 0x107c, 0x10c9, 0x1117, 0x1167, 0x11b7, 0x1208, 0x1259,
+ 0x12ac, 0x12ff, 0x1353, 0x13a8, 0x13fe, 0x1455, 0x14ad, 0x1505, 0x155f,
+ 0x15b9, 0x1614, 0x1670, 0x16cc, 0x172a, 0x1789, 0x17e8, 0x1848, 0x18a9,
+ 0x190b, 0x196e, 0x19d2, 0x1a37, 0x1a9c, 0x1b03, 0x1b6a, 0x1bd2, 0x1c3c,
+ 0x1ca6, 0x1d11, 0x1d7c, 0x1de9, 0x1e57, 0x1ec5, 0x1f35, 0x1fa5, 0x2017,
+ 0x2089, 0x20fc, 0x2170, 0x21e5, 0x225b, 0x22d2, 0x234a, 0x23c2, 0x243c,
+ 0x24b7, 0x2532, 0x25af, 0x262c, 0x26aa, 0x272a, 0x27aa, 0x282b, 0x28ad,
+ 0x2930, 0x29b5, 0x2a3a, 0x2ac0, 0x2b47, 0x2bcf, 0x2c57, 0x2ce1, 0x2d6c,
+ 0x2df8, 0x2e85, 0x2f13, 0x2fa1, 0x3031, 0x30c2, 0x3154, 0x31e6, 0x327a,
+ 0x330f, 0x33a4, 0x343b, 0x34d3, 0x356b, 0x3605, 0x36a0, 0x373b, 0x37d8,
+ 0x3876, 0x3914, 0x39b4, 0x3a55, 0x3af6, 0x3b99, 0x3c3d, 0x3ce2, 0x3d88,
+ 0x3e2e, 0x3ed6, 0x3f7f, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x0, 0x2, 0x5, 0x8, 0xb, 0xe, 0x11,
+ 0x14, 0x17, 0x1a, 0x1c, 0x1f, 0x22, 0x25, 0x28, 0x2b, 0x2e, 0x31, 0x34,
+ 0x37, 0x3a, 0x3d, 0x40, 0x44, 0x47, 0x4b, 0x4f, 0x52, 0x56, 0x5b, 0x5f,
+ 0x63, 0x67, 0x6c, 0x70, 0x75, 0x7a, 0x7f, 0x84, 0x89, 0x8f, 0x94, 0x99,
+ 0x9f, 0xa5, 0xab, 0xb1, 0xb7, 0xbd, 0xc4, 0xca, 0xd1, 0xd7, 0xde, 0xe5,
+ 0xec, 0xf4, 0xfb, 0x103, 0x10a, 0x112, 0x11a, 0x122, 0x12a, 0x132,
+ 0x13b, 0x143, 0x14c, 0x155, 0x15e, 0x167, 0x170, 0x179, 0x183, 0x18d,
+ 0x196, 0x1a0, 0x1aa, 0x1b5, 0x1bf, 0x1c9, 0x1d4, 0x1df, 0x1ea, 0x1f5,
+ 0x200, 0x20b, 0x217, 0x223, 0x22e, 0x23a, 0x246, 0x253, 0x25f, 0x26b,
+ 0x278, 0x285, 0x292, 0x29f, 0x2ac, 0x2ba, 0x2c7, 0x2d5, 0x2e3, 0x2f1,
+ 0x2ff, 0x30e, 0x31c, 0x32b, 0x33a, 0x349, 0x358, 0x367, 0x376, 0x386,
+ 0x396, 0x3a6, 0x3b6, 0x3c6, 0x3d6, 0x3e7, 0x3f8, 0x409, 0x41a, 0x42b,
+ 0x43c, 0x44e, 0x45f, 0x471, 0x483, 0x496, 0x4a8, 0x4bb, 0x4cd, 0x4e0,
+ 0x4f3, 0x506, 0x51a, 0x52d, 0x541, 0x555, 0x569, 0x57d, 0x592, 0x5a6,
+ 0x5bb, 0x5d0, 0x5e5, 0x5fa, 0x610, 0x625, 0x63b, 0x651, 0x667, 0x67d,
+ 0x694, 0x6aa, 0x6c1, 0x6d8, 0x6f0, 0x707, 0x71e, 0x736, 0x74e, 0x766,
+ 0x77e, 0x797, 0x7af, 0x7c8, 0x7e1, 0x7fa, 0x814, 0x82d, 0x847, 0x861,
+ 0x87b, 0x895, 0x8b0, 0x8ca, 0x8e5, 0x900, 0x91b, 0x937, 0x952, 0x96e,
+ 0x98a, 0x9a6, 0x9c3, 0x9df, 0x9fc, 0xa19, 0xa36, 0xa53, 0xa70, 0xa8e,
+ 0xaac, 0xaca, 0xae8, 0xb07, 0xb25, 0xb44, 0xb63, 0xb82, 0xba2, 0xbc1,
+ 0xbe1, 0xc01, 0xc21, 0xc41, 0xc62, 0xc83, 0xca4, 0xcc5, 0xce6, 0xd08,
+ 0xd29, 0xd4b, 0xd6d, 0xd90, 0xdb2, 0xdd5, 0xdf8, 0xe1b, 0xe3e, 0xe62,
+ 0xe85, 0xea9, 0xece, 0xef2, 0xf16, 0xf3b, 0xf60, 0xf85, 0xfab, 0xfd0,
+ 0xff6, 0x101c, 0x1042, 0x1068, 0x108f, 0x10b6, 0x10dd, 0x1104, 0x112b,
+ 0x1153, 0x117b, 0x11a3, 0x11cb, 0x11f3, 0x121c, 0x1245, 0x126e, 0x1297,
+ 0x12c1, 0x12ea, 0x1314, 0x133e, 0x1369, 0x1393, 0x13be, 0x13e9, 0x1414,
+ 0x143f, 0x146b, 0x1497, 0x14c3, 0x14ef, 0x151b, 0x1548, 0x1575, 0x15a2,
+ 0x15cf, 0x15fd, 0x162b, 0x1659, 0x1687, 0x16b5, 0x16e4, 0x1713, 0x1742,
+ 0x1771, 0x17a0, 0x17d0, 0x1800, 0x1830, 0x1860, 0x1891, 0x18c2, 0x18f3,
+ 0x1924, 0x1956, 0x1987, 0x19b9, 0x19eb, 0x1a1e, 0x1a50, 0x1a83, 0x1ab6,
+ 0x1ae9, 0x1b1d, 0x1b50, 0x1b84, 0x1bb8, 0x1bed, 0x1c21, 0x1c56, 0x1c8b,
+ 0x1cc0, 0x1cf6, 0x1d2b, 0x1d61, 0x1d97, 0x1dce, 0x1e04, 0x1e3b, 0x1e72,
+ 0x1eaa, 0x1ee1, 0x1f19, 0x1f51, 0x1f89, 0x1fc1, 0x1ffa, 0x2033, 0x206c,
+ 0x20a5, 0x20df, 0x2119, 0x2153, 0x218d, 0x21c8, 0x2202, 0x223d, 0x2279,
+ 0x22b4, 0x22f0, 0x232c, 0x2368, 0x23a4, 0x23e1, 0x241d, 0x245a, 0x2498,
+ 0x24d5, 0x2513, 0x2551, 0x258f, 0x25ce, 0x260d, 0x264b, 0x268b, 0x26ca,
+ 0x270a, 0x274a, 0x278a, 0x27ca, 0x280b, 0x284c, 0x288d, 0x28ce, 0x2910,
+ 0x2951, 0x2993, 0x29d6, 0x2a18, 0x2a5b, 0x2a9e, 0x2ae1, 0x2b25, 0x2b68,
+ 0x2bac, 0x2bf1, 0x2c35, 0x2c7a, 0x2cbf, 0x2d04, 0x2d49, 0x2d8f, 0x2dd5,
+ 0x2e1b, 0x2e62, 0x2ea8, 0x2eef, 0x2f36, 0x2f7e, 0x2fc5, 0x300d, 0x3055,
+ 0x309e, 0x30e6, 0x312f, 0x3178, 0x31c1, 0x320b, 0x3255, 0x329f, 0x32e9,
+ 0x3334, 0x337f, 0x33ca, 0x3415, 0x3461, 0x34ad, 0x34f9, 0x3545, 0x3592,
+ 0x35de, 0x362b, 0x3679, 0x36c6, 0x3714, 0x3762, 0x37b1, 0x37ff, 0x384e,
+ 0x389d, 0x38ed, 0x393c, 0x398c, 0x39dc, 0x3a2c, 0x3a7d, 0x3ace, 0x3b1f,
+ 0x3b70, 0x3bc2, 0x3c14, 0x3c66, 0x3cb8, 0x3d0b, 0x3d5e, 0x3db1, 0x3e05,
+ 0x3e58, 0x3eac, 0x3f00, 0x3f55, 0x3faa, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x6},
+ {0xdab, 0x33c, 0x216a, 0xe8, 0x766, 0x1646, 0x2f44, 0x52, 0x1dc, 0x512,
+ 0xa41, 0x11aa, 0x1b84, 0x27fd, 0x3743, 0x26, 0x92, 0x155, 0x27e, 0x418,
+ 0x62b, 0x8c2, 0xbe4, 0xf97, 0x13e4, 0x18d0, 0x1e61, 0x249e, 0x2b8a,
+ 0x332c, 0x3b89, 0x12, 0x3a, 0x70, 0xba, 0x11c, 0x196, 0x22a, 0x2da,
+ 0x3a6, 0x491, 0x59a, 0x6c4, 0x810, 0x97d, 0xb0e, 0xcc3, 0xe9c, 0x109c,
+ 0x12c2, 0x1510, 0x1786, 0x1a25, 0x1ced, 0x1fe0, 0x22fe, 0x2648, 0x29be,
+ 0x2d61, 0x3132, 0x3532, 0x3960, 0x3dbe, 0x8, 0x1c, 0x30, 0x46, 0x60,
+ 0x80, 0xa5, 0xd0, 0x101, 0x138, 0x175, 0x1b8, 0x202, 0x253, 0x2ab,
+ 0x30a, 0x370, 0x3de, 0x453, 0x4d0, 0x555, 0x5e2, 0x677, 0x714, 0x7ba,
+ 0x868, 0x91e, 0x9de, 0xaa6, 0xb77, 0xc52, 0xd35, 0xe22, 0xf19, 0x1018,
+ 0x1122, 0x1235, 0x1352, 0x1479, 0x15aa, 0x16e4, 0x182a, 0x1979, 0x1ad3,
+ 0x1c37, 0x1da6, 0x1f1f, 0x20a4, 0x2233, 0x23cc, 0x2571, 0x2721, 0x28dc,
+ 0x2aa3, 0x2c74, 0x2e51, 0x303a, 0x322e, 0x342e, 0x3639, 0x3850, 0x3a73,
+ 0x3ca2, 0x3edd, 0x3, 0xd, 0x17, 0x21, 0x2b, 0x35, 0x40, 0x4c, 0x59,
+ 0x68, 0x78, 0x89, 0x9c, 0xb0, 0xc5, 0xdc, 0xf4, 0x10e, 0x12a, 0x146,
+ 0x165, 0x185, 0x1a7, 0x1ca, 0x1ef, 0x216, 0x23e, 0x269, 0x294, 0x2c2,
+ 0x2f2, 0x323, 0x356, 0x38b, 0x3c2, 0x3fb, 0x435, 0x472, 0x4b0, 0x4f1,
+ 0x533, 0x577, 0x5be, 0x606, 0x651, 0x69d, 0x6ec, 0x73d, 0x78f, 0x7e4,
+ 0x83b, 0x895, 0x8f0, 0x94d, 0x9ad, 0xa0f, 0xa73, 0xada, 0xb42, 0xbad,
+ 0xc1a, 0xc8a, 0xcfc, 0xd70, 0xde6, 0xe5f, 0xeda, 0xf58, 0xfd8, 0x105a,
+ 0x10df, 0x1166, 0x11ef, 0x127b, 0x130a, 0x139b, 0x142e, 0x14c4, 0x155c,
+ 0x15f7, 0x1695, 0x1735, 0x17d7, 0x187d, 0x1924, 0x19cf, 0x1a7b, 0x1b2b,
+ 0x1bdd, 0x1c92, 0x1d49, 0x1e03, 0x1ec0, 0x1f7f, 0x2042, 0x2106, 0x21ce,
+ 0x2298, 0x2365, 0x2435, 0x2507, 0x25dc, 0x26b4, 0x278f, 0x286d, 0x294d,
+ 0x2a30, 0x2b16, 0x2bff, 0x2ceb, 0x2dd9, 0x2eca, 0x2fbf, 0x30b6, 0x31b0,
+ 0x32ad, 0x33ac, 0x34af, 0x35b5, 0x36bd, 0x37c9, 0x38d8, 0x39e9, 0x3afd,
+ 0x3c15, 0x3d2f, 0x3e4d, 0x3f6d, 0x1, 0x6, 0xb, 0x10, 0x15, 0x1a, 0x1e,
+ 0x23, 0x28, 0x2d, 0x32, 0x37, 0x3d, 0x43, 0x49, 0x4f, 0x56, 0x5d, 0x64,
+ 0x6c, 0x74, 0x7c, 0x85, 0x8e, 0x97, 0xa0, 0xaa, 0xb5, 0xc0, 0xcb, 0xd6,
+ 0xe2, 0xee, 0xfb, 0x108, 0x115, 0x123, 0x131, 0x13f, 0x14e, 0x15d,
+ 0x16d, 0x17d, 0x18d, 0x19e, 0x1af, 0x1c1, 0x1d3, 0x1e6, 0x1f9, 0x20c,
+ 0x220, 0x234, 0x249, 0x25e, 0x273, 0x289, 0x2a0, 0x2b7, 0x2ce, 0x2e6,
+ 0x2fe, 0x316, 0x32f, 0x349, 0x363, 0x37e, 0x398, 0x3b4, 0x3d0, 0x3ec,
+ 0x409, 0x426, 0x444, 0x462, 0x481, 0x4a0, 0x4c0, 0x4e0, 0x501, 0x522,
+ 0x544, 0x566, 0x589, 0x5ac, 0x5d0, 0x5f4, 0x619, 0x63e, 0x664, 0x68a,
+ 0x6b1, 0x6d8, 0x700, 0x728, 0x751, 0x77a, 0x7a4, 0x7cf, 0x7fa, 0x825,
+ 0x851, 0x87e, 0x8ab, 0x8d9, 0x907, 0x936, 0x965, 0x995, 0x9c5, 0x9f6,
+ 0xa28, 0xa5a, 0xa8d, 0xac0, 0xaf4, 0xb28, 0xb5d, 0xb92, 0xbc8, 0xbff,
+ 0xc36, 0xc6e, 0xca6, 0xcdf, 0xd19, 0xd53, 0xd8d, 0xdc8, 0xe04, 0xe41,
+ 0xe7e, 0xebb, 0xef9, 0xf38, 0xf77, 0xfb7, 0xff8, 0x1039, 0x107b,
+ 0x10bd, 0x1100, 0x1144, 0x1188, 0x11cd, 0x1212, 0x1258, 0x129f, 0x12e6,
+ 0x132e, 0x1376, 0x13bf, 0x1409, 0x1453, 0x149e, 0x14ea, 0x1536, 0x1583,
+ 0x15d0, 0x161e, 0x166d, 0x16bd, 0x170d, 0x175d, 0x17af, 0x1800, 0x1853,
+ 0x18a6, 0x18fa, 0x194f, 0x19a4, 0x19fa, 0x1a50, 0x1aa7, 0x1aff, 0x1b57,
+ 0x1bb0, 0x1c0a, 0x1c64, 0x1cbf, 0x1d1b, 0x1d78, 0x1dd5, 0x1e32, 0x1e91,
+ 0x1ef0, 0x1f4f, 0x1fb0, 0x2011, 0x2072, 0x20d5, 0x2138, 0x219c, 0x2200,
+ 0x2265, 0x22cb, 0x2331, 0x2399, 0x2401, 0x2469, 0x24d2, 0x253c, 0x25a7,
+ 0x2612, 0x267e, 0x26eb, 0x2758, 0x27c6, 0x2835, 0x28a4, 0x2915, 0x2985,
+ 0x29f7, 0x2a69, 0x2adc, 0x2b50, 0x2bc4, 0x2c3a, 0x2caf, 0x2d26, 0x2d9d,
+ 0x2e15, 0x2e8e, 0x2f07, 0x2f81, 0x2ffc, 0x3078, 0x30f4, 0x3171, 0x31ef,
+ 0x326d, 0x32ec, 0x336c, 0x33ed, 0x346e, 0x34f0, 0x3573, 0x35f7, 0x367b,
+ 0x3700, 0x3786, 0x380c, 0x3894, 0x391c, 0x39a4, 0x3a2e, 0x3ab8, 0x3b43,
+ 0x3bcf, 0x3c5b, 0x3ce8, 0x3d76, 0x3e05, 0x3e95, 0x3f25, 0x3fb6, 0x0,
+ 0x2, 0x4, 0x7, 0x9, 0xc, 0xe, 0x11, 0x13, 0x16, 0x18, 0x1b, 0x1d, 0x20,
+ 0x22, 0x25, 0x27, 0x2a, 0x2c, 0x2f, 0x31, 0x34, 0x36, 0x39, 0x3c, 0x3e,
+ 0x41, 0x44, 0x47, 0x4a, 0x4d, 0x51, 0x54, 0x57, 0x5b, 0x5f, 0x62, 0x66,
+ 0x6a, 0x6e, 0x72, 0x76, 0x7a, 0x7e, 0x82, 0x87, 0x8b, 0x90, 0x94, 0x99,
+ 0x9e, 0xa3, 0xa8, 0xad, 0xb2, 0xb7, 0xbd, 0xc2, 0xc8, 0xcd, 0xd3, 0xd9,
+ 0xdf, 0xe5, 0xeb, 0xf1, 0xf7, 0xfe, 0x104, 0x10b, 0x111, 0x118, 0x11f,
+ 0x126, 0x12d, 0x134, 0x13b, 0x143, 0x14a, 0x152, 0x159, 0x161, 0x169,
+ 0x171, 0x179, 0x181, 0x189, 0x192, 0x19a, 0x1a2, 0x1ab, 0x1b4, 0x1bd,
+ 0x1c6, 0x1cf, 0x1d8, 0x1e1, 0x1ea, 0x1f4, 0x1fe, 0x207, 0x211, 0x21b,
+ 0x225, 0x22f, 0x239, 0x244, 0x24e, 0x258, 0x263, 0x26e, 0x279, 0x284,
+ 0x28f, 0x29a, 0x2a5, 0x2b1, 0x2bc, 0x2c8, 0x2d4, 0x2e0, 0x2ec, 0x2f8,
+ 0x304, 0x310, 0x31d, 0x329, 0x336, 0x343, 0x350, 0x35d, 0x36a, 0x377,
+ 0x384, 0x392, 0x39f, 0x3ad, 0x3bb, 0x3c9, 0x3d7, 0x3e5, 0x3f3, 0x402,
+ 0x410, 0x41f, 0x42e, 0x43d, 0x44c, 0x45b, 0x46a, 0x479, 0x489, 0x499,
+ 0x4a8, 0x4b8, 0x4c8, 0x4d8, 0x4e8, 0x4f9, 0x509, 0x51a, 0x52b, 0x53b,
+ 0x54c, 0x55e, 0x56f, 0x580, 0x592, 0x5a3, 0x5b5, 0x5c7, 0x5d9, 0x5eb,
+ 0x5fd, 0x60f, 0x622, 0x635, 0x647, 0x65a, 0x66d, 0x680, 0x694, 0x6a7,
+ 0x6bb, 0x6ce, 0x6e2, 0x6f6, 0x70a, 0x71e, 0x732, 0x747, 0x75b, 0x770,
+ 0x785, 0x79a, 0x7af, 0x7c4, 0x7da, 0x7ef, 0x805, 0x81a, 0x830, 0x846,
+ 0x85d, 0x873, 0x889, 0x8a0, 0x8b7, 0x8cd, 0x8e4, 0x8fb, 0x913, 0x92a,
+ 0x942, 0x959, 0x971, 0x989, 0x9a1, 0x9b9, 0x9d2, 0x9ea, 0xa03, 0xa1c,
+ 0xa34, 0xa4d, 0xa67, 0xa80, 0xa99, 0xab3, 0xacd, 0xae7, 0xb01, 0xb1b,
+ 0xb35, 0xb50, 0xb6a, 0xb85, 0xba0, 0xbbb, 0xbd6, 0xbf1, 0xc0d, 0xc28,
+ 0xc44, 0xc60, 0xc7c, 0xc98, 0xcb4, 0xcd1, 0xced, 0xd0a, 0xd27, 0xd44,
+ 0xd61, 0xd7e, 0xd9c, 0xdba, 0xdd7, 0xdf5, 0xe13, 0xe31, 0xe50, 0xe6e,
+ 0xe8d, 0xeac, 0xecb, 0xeea, 0xf09, 0xf28, 0xf48, 0xf68, 0xf87, 0xfa7,
+ 0xfc7, 0xfe8, 0x1008, 0x1029, 0x1049, 0x106a, 0x108b, 0x10ad, 0x10ce,
+ 0x10ef, 0x1111, 0x1133, 0x1155, 0x1177, 0x1199, 0x11bb, 0x11de, 0x1201,
+ 0x1223, 0x1246, 0x126a, 0x128d, 0x12b0, 0x12d4, 0x12f8, 0x131c, 0x1340,
+ 0x1364, 0x1388, 0x13ad, 0x13d2, 0x13f6, 0x141c, 0x1441, 0x1466, 0x148b,
+ 0x14b1, 0x14d7, 0x14fd, 0x1523, 0x1549, 0x1570, 0x1596, 0x15bd, 0x15e4,
+ 0x160b, 0x1632, 0x1659, 0x1681, 0x16a9, 0x16d1, 0x16f9, 0x1721, 0x1749,
+ 0x1771, 0x179a, 0x17c3, 0x17ec, 0x1815, 0x183e, 0x1868, 0x1891, 0x18bb,
+ 0x18e5, 0x190f, 0x1939, 0x1964, 0x198e, 0x19b9, 0x19e4, 0x1a0f, 0x1a3a,
+ 0x1a66, 0x1a91, 0x1abd, 0x1ae9, 0x1b15, 0x1b41, 0x1b6d, 0x1b9a, 0x1bc7,
+ 0x1bf4, 0x1c21, 0x1c4e, 0x1c7b, 0x1ca9, 0x1cd6, 0x1d04, 0x1d32, 0x1d60,
+ 0x1d8f, 0x1dbd, 0x1dec, 0x1e1b, 0x1e4a, 0x1e79, 0x1ea8, 0x1ed8, 0x1f07,
+ 0x1f37, 0x1f67, 0x1f98, 0x1fc8, 0x1ff8, 0x2029, 0x205a, 0x208b, 0x20bc,
+ 0x20ee, 0x211f, 0x2151, 0x2183, 0x21b5, 0x21e7, 0x2219, 0x224c, 0x227f,
+ 0x22b2, 0x22e5, 0x2318, 0x234b, 0x237f, 0x23b3, 0x23e6, 0x241b, 0x244f,
+ 0x2483, 0x24b8, 0x24ed, 0x2522, 0x2557, 0x258c, 0x25c2, 0x25f7, 0x262d,
+ 0x2663, 0x2699, 0x26cf, 0x2706, 0x273d, 0x2774, 0x27ab, 0x27e2, 0x2819,
+ 0x2851, 0x2888, 0x28c0, 0x28f8, 0x2931, 0x2969, 0x29a2, 0x29db, 0x2a14,
+ 0x2a4d, 0x2a86, 0x2ac0, 0x2af9, 0x2b33, 0x2b6d, 0x2ba7, 0x2be2, 0x2c1c,
+ 0x2c57, 0x2c92, 0x2ccd, 0x2d08, 0x2d44, 0x2d7f, 0x2dbb, 0x2df7, 0x2e33,
+ 0x2e70, 0x2eac, 0x2ee9, 0x2f26, 0x2f63, 0x2fa0, 0x2fdd, 0x301b, 0x3059,
+ 0x3097, 0x30d5, 0x3113, 0x3152, 0x3190, 0x31cf, 0x320e, 0x324e, 0x328d,
+ 0x32cd, 0x330c, 0x334c, 0x338c, 0x33cd, 0x340d, 0x344e, 0x348f, 0x34d0,
+ 0x3511, 0x3552, 0x3594, 0x35d6, 0x3618, 0x365a, 0x369c, 0x36df, 0x3721,
+ 0x3764, 0x37a7, 0x37eb, 0x382e, 0x3872, 0x38b6, 0x38fa, 0x393e, 0x3982,
+ 0x39c7, 0x3a0b, 0x3a50, 0x3a95, 0x3adb, 0x3b20, 0x3b66, 0x3bac, 0x3bf2,
+ 0x3c38, 0x3c7e, 0x3cc5, 0x3d0c, 0x3d53, 0x3d9a, 0x3de1, 0x3e29, 0x3e71,
+ 0x3eb9, 0x3f01, 0x3f49, 0x3f91, 0x3fda, 0x6},
+ {0x2e59, 0x255e, 0x3614, 0x1ec7, 0x2a3b, 0x323d, 0x39f4, 0x197e,
+ 0x2273, 0x2805, 0x2c55, 0x304f, 0x3428, 0x3803, 0x39ff, 0x14cc, 0x1c7b,
+ 0x20b8, 0x2413, 0x26a5, 0x2907, 0x2b3e, 0x2d52, 0x2f4d, 0x3139, 0x331c,
+ 0x34fe, 0x36e4, 0x38d1, 0x39ff, 0x39ff, 0x1090, 0x1781, 0x1b11, 0x1dca,
+ 0x1ff0, 0x21af, 0x2335, 0x24a8, 0x261e, 0x2747, 0x287b, 0x29af, 0x2ab2,
+ 0x2be5, 0x2cc9, 0x2df4, 0x2ec9, 0x2fe8, 0x30ba, 0x31cd, 0x32a4, 0x33aa,
+ 0x348a, 0x3587, 0x3673, 0x3769, 0x3861, 0x3955, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0xc70, 0x1300, 0x164d, 0x1886, 0x1a57, 0x1bef, 0x1d16, 0x1e4e,
+ 0x1f52, 0x2052, 0x212c, 0x2220, 0x22ce, 0x23a7, 0x245a, 0x24fe, 0x25c8,
+ 0x265e, 0x26f2, 0x27a5, 0x283d, 0x28be, 0x2958, 0x2a07, 0x2a74, 0x2af5,
+ 0x2b8e, 0x2c21, 0x2c8c, 0x2d0b, 0x2da0, 0x2e28, 0x2e8f, 0x2f09, 0x2f98,
+ 0x3020, 0x3083, 0x30f7, 0x3180, 0x3210, 0x326e, 0x32dd, 0x3360, 0x33fa,
+ 0x3457, 0x34c2, 0x3540, 0x35d4, 0x3641, 0x36a9, 0x3724, 0x37b5, 0x3830,
+ 0x3897, 0x3910, 0x39a1, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x800, 0xed0, 0x120a, 0x1416, 0x15a2, 0x16dd, 0x181d,
+ 0x18fb, 0x1a07, 0x1ab0, 0x1b7b, 0x1c36, 0x1cc5, 0x1d6d, 0x1e17, 0x1e88,
+ 0x1f0a, 0x1f9e, 0x2024, 0x2084, 0x20f1, 0x216b, 0x21f6, 0x2248, 0x229f,
+ 0x2300, 0x236d, 0x23e5, 0x2435, 0x2480, 0x24d2, 0x252d, 0x2592, 0x2600,
+ 0x263d, 0x2680, 0x26ca, 0x271c, 0x2775, 0x27d7, 0x2821, 0x285b, 0x289c,
+ 0x28e2, 0x292f, 0x2982, 0x29de, 0x2a21, 0x2a57, 0x2a92, 0x2ad3, 0x2b19,
+ 0x2b65, 0x2bb9, 0x2c09, 0x2c3b, 0x2c70, 0x2caa, 0x2ce9, 0x2d2e, 0x2d78,
+ 0x2dc9, 0x2e10, 0x2e40, 0x2e73, 0x2eab, 0x2ee8, 0x2f2a, 0x2f72, 0x2fbf,
+ 0x3009, 0x3037, 0x3068, 0x309e, 0x30d8, 0x3117, 0x315b, 0x31a5, 0x31f6,
+ 0x3226, 0x3255, 0x3288, 0x32c0, 0x32fc, 0x333d, 0x3384, 0x33d1, 0x3412,
+ 0x343f, 0x3470, 0x34a6, 0x34df, 0x351e, 0x3562, 0x35ad, 0x35fd, 0x362a,
+ 0x365a, 0x368d, 0x36c6, 0x3703, 0x3746, 0x378e, 0x37dd, 0x3819, 0x3848,
+ 0x387b, 0x38b3, 0x38f0, 0x3932, 0x397a, 0x39c9, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x200, 0xaa0, 0xde0, 0xfe0, 0x1144,
+ 0x127e, 0x138e, 0x146d, 0x1532, 0x160d, 0x1693, 0x172c, 0x17db, 0x1850,
+ 0x18bf, 0x193b, 0x19c5, 0x1a2e, 0x1a83, 0x1adf, 0x1b45, 0x1bb4, 0x1c16,
+ 0x1c58, 0x1c9f, 0x1ced, 0x1d40, 0x1d9b, 0x1dfc, 0x1e32, 0x1e6a, 0x1ea7,
+ 0x1ee8, 0x1f2d, 0x1f77, 0x1fc6, 0x200d, 0x203a, 0x206a, 0x209e, 0x20d4,
+ 0x210e, 0x214b, 0x218d, 0x21d2, 0x220d, 0x2234, 0x225d, 0x2289, 0x22b6,
+ 0x22e7, 0x231a, 0x2350, 0x238a, 0x23c6, 0x2402, 0x2424, 0x2447, 0x246c,
+ 0x2493, 0x24bd, 0x24e8, 0x2515, 0x2545, 0x2578, 0x25ac, 0x25e4, 0x260f,
+ 0x262d, 0x264d, 0x266f, 0x2692, 0x26b7, 0x26de, 0x2707, 0x2731, 0x275e,
+ 0x278d, 0x27bd, 0x27f1, 0x2813, 0x282f, 0x284c, 0x286b, 0x288b, 0x28ad,
+ 0x28d0, 0x28f4, 0x291b, 0x2943, 0x296d, 0x2998, 0x29c6, 0x29f6, 0x2a14,
+ 0x2a2e, 0x2a49, 0x2a65, 0x2a83, 0x2aa2, 0x2ac2, 0x2ae4, 0x2b07, 0x2b2b,
+ 0x2b52, 0x2b7a, 0x2ba3, 0x2bcf, 0x2bfc, 0x2c15, 0x2c2e, 0x2c48, 0x2c62,
+ 0x2c7e, 0x2c9b, 0x2cb9, 0x2cd9, 0x2cfa, 0x2d1c, 0x2d40, 0x2d65, 0x2d8c,
+ 0x2db4, 0x2dde, 0x2e05, 0x2e1c, 0x2e34, 0x2e4c, 0x2e66, 0x2e81, 0x2e9d,
+ 0x2eba, 0x2ed9, 0x2ef8, 0x2f19, 0x2f3c, 0x2f5f, 0x2f85, 0x2fab, 0x2fd4,
+ 0x2ffe, 0x3014, 0x302b, 0x3043, 0x305c, 0x3075, 0x3090, 0x30ac, 0x30c9,
+ 0x30e7, 0x3107, 0x3128, 0x314a, 0x316d, 0x3192, 0x31b9, 0x31e1, 0x3205,
+ 0x321b, 0x3232, 0x3249, 0x3262, 0x327b, 0x3296, 0x32b2, 0x32cf, 0x32ed,
+ 0x330c, 0x332d, 0x334f, 0x3372, 0x3397, 0x33bd, 0x33e5, 0x3407, 0x341d,
+ 0x3434, 0x344b, 0x3464, 0x347d, 0x3498, 0x34b4, 0x34d1, 0x34ef, 0x350e,
+ 0x352f, 0x3551, 0x3574, 0x359a, 0x35c0, 0x35e8, 0x3609, 0x361f, 0x3636,
+ 0x364d, 0x3666, 0x3680, 0x369b, 0x36b7, 0x36d5, 0x36f3, 0x3713, 0x3734,
+ 0x3757, 0x377b, 0x37a1, 0x37c9, 0x37f2, 0x380e, 0x3825, 0x383c, 0x3855,
+ 0x386e, 0x3889, 0x38a5, 0x38c2, 0x38e0, 0x3900, 0x3921, 0x3944, 0x3968,
+ 0x398e, 0x39b5, 0x39de, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x0,
+ 0x600, 0x980, 0xba0, 0xd20, 0xe58, 0xf58, 0x103c, 0x10e8, 0x11a8,
+ 0x1242, 0x12bc, 0x1346, 0x13dc, 0x1441, 0x149b, 0x14fe, 0x1569, 0x15dd,
+ 0x162c, 0x166f, 0x16b7, 0x1704, 0x1756, 0x17ad, 0x1805, 0x1836, 0x186b,
+ 0x18a2, 0x18dd, 0x191b, 0x195c, 0x19a1, 0x19e9, 0x1a1b, 0x1a43, 0x1a6d,
+ 0x1a99, 0x1ac7, 0x1af8, 0x1b2a, 0x1b5f, 0x1b97, 0x1bd1, 0x1c06, 0x1c26,
+ 0x1c47, 0x1c69, 0x1c8d, 0x1cb2, 0x1cd9, 0x1d01, 0x1d2b, 0x1d56, 0x1d83,
+ 0x1db2, 0x1de3, 0x1e0a, 0x1e25, 0x1e40, 0x1e5c, 0x1e79, 0x1e97, 0x1eb7,
+ 0x1ed7, 0x1ef9, 0x1f1b, 0x1f3f, 0x1f64, 0x1f8b, 0x1fb2, 0x1fdb, 0x2002,
+ 0x2018, 0x202f, 0x2046, 0x205e, 0x2077, 0x2091, 0x20ab, 0x20c6, 0x20e2,
+ 0x20ff, 0x211d, 0x213c, 0x215b, 0x217c, 0x219d, 0x21c0, 0x21e4, 0x2204,
+ 0x2217, 0x222a, 0x223e, 0x2253, 0x2268, 0x227d, 0x2294, 0x22ab, 0x22c2,
+ 0x22db, 0x22f4, 0x230d, 0x2328, 0x2343, 0x235e, 0x237b, 0x2398, 0x23b7,
+ 0x23d5, 0x23f5, 0x240b, 0x241b, 0x242d, 0x243e, 0x2450, 0x2463, 0x2476,
+ 0x248a, 0x249e, 0x24b2, 0x24c7, 0x24dd, 0x24f3, 0x250a, 0x2521, 0x2539,
+ 0x2552, 0x256b, 0x2584, 0x259f, 0x25ba, 0x25d6, 0x25f2, 0x2607, 0x2616,
+ 0x2626, 0x2635, 0x2645, 0x2656, 0x2666, 0x2678, 0x2689, 0x269b, 0x26ae,
+ 0x26c1, 0x26d4, 0x26e8, 0x26fc, 0x2711, 0x2726, 0x273c, 0x2753, 0x2769,
+ 0x2781, 0x2799, 0x27b1, 0x27ca, 0x27e4, 0x27fe, 0x280c, 0x281a, 0x2828,
+ 0x2836, 0x2845, 0x2854, 0x2863, 0x2873, 0x2883, 0x2893, 0x28a4, 0x28b5,
+ 0x28c7, 0x28d9, 0x28eb, 0x28fe, 0x2911, 0x2925, 0x2939, 0x294d, 0x2962,
+ 0x2977, 0x298d, 0x29a4, 0x29bb, 0x29d2, 0x29ea, 0x2a01, 0x2a0d, 0x2a1a,
+ 0x2a27, 0x2a34, 0x2a42, 0x2a50, 0x2a5e, 0x2a6d, 0x2a7b, 0x2a8a, 0x2a9a,
+ 0x2aaa, 0x2aba, 0x2aca, 0x2adb, 0x2aec, 0x2afe, 0x2b10, 0x2b22, 0x2b35,
+ 0x2b48, 0x2b5c, 0x2b6f, 0x2b84, 0x2b99, 0x2bae, 0x2bc4, 0x2bda, 0x2bf0,
+ 0x2c03, 0x2c0f, 0x2c1b, 0x2c28, 0x2c34, 0x2c41, 0x2c4e, 0x2c5b, 0x2c69,
+ 0x2c77, 0x2c85, 0x2c94, 0x2ca3, 0x2cb2, 0x2cc1, 0x2cd1, 0x2ce1, 0x2cf2,
+ 0x2d02, 0x2d13, 0x2d25, 0x2d37, 0x2d49, 0x2d5c, 0x2d6f, 0x2d82, 0x2d96,
+ 0x2daa, 0x2dbf, 0x2dd4, 0x2de9, 0x2dff, 0x2e0b, 0x2e16, 0x2e22, 0x2e2d,
+ 0x2e3a, 0x2e46, 0x2e53, 0x2e60, 0x2e6d, 0x2e7a, 0x2e88, 0x2e96, 0x2ea4,
+ 0x2eb3, 0x2ec2, 0x2ed1, 0x2ee0, 0x2ef0, 0x2f00, 0x2f11, 0x2f22, 0x2f33,
+ 0x2f44, 0x2f56, 0x2f68, 0x2f7b, 0x2f8e, 0x2fa1, 0x2fb5, 0x2fc9, 0x2fde,
+ 0x2ff3, 0x3004, 0x300f, 0x301a, 0x3025, 0x3031, 0x303d, 0x3049, 0x3055,
+ 0x3062, 0x306f, 0x307c, 0x3089, 0x3097, 0x30a5, 0x30b3, 0x30c2, 0x30d1,
+ 0x30e0, 0x30ef, 0x30ff, 0x310f, 0x311f, 0x3130, 0x3141, 0x3153, 0x3164,
+ 0x3176, 0x3189, 0x319c, 0x31af, 0x31c3, 0x31d7, 0x31eb, 0x3200, 0x320a,
+ 0x3215, 0x3220, 0x322c, 0x3237, 0x3243, 0x324f, 0x325b, 0x3268, 0x3275,
+ 0x3282, 0x328f, 0x329d, 0x32ab, 0x32b9, 0x32c7, 0x32d6, 0x32e5, 0x32f4,
+ 0x3304, 0x3314, 0x3324, 0x3335, 0x3346, 0x3357, 0x3369, 0x337b, 0x338d,
+ 0x33a0, 0x33b3, 0x33c7, 0x33db, 0x33ef, 0x3402, 0x340d, 0x3417, 0x3422,
+ 0x342e, 0x3439, 0x3445, 0x3451, 0x345d, 0x346a, 0x3477, 0x3484, 0x3491,
+ 0x349f, 0x34ad, 0x34bb, 0x34c9, 0x34d8, 0x34e7, 0x34f6, 0x3506, 0x3516,
+ 0x3526, 0x3537, 0x3548, 0x355a, 0x356b, 0x357e, 0x3590, 0x35a3, 0x35b6,
+ 0x35ca, 0x35de, 0x35f3, 0x3604, 0x360e, 0x3619, 0x3624, 0x3630, 0x363b,
+ 0x3647, 0x3654, 0x3660, 0x366d, 0x367a, 0x3687, 0x3694, 0x36a2, 0x36b0,
+ 0x36be, 0x36cd, 0x36dc, 0x36eb, 0x36fb, 0x370b, 0x371b, 0x372c, 0x373d,
+ 0x374e, 0x3760, 0x3772, 0x3785, 0x3798, 0x37ab, 0x37bf, 0x37d3, 0x37e7,
+ 0x37fd, 0x3809, 0x3814, 0x381f, 0x382a, 0x3836, 0x3842, 0x384e, 0x385b,
+ 0x3868, 0x3875, 0x3882, 0x3890, 0x389e, 0x38ac, 0x38ba, 0x38c9, 0x38d8,
+ 0x38e8, 0x38f8, 0x3908, 0x3919, 0x3929, 0x393b, 0x394c, 0x395f, 0x3971,
+ 0x3984, 0x3997, 0x39ab, 0x39bf, 0x39d4, 0x39e9, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0xc},
+ {0x1542, 0x62b, 0x2ed7, 0x230, 0xc78, 0x20ad, 0x3fda, 0xff, 0x3e7,
+ 0x904, 0x108b, 0x1aa2, 0x2769, 0x36fc, 0x3fff, 0x7d, 0x188, 0x2fa,
+ 0x4f7, 0x785, 0xaaa, 0xe6d, 0x12d2, 0x17dc, 0x1d92, 0x23f5, 0x2b09,
+ 0x32d2, 0x3b53, 0x3fff, 0x3fff, 0x3c, 0xbe, 0x140, 0x1d8, 0x291, 0x36c,
+ 0x46a, 0x58c, 0x6d3, 0x840, 0x9d2, 0xb8c, 0xd6d, 0xf77, 0x11a9, 0x1405,
+ 0x168a, 0x193a, 0x1c14, 0x1f1a, 0x224b, 0x25a9, 0x2933, 0x2ceb, 0x30cf,
+ 0x34e1, 0x3922, 0x3d91, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x1c, 0x5d,
+ 0x9e, 0xdf, 0x120, 0x163, 0x1af, 0x203, 0x260, 0x2c5, 0x332, 0x3a8,
+ 0x427, 0x4af, 0x540, 0x5db, 0x67e, 0x72b, 0x7e1, 0x8a1, 0x96a, 0xa3d,
+ 0xb1a, 0xc01, 0xcf1, 0xdec, 0xef1, 0xfff, 0x1119, 0x123c, 0x136a,
+ 0x14a2, 0x15e5, 0x1732, 0x188a, 0x19ec, 0x1b59, 0x1cd2, 0x1e54, 0x1fe2,
+ 0x217b, 0x231f, 0x24ce, 0x2687, 0x284d, 0x2a1d, 0x2bf8, 0x2ddf, 0x2fd2,
+ 0x31cf, 0x33d8, 0x35ed, 0x380d, 0x3a39, 0x3c71, 0x3eb4, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0xc, 0x2c, 0x4d, 0x6d,
+ 0x8e, 0xae, 0xcf, 0xef, 0x110, 0x12f, 0x151, 0x175, 0x19b, 0x1c3,
+ 0x1ed, 0x21a, 0x248, 0x278, 0x2ab, 0x2df, 0x316, 0x34f, 0x38a, 0x3c7,
+ 0x407, 0x449, 0x48d, 0x4d3, 0x51b, 0x566, 0x5b3, 0x603, 0x654, 0x6a8,
+ 0x6ff, 0x757, 0x7b3, 0x810, 0x870, 0x8d2, 0x937, 0x99e, 0xa07, 0xa73,
+ 0xae2, 0xb53, 0xbc6, 0xc3c, 0xcb4, 0xd2f, 0xdac, 0xe2c, 0xeae, 0xf33,
+ 0xfbb, 0x1045, 0x10d1, 0x1160, 0x11f2, 0x1286, 0x131d, 0x13b7, 0x1453,
+ 0x14f2, 0x1593, 0x1637, 0x16de, 0x1787, 0x1833, 0x18e1, 0x1993, 0x1a47,
+ 0x1afd, 0x1bb6, 0x1c72, 0x1d31, 0x1df3, 0x1eb7, 0x1f7e, 0x2047, 0x2114,
+ 0x21e3, 0x22b5, 0x2389, 0x2461, 0x253b, 0x2618, 0x26f8, 0x27da, 0x28c0,
+ 0x29a8, 0x2a93, 0x2b81, 0x2c71, 0x2d65, 0x2e5b, 0x2f54, 0x3050, 0x314f,
+ 0x3251, 0x3355, 0x345d, 0x3567, 0x3674, 0x3784, 0x3897, 0x39ad, 0x3ac6,
+ 0x3be2, 0x3d00, 0x3e22, 0x3f46, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x4, 0x14, 0x24, 0x34, 0x45, 0x55, 0x65, 0x75, 0x86,
+ 0x96, 0xa6, 0xb6, 0xc7, 0xd7, 0xe7, 0xf7, 0x108, 0x118, 0x127, 0x138,
+ 0x149, 0x15a, 0x16c, 0x17f, 0x192, 0x1a5, 0x1b9, 0x1ce, 0x1e3, 0x1f8,
+ 0x20e, 0x225, 0x23c, 0x254, 0x26c, 0x285, 0x29e, 0x2b8, 0x2d2, 0x2ed,
+ 0x308, 0x324, 0x341, 0x35e, 0x37b, 0x399, 0x3b8, 0x3d7, 0x3f7, 0x417,
+ 0x438, 0x459, 0x47b, 0x49e, 0x4c1, 0x4e5, 0x509, 0x52e, 0x553, 0x579,
+ 0x5a0, 0x5c7, 0x5ef, 0x617, 0x640, 0x669, 0x693, 0x6be, 0x6e9, 0x715,
+ 0x741, 0x76e, 0x79c, 0x7ca, 0x7f8, 0x828, 0x858, 0x888, 0x8b9, 0x8eb,
+ 0x91d, 0x950, 0x984, 0x9b8, 0x9ed, 0xa22, 0xa58, 0xa8f, 0xac6, 0xafe,
+ 0xb36, 0xb6f, 0xba9, 0xbe3, 0xc1e, 0xc5a, 0xc96, 0xcd3, 0xd10, 0xd4e,
+ 0xd8d, 0xdcc, 0xe0c, 0xe4c, 0xe8e, 0xecf, 0xf12, 0xf55, 0xf99, 0xfdd,
+ 0x1022, 0x1068, 0x10ae, 0x10f5, 0x113c, 0x1185, 0x11ce, 0x1217, 0x1261,
+ 0x12ac, 0x12f7, 0x1343, 0x1390, 0x13de, 0x142c, 0x147a, 0x14ca, 0x151a,
+ 0x156a, 0x15bc, 0x160e, 0x1660, 0x16b4, 0x1708, 0x175c, 0x17b2, 0x1808,
+ 0x185e, 0x18b5, 0x190d, 0x1966, 0x19bf, 0x1a19, 0x1a74, 0x1acf, 0x1b2b,
+ 0x1b88, 0x1be5, 0x1c43, 0x1ca2, 0x1d01, 0x1d61, 0x1dc2, 0x1e23, 0x1e86,
+ 0x1ee8, 0x1f4c, 0x1fb0, 0x2015, 0x207a, 0x20e0, 0x2147, 0x21af, 0x2217,
+ 0x2280, 0x22ea, 0x2354, 0x23bf, 0x242b, 0x2497, 0x2504, 0x2572, 0x25e0,
+ 0x2650, 0x26c0, 0x2730, 0x27a1, 0x2813, 0x2886, 0x28f9, 0x296d, 0x29e2,
+ 0x2a58, 0x2ace, 0x2b45, 0x2bbc, 0x2c35, 0x2cae, 0x2d27, 0x2da2, 0x2e1d,
+ 0x2e99, 0x2f15, 0x2f93, 0x3011, 0x308f, 0x310f, 0x318f, 0x3210, 0x3291,
+ 0x3314, 0x3397, 0x341a, 0x349f, 0x3524, 0x35aa, 0x3631, 0x36b8, 0x3740,
+ 0x37c9, 0x3852, 0x38dc, 0x3967, 0x39f3, 0x3a7f, 0x3b0d, 0x3b9a, 0x3c29,
+ 0x3cb8, 0x3d48, 0x3dd9, 0x3e6b, 0x3efd, 0x3f90, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x0, 0x8, 0x10, 0x18, 0x20, 0x28, 0x30, 0x38, 0x41,
+ 0x49, 0x51, 0x59, 0x61, 0x69, 0x71, 0x79, 0x82, 0x8a, 0x92, 0x9a, 0xa2,
+ 0xaa, 0xb2, 0xba, 0xc3, 0xcb, 0xd3, 0xdb, 0xe3, 0xeb, 0xf3, 0xfb,
+ 0x104, 0x10c, 0x114, 0x11c, 0x124, 0x12b, 0x134, 0x13c, 0x144, 0x14d,
+ 0x156, 0x15f, 0x168, 0x171, 0x17a, 0x183, 0x18d, 0x197, 0x1a0, 0x1aa,
+ 0x1b4, 0x1be, 0x1c9, 0x1d3, 0x1dd, 0x1e8, 0x1f3, 0x1fe, 0x209, 0x214,
+ 0x21f, 0x22b, 0x236, 0x242, 0x24e, 0x25a, 0x266, 0x272, 0x27e, 0x28b,
+ 0x297, 0x2a4, 0x2b1, 0x2be, 0x2cb, 0x2d9, 0x2e6, 0x2f4, 0x301, 0x30f,
+ 0x31d, 0x32b, 0x339, 0x348, 0x356, 0x365, 0x374, 0x383, 0x392, 0x3a1,
+ 0x3b0, 0x3c0, 0x3cf, 0x3df, 0x3ef, 0x3ff, 0x40f, 0x41f, 0x430, 0x440,
+ 0x451, 0x462, 0x473, 0x484, 0x495, 0x4a7, 0x4b8, 0x4ca, 0x4dc, 0x4ee,
+ 0x500, 0x512, 0x525, 0x537, 0x54a, 0x55d, 0x570, 0x583, 0x596, 0x5a9,
+ 0x5bd, 0x5d1, 0x5e5, 0x5f9, 0x60d, 0x621, 0x635, 0x64a, 0x65f, 0x674,
+ 0x689, 0x69e, 0x6b3, 0x6c8, 0x6de, 0x6f4, 0x70a, 0x720, 0x736, 0x74c,
+ 0x763, 0x779, 0x790, 0x7a7, 0x7be, 0x7d5, 0x7ed, 0x804, 0x81c, 0x834,
+ 0x84c, 0x864, 0x87c, 0x894, 0x8ad, 0x8c6, 0x8df, 0x8f8, 0x911, 0x92a,
+ 0x944, 0x95d, 0x977, 0x991, 0x9ab, 0x9c5, 0x9e0, 0x9fa, 0xa15, 0xa30,
+ 0xa4b, 0xa66, 0xa81, 0xa9d, 0xab8, 0xad4, 0xaf0, 0xb0c, 0xb28, 0xb44,
+ 0xb61, 0xb7e, 0xb9a, 0xbb7, 0xbd5, 0xbf2, 0xc0f, 0xc2d, 0xc4b, 0xc69,
+ 0xc87, 0xca5, 0xcc3, 0xce2, 0xd01, 0xd1f, 0xd3f, 0xd5e, 0xd7d, 0xd9c,
+ 0xdbc, 0xddc, 0xdfc, 0xe1c, 0xe3c, 0xe5d, 0xe7d, 0xe9e, 0xebf, 0xee0,
+ 0xf01, 0xf23, 0xf44, 0xf66, 0xf88, 0xfaa, 0xfcc, 0xfee, 0x1011, 0x1033,
+ 0x1056, 0x1079, 0x109c, 0x10c0, 0x10e3, 0x1107, 0x112b, 0x114e, 0x1173,
+ 0x1197, 0x11bb, 0x11e0, 0x1205, 0x1229, 0x124f, 0x1274, 0x1299, 0x12bf,
+ 0x12e4, 0x130a, 0x1330, 0x1357, 0x137d, 0x13a3, 0x13ca, 0x13f1, 0x1418,
+ 0x143f, 0x1467, 0x148e, 0x14b6, 0x14de, 0x1506, 0x152e, 0x1556, 0x157f,
+ 0x15a7, 0x15d0, 0x15f9, 0x1622, 0x164c, 0x1675, 0x169f, 0x16c9, 0x16f3,
+ 0x171d, 0x1747, 0x1772, 0x179c, 0x17c7, 0x17f2, 0x181d, 0x1848, 0x1874,
+ 0x18a0, 0x18cb, 0x18f7, 0x1923, 0x1950, 0x197c, 0x19a9, 0x19d6, 0x1a03,
+ 0x1a30, 0x1a5d, 0x1a8b, 0x1ab8, 0x1ae6, 0x1b14, 0x1b42, 0x1b71, 0x1b9f,
+ 0x1bce, 0x1bfd, 0x1c2c, 0x1c5b, 0x1c8a, 0x1cba, 0x1ce9, 0x1d19, 0x1d49,
+ 0x1d79, 0x1daa, 0x1dda, 0x1e0b, 0x1e3c, 0x1e6d, 0x1e9e, 0x1ed0, 0x1f01,
+ 0x1f33, 0x1f65, 0x1f97, 0x1fc9, 0x1ffb, 0x202e, 0x2061, 0x2094, 0x20c7,
+ 0x20fa, 0x212d, 0x2161, 0x2195, 0x21c9, 0x21fd, 0x2231, 0x2266, 0x229a,
+ 0x22cf, 0x2304, 0x2339, 0x236f, 0x23a4, 0x23da, 0x2410, 0x2446, 0x247c,
+ 0x24b2, 0x24e9, 0x2520, 0x2556, 0x258e, 0x25c5, 0x25fc, 0x2634, 0x266c,
+ 0x26a3, 0x26dc, 0x2714, 0x274c, 0x2785, 0x27be, 0x27f7, 0x2830, 0x2869,
+ 0x28a3, 0x28dc, 0x2916, 0x2950, 0x298b, 0x29c5, 0x2a00, 0x2a3a, 0x2a75,
+ 0x2ab0, 0x2aec, 0x2b27, 0x2b63, 0x2b9e, 0x2bda, 0x2c17, 0x2c53, 0x2c8f,
+ 0x2ccc, 0x2d09, 0x2d46, 0x2d83, 0x2dc1, 0x2dfe, 0x2e3c, 0x2e7a, 0x2eb8,
+ 0x2ef6, 0x2f35, 0x2f73, 0x2fb2, 0x2ff1, 0x3030, 0x3070, 0x30af, 0x30ef,
+ 0x312f, 0x316f, 0x31af, 0x31f0, 0x3230, 0x3271, 0x32b2, 0x32f3, 0x3334,
+ 0x3376, 0x33b8, 0x33f9, 0x343b, 0x347e, 0x34c0, 0x3503, 0x3545, 0x3588,
+ 0x35cb, 0x360f, 0x3652, 0x3696, 0x36da, 0x371e, 0x3762, 0x37a6, 0x37eb,
+ 0x3830, 0x3875, 0x38ba, 0x38ff, 0x3945, 0x398a, 0x39d0, 0x3a16, 0x3a5c,
+ 0x3aa3, 0x3ae9, 0x3b30, 0x3b77, 0x3bbe, 0x3c05, 0x3c4d, 0x3c94, 0x3cdc,
+ 0x3d24, 0x3d6c, 0x3db5, 0x3dfd, 0x3e46, 0x3e8f, 0x3ed8, 0x3f21, 0x3f6b,
+ 0x3fb5, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff, 0x3fff,
+ 0x3fff, 0x3fff, 0x6},
+ {0x3394, 0x2f73, 0x37ad, 0x2b6c, 0x31e4, 0x3584, 0x39f8, 0x275f,
+ 0x2ddf, 0x30b3, 0x32a6, 0x347b, 0x3687, 0x38b1, 0x39ff, 0x2343, 0x29d5,
+ 0x2caf, 0x2ea3, 0x302f, 0x3145, 0x3249, 0x3312, 0x341a, 0x34f3, 0x361b,
+ 0x370b, 0x3839, 0x3945, 0x39ff, 0x39ff, 0x1f0e, 0x25c0, 0x28a6, 0x2a9d,
+ 0x2c2b, 0x2d40, 0x2e46, 0x2f08, 0x2fe6, 0x306f, 0x30fa, 0x3193, 0x321c,
+ 0x3277, 0x32d9, 0x3350, 0x33e0, 0x3448, 0x34b4, 0x3538, 0x35d8, 0x364e,
+ 0x36c6, 0x3758, 0x3805, 0x3872, 0x38f7, 0x399a, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x1aaa, 0x2198, 0x2495, 0x2691, 0x2824, 0x2937, 0x2a40, 0x2b01,
+ 0x2bde, 0x2c6b, 0x2cf6, 0x2d8e, 0x2e1a, 0x2e74, 0x2ed5, 0x2f3d, 0x2fac,
+ 0x3011, 0x304f, 0x3091, 0x30d6, 0x311f, 0x316b, 0x31bb, 0x3207, 0x3232,
+ 0x3260, 0x328e, 0x32bf, 0x32f5, 0x3330, 0x3371, 0x33b9, 0x3404, 0x3430,
+ 0x3461, 0x3497, 0x34d3, 0x3514, 0x355d, 0x35ad, 0x3603, 0x3634, 0x366a,
+ 0x36a6, 0x36e8, 0x3731, 0x3782, 0x37db, 0x381e, 0x3855, 0x3891, 0x38d4,
+ 0x391d, 0x396e, 0x39c8, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x15ea, 0x1d4a, 0x2074, 0x227a, 0x2414, 0x2524, 0x2635,
+ 0x26f5, 0x27d0, 0x2863, 0x28ed, 0x2984, 0x2a14, 0x2a6e, 0x2acf, 0x2b36,
+ 0x2ba5, 0x2c0d, 0x2c4b, 0x2c8d, 0x2cd2, 0x2d1a, 0x2d67, 0x2db6, 0x2e04,
+ 0x2e30, 0x2e5d, 0x2e8b, 0x2ebc, 0x2eee, 0x2f22, 0x2f58, 0x2f8f, 0x2fc8,
+ 0x3001, 0x3020, 0x303f, 0x305f, 0x3080, 0x30a2, 0x30c5, 0x30e8, 0x310d,
+ 0x3132, 0x3158, 0x317f, 0x31a7, 0x31d0, 0x31f9, 0x3212, 0x3227, 0x323e,
+ 0x3254, 0x326b, 0x3282, 0x329a, 0x32b3, 0x32cc, 0x32e7, 0x3303, 0x3321,
+ 0x3340, 0x3360, 0x3383, 0x33a7, 0x33cd, 0x33f5, 0x340f, 0x3425, 0x343c,
+ 0x3455, 0x346e, 0x3489, 0x34a5, 0x34c3, 0x34e2, 0x3503, 0x3526, 0x354a,
+ 0x3570, 0x3598, 0x35c2, 0x35ef, 0x360e, 0x3627, 0x3641, 0x365c, 0x3678,
+ 0x3696, 0x36b6, 0x36d7, 0x36f9, 0x371e, 0x3744, 0x376d, 0x3797, 0x37c4,
+ 0x37f2, 0x3812, 0x382c, 0x3847, 0x3863, 0x3881, 0x38a1, 0x38c2, 0x38e5,
+ 0x390a, 0x3931, 0x3959, 0x3984, 0x39b1, 0x39e0, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0xf78, 0x18b7, 0x1c33, 0x1e4c, 0x1fed,
+ 0x20ff, 0x221f, 0x22db, 0x23b3, 0x2453, 0x24db, 0x2570, 0x260a, 0x2662,
+ 0x26c2, 0x2729, 0x2796, 0x2805, 0x2843, 0x2884, 0x28c9, 0x2911, 0x295d,
+ 0x29ac, 0x29ff, 0x2a2a, 0x2a57, 0x2a85, 0x2ab6, 0x2ae8, 0x2b1c, 0x2b51,
+ 0x2b88, 0x2bc1, 0x2bfc, 0x2c1c, 0x2c3b, 0x2c5b, 0x2c7c, 0x2c9e, 0x2cc0,
+ 0x2ce4, 0x2d08, 0x2d2d, 0x2d53, 0x2d7a, 0x2da2, 0x2dcb, 0x2df4, 0x2e0f,
+ 0x2e25, 0x2e3b, 0x2e51, 0x2e68, 0x2e80, 0x2e97, 0x2eb0, 0x2ec8, 0x2ee1,
+ 0x2efb, 0x2f15, 0x2f2f, 0x2f4a, 0x2f66, 0x2f81, 0x2f9d, 0x2fba, 0x2fd7,
+ 0x2ff5, 0x3009, 0x3018, 0x3028, 0x3037, 0x3047, 0x3057, 0x3067, 0x3078,
+ 0x3088, 0x3099, 0x30aa, 0x30bc, 0x30cd, 0x30df, 0x30f1, 0x3103, 0x3116,
+ 0x3128, 0x313b, 0x314e, 0x3162, 0x3175, 0x3189, 0x319d, 0x31b1, 0x31c5,
+ 0x31da, 0x31ef, 0x3202, 0x320c, 0x3217, 0x3222, 0x322d, 0x3238, 0x3243,
+ 0x324e, 0x325a, 0x3265, 0x3271, 0x327d, 0x3288, 0x3294, 0x32a0, 0x32ad,
+ 0x32b9, 0x32c6, 0x32d3, 0x32e0, 0x32ee, 0x32fc, 0x330a, 0x3319, 0x3328,
+ 0x3338, 0x3348, 0x3358, 0x3369, 0x337a, 0x338c, 0x339e, 0x33b0, 0x33c3,
+ 0x33d6, 0x33ea, 0x33ff, 0x340a, 0x3414, 0x341f, 0x342b, 0x3436, 0x3442,
+ 0x344e, 0x345b, 0x3468, 0x3475, 0x3482, 0x3490, 0x349e, 0x34ad, 0x34bc,
+ 0x34cb, 0x34da, 0x34ea, 0x34fb, 0x350c, 0x351d, 0x352f, 0x3541, 0x3553,
+ 0x3566, 0x357a, 0x358e, 0x35a3, 0x35b8, 0x35cd, 0x35e4, 0x35fa, 0x3609,
+ 0x3614, 0x3621, 0x362d, 0x363a, 0x3647, 0x3655, 0x3663, 0x3671, 0x3680,
+ 0x368e, 0x369e, 0x36ae, 0x36be, 0x36ce, 0x36df, 0x36f0, 0x3702, 0x3715,
+ 0x3727, 0x373a, 0x374e, 0x3762, 0x3777, 0x378c, 0x37a2, 0x37b8, 0x37cf,
+ 0x37e7, 0x37ff, 0x380b, 0x3818, 0x3825, 0x3832, 0x3840, 0x384e, 0x385c,
+ 0x386b, 0x387a, 0x3889, 0x3899, 0x38a9, 0x38ba, 0x38cb, 0x38dc, 0x38ee,
+ 0x3901, 0x3913, 0x3927, 0x393b, 0x394f, 0x3964, 0x3979, 0x398f, 0x39a5,
+ 0x39bc, 0x39d4, 0x39ec, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x0,
+ 0x137a, 0x177a, 0x19ea, 0x1b7a, 0x1cb7, 0x1dea, 0x1eaa, 0x1f7a, 0x2033,
+ 0x20b7, 0x214a, 0x21ea, 0x224c, 0x22aa, 0x230e, 0x237a, 0x23ed, 0x2433,
+ 0x2474, 0x24b7, 0x24ff, 0x254a, 0x2598, 0x25ea, 0x261f, 0x264c, 0x267a,
+ 0x26aa, 0x26db, 0x270e, 0x2743, 0x277a, 0x27b3, 0x27ed, 0x2814, 0x2833,
+ 0x2853, 0x2874, 0x2895, 0x28b7, 0x28db, 0x28ff, 0x2924, 0x294a, 0x2970,
+ 0x2998, 0x29c0, 0x29ea, 0x2a0a, 0x2a1f, 0x2a35, 0x2a4c, 0x2a62, 0x2a7a,
+ 0x2a91, 0x2aaa, 0x2ac2, 0x2adb, 0x2af5, 0x2b0e, 0x2b29, 0x2b43, 0x2b5f,
+ 0x2b7a, 0x2b96, 0x2bb3, 0x2bd0, 0x2bed, 0x2c05, 0x2c14, 0x2c24, 0x2c33,
+ 0x2c43, 0x2c53, 0x2c63, 0x2c74, 0x2c84, 0x2c95, 0x2ca6, 0x2cb7, 0x2cc9,
+ 0x2cdb, 0x2ced, 0x2cff, 0x2d11, 0x2d24, 0x2d37, 0x2d4a, 0x2d5d, 0x2d70,
+ 0x2d84, 0x2d98, 0x2dac, 0x2dc0, 0x2dd5, 0x2dea, 0x2dff, 0x2e0a, 0x2e14,
+ 0x2e1f, 0x2e2a, 0x2e35, 0x2e40, 0x2e4c, 0x2e57, 0x2e62, 0x2e6e, 0x2e7a,
+ 0x2e85, 0x2e91, 0x2e9d, 0x2eaa, 0x2eb6, 0x2ec2, 0x2ecf, 0x2edb, 0x2ee8,
+ 0x2ef5, 0x2f01, 0x2f0e, 0x2f1c, 0x2f29, 0x2f36, 0x2f43, 0x2f51, 0x2f5f,
+ 0x2f6c, 0x2f7a, 0x2f88, 0x2f96, 0x2fa5, 0x2fb3, 0x2fc1, 0x2fd0, 0x2fde,
+ 0x2fed, 0x2ffc, 0x3005, 0x300d, 0x3014, 0x301c, 0x3024, 0x302b, 0x3033,
+ 0x303b, 0x3043, 0x304b, 0x3053, 0x305b, 0x3063, 0x306b, 0x3074, 0x307c,
+ 0x3084, 0x308d, 0x3095, 0x309e, 0x30a6, 0x30af, 0x30b7, 0x30c0, 0x30c9,
+ 0x30d2, 0x30db, 0x30e4, 0x30ed, 0x30f6, 0x30ff, 0x3108, 0x3111, 0x311a,
+ 0x3124, 0x312d, 0x3137, 0x3140, 0x314a, 0x3153, 0x315d, 0x3167, 0x3170,
+ 0x317a, 0x3184, 0x318e, 0x3198, 0x31a2, 0x31ac, 0x31b6, 0x31c0, 0x31cb,
+ 0x31d5, 0x31df, 0x31ea, 0x31f4, 0x31ff, 0x3204, 0x320a, 0x320f, 0x3214,
+ 0x321a, 0x321f, 0x3225, 0x322a, 0x3230, 0x3235, 0x323b, 0x3240, 0x3246,
+ 0x324c, 0x3251, 0x3257, 0x325d, 0x3262, 0x3268, 0x326e, 0x3274, 0x327a,
+ 0x3280, 0x3285, 0x328b, 0x3291, 0x3297, 0x329d, 0x32a3, 0x32aa, 0x32b0,
+ 0x32b6, 0x32bc, 0x32c2, 0x32c9, 0x32cf, 0x32d6, 0x32dd, 0x32e4, 0x32ea,
+ 0x32f1, 0x32f8, 0x32ff, 0x3307, 0x330e, 0x3315, 0x331d, 0x3324, 0x332c,
+ 0x3334, 0x333c, 0x3344, 0x334c, 0x3354, 0x335c, 0x3365, 0x336d, 0x3376,
+ 0x337e, 0x3387, 0x3390, 0x3399, 0x33a2, 0x33ab, 0x33b5, 0x33be, 0x33c8,
+ 0x33d2, 0x33db, 0x33e5, 0x33ef, 0x33fa, 0x3402, 0x3407, 0x340c, 0x3412,
+ 0x3417, 0x341d, 0x3422, 0x3428, 0x342e, 0x3433, 0x3439, 0x343f, 0x3445,
+ 0x344b, 0x3451, 0x3458, 0x345e, 0x3464, 0x346b, 0x3471, 0x3478, 0x347f,
+ 0x3486, 0x348d, 0x3493, 0x349b, 0x34a2, 0x34a9, 0x34b0, 0x34b8, 0x34bf,
+ 0x34c7, 0x34cf, 0x34d6, 0x34de, 0x34e6, 0x34ee, 0x34f7, 0x34ff, 0x3507,
+ 0x3510, 0x3519, 0x3521, 0x352a, 0x3533, 0x353c, 0x3545, 0x354f, 0x3558,
+ 0x3562, 0x356b, 0x3575, 0x357f, 0x3589, 0x3593, 0x359d, 0x35a8, 0x35b2,
+ 0x35bd, 0x35c8, 0x35d3, 0x35de, 0x35e9, 0x35f4, 0x3600, 0x3606, 0x360b,
+ 0x3611, 0x3617, 0x361e, 0x3624, 0x362a, 0x3630, 0x3637, 0x363d, 0x3644,
+ 0x364b, 0x3651, 0x3658, 0x365f, 0x3666, 0x366d, 0x3675, 0x367c, 0x3683,
+ 0x368b, 0x3692, 0x369a, 0x36a2, 0x36aa, 0x36b2, 0x36ba, 0x36c2, 0x36ca,
+ 0x36d2, 0x36db, 0x36e3, 0x36ec, 0x36f5, 0x36fe, 0x3707, 0x3710, 0x3719,
+ 0x3723, 0x372c, 0x3736, 0x373f, 0x3749, 0x3753, 0x375d, 0x3767, 0x3772,
+ 0x377c, 0x3787, 0x3792, 0x379c, 0x37a7, 0x37b3, 0x37be, 0x37c9, 0x37d5,
+ 0x37e1, 0x37ec, 0x37f8, 0x3802, 0x3808, 0x380e, 0x3815, 0x381b, 0x3822,
+ 0x3828, 0x382f, 0x3836, 0x383c, 0x3843, 0x384a, 0x3851, 0x3858, 0x3860,
+ 0x3867, 0x386e, 0x3876, 0x387e, 0x3885, 0x388d, 0x3895, 0x389d, 0x38a5,
+ 0x38ad, 0x38b6, 0x38be, 0x38c7, 0x38cf, 0x38d8, 0x38e1, 0x38ea, 0x38f3,
+ 0x38fc, 0x3905, 0x390f, 0x3918, 0x3922, 0x392c, 0x3936, 0x3940, 0x394a,
+ 0x3954, 0x395e, 0x3969, 0x3974, 0x397e, 0x3989, 0x3994, 0x39a0, 0x39ab,
+ 0x39b6, 0x39c2, 0x39ce, 0x39da, 0x39e6, 0x39f2, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff,
+ 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0x39ff, 0xc},
+};
+
+static const u32 dcss_cscos[8][29] = {
+ {0x1, 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x36c, 0x36c, 0x36c, 0x0, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+ 0x3ac, 0x3ac, 0x3ac},
+ {0x8000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0},
+ {0x3, 0x20e0, 0x54d9, 0x76c, 0xffffee22, 0xffffd1e1, 0x4000, 0x4000,
+ 0xffffc527, 0xfffffadc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x380, 0x380,
+ 0x380, 0xf, 0x40, 0x200, 0x200, 0x40, 0x40, 0x40, 0x3ac, 0x3c0, 0x3c0},
+ {0x3, 0x21a1, 0x56c9, 0x798, 0xffffee22, 0xffffd1e1, 0x4000, 0x4000,
+ 0xffffc527, 0xfffffadc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3ff, 0x3ff,
+ 0x3ff, 0xf, 0x0, 0x200, 0x200, 0x0, 0x0, 0x0, 0x3ff, 0x3ff, 0x3ff},
+ {0x3, 0x1a9b, 0x5981, 0x909, 0xfffff157, 0xffffceac, 0x4000, 0x4000,
+ 0xffffc5e0, 0xfffffa23, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x380, 0x380,
+ 0x380, 0xf, 0x40, 0x200, 0x200, 0x40, 0x40, 0x40, 0x3ac, 0x3c0, 0x3c0},
+ {0x3, 0x256b, 0x4976, 0xe45, 0xffffea68, 0xffffd59b, 0x4000, 0x4000,
+ 0xffffca6a, 0xfffff599, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x380, 0x380,
+ 0x380, 0xf, 0x40, 0x200, 0x200, 0x40, 0x40, 0x40, 0x3ac, 0x3c0, 0x3c0},
+ {0x3, 0x1b37, 0x5b8c, 0x93e, 0xfffff157, 0xffffceac, 0x4000, 0x4000,
+ 0xffffc5e0, 0xfffffa23, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3ff, 0x3ff,
+ 0x3ff, 0xf, 0x0, 0x200, 0x200, 0x0, 0x0, 0x0, 0x3ff, 0x3ff, 0x3ff},
+ {0x3, 0x2646, 0x4b23, 0xe98, 0xffffea68, 0xffffd59b, 0x4000, 0x4000,
+ 0xffffca6a, 0xfffff599, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3ff, 0x3ff,
+ 0x3ff, 0xf, 0x0, 0x200, 0x200, 0x0, 0x0, 0x0, 0x3ff, 0x3ff, 0x3ff},
+};
+
+struct dcss_pipe_cfg {
+ u32 id;
+ u8 idx[5];
+};
+
+static const struct dcss_pipe_cfg dcss_cfg_table[226] = {
+ /* IPIPE:RGB,10b,Limited,REC2020,REC2084;OPIPE:RGB,10b,Limited,REC2020,REC2084 */
+ {0x0a160a16, {0, 0, 0, 0, 0}},
+ /* IPIPE:YCbCr,10b,Limited,REC2020,REC2084;OPIPE:RGB,10b,Limited,REC2020,REC2084 */
+ {0x0a160a1a, {1, 0, 0, 0, 0}},
+ /* IPIPE:RGB,10b,Limited,REC2020,REC2100HLG;OPIPE:RGB,10b,Limited,REC2020,REC2084 */
+ {0x0a160a86, {0, 4, 0, 0, 0}},
+ /* IPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG;OPIPE:RGB,10b,Limited,REC2020,REC2084 */
+ {0x0a160a8a, {1, 4, 0, 0, 0}},
+ /* IPIPE:RGB,10b,Full,REC2020,REC2084;OPIPE:RGB,10b,Limited,REC2020,REC2084 */
+ {0x0a160c16, {0, 1, 0, 0, 0}},
+ /* IPIPE:YCbCr,10b,Full,REC2020,REC2084;OPIPE:RGB,10b,Limited,REC2020,REC2084 */
+ {0x0a160c1a, {2, 0, 0, 0, 0}},
+ /* IPIPE:RGB,10b,Full,REC2020,REC2100HLG;OPIPE:RGB,10b,Limited,REC2020,REC2084 */
+ {0x0a160c86, {0, 5, 0, 0, 0}},
+ /* IPIPE:YCbCr,10b,Full,REC2020,REC2100HLG;OPIPE:RGB,10b,Limited,REC2020,REC2084 */
+ {0x0a160c8a, {2, 4, 0, 0, 0}},
+ /* IPIPE:RGB,10b,Limited,REC709,REC709;OPIPE:RGB,10b,Limited,REC2020,REC2084 */
+ {0x0a161226, {0, 2, 1, 0, 0}},
+ /* IPIPE:YCbCr,10b,Limited,REC709,REC709;OPIPE:RGB,10b,Limited,REC2020,REC2084 */
+ {0x0a16122a, {3, 2, 1, 0, 0}},
+ /* IPIPE:RGB,10b,Limited,REC709,SRGB;OPIPE:RGB,10b,Limited,REC2020,REC2084 */
+ {0x0a161306, {0, 6, 1, 0, 0}},
+ /* IPIPE:RGB,10b,Full,REC709,REC709;OPIPE:RGB,10b,Limited,REC2020,REC2084 */
+ {0x0a161426, {0, 3, 1, 0, 0}},
+ /* IPIPE:YCbCr,10b,Full,REC709,REC709;OPIPE:RGB,10b,Limited,REC2020,REC2084 */
+ {0x0a16142a, {5, 2, 1, 0, 0}},
+ /* IPIPE:RGB,10b,Full,REC709,SRGB;OPIPE:RGB,10b,Limited,REC2020,REC2084 */
+ {0x0a161506, {0, 7, 1, 0, 0}},
+ /* IPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709;OPIPE:RGB,10b,Limited,REC2020,REC2084 */
+ {0x0a16222a, {4, 2, 2, 0, 0}},
+ /* IPIPE:YCbCr,10b,Full,REC601_NTSC,REC709;OPIPE:RGB,10b,Limited,REC2020,REC2084 */
+ {0x0a16242a, {6, 2, 2, 0, 0}},
+ /* IPIPE:RGB,10b,Limited,REC2020,REC2084;OPIPE:YCbCr,10b,Limited,REC2020,REC2084 */
+ {0x0a1a0a16, {0, 0, 0, 8, 2}},
+ /* IPIPE:YCbCr,10b,Limited,REC2020,REC2084;OPIPE:YCbCr,10b,Limited,REC2020,REC2084 */
+ {0x0a1a0a1a, {14, 8, 0, 8, 2}},
+ /* IPIPE:RGB,10b,Limited,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Limited,REC2020,REC2084 */
+ {0x0a1a0a86, {0, 4, 0, 8, 2}},
+ /* IPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Limited,REC2020,REC2084 */
+ {0x0a1a0a8a, {14, 10, 0, 8, 2}},
+ /* IPIPE:RGB,10b,Full,REC2020,REC2084;OPIPE:YCbCr,10b,Limited,REC2020,REC2084 */
+ {0x0a1a0c16, {0, 1, 0, 8, 2}},
+ /* IPIPE:YCbCr,10b,Full,REC2020,REC2084;OPIPE:YCbCr,10b,Limited,REC2020,REC2084 */
+ {0x0a1a0c1a, {15, 8, 0, 8, 2}},
+ /* IPIPE:RGB,10b,Full,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Limited,REC2020,REC2084 */
+ {0x0a1a0c86, {0, 5, 0, 8, 2}},
+ /* IPIPE:YCbCr,10b,Full,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Limited,REC2020,REC2084 */
+ {0x0a1a0c8a, {15, 10, 0, 8, 2}},
+ /* IPIPE:RGB,10b,Limited,REC709,REC709;OPIPE:YCbCr,10b,Limited,REC2020,REC2084 */
+ {0x0a1a1226, {0, 2, 1, 8, 2}},
+ /* IPIPE:YCbCr,10b,Limited,REC709,REC709;OPIPE:YCbCr,10b,Limited,REC2020,REC2084 */
+ {0x0a1a122a, {16, 9, 1, 8, 2}},
+ /* IPIPE:RGB,10b,Limited,REC709,SRGB;OPIPE:YCbCr,10b,Limited,REC2020,REC2084 */
+ {0x0a1a1306, {0, 6, 1, 8, 2}},
+ /* IPIPE:RGB,10b,Full,REC709,REC709;OPIPE:YCbCr,10b,Limited,REC2020,REC2084 */
+ {0x0a1a1426, {0, 3, 1, 8, 2}},
+ /* IPIPE:YCbCr,10b,Full,REC709,REC709;OPIPE:YCbCr,10b,Limited,REC2020,REC2084 */
+ {0x0a1a142a, {18, 9, 1, 8, 2}},
+ /* IPIPE:RGB,10b,Full,REC709,SRGB;OPIPE:YCbCr,10b,Limited,REC2020,REC2084 */
+ {0x0a1a1506, {0, 7, 1, 8, 2}},
+ /* IPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709;OPIPE:YCbCr,10b,Limited,REC2020,REC2084 */
+ {0x0a1a222a, {17, 9, 2, 8, 2}},
+ /* IPIPE:YCbCr,10b,Full,REC601_NTSC,REC709;OPIPE:YCbCr,10b,Limited,REC2020,REC2084 */
+ {0x0a1a242a, {19, 9, 2, 8, 2}},
+ /* IPIPE:RGB,10b,Limited,REC2020,REC2084;OPIPE:RGB,10b,Limited,REC2020,REC2100HLG */
+ {0x0a860a16, {0, 0, 0, 4, 0}},
+ /* IPIPE:YCbCr,10b,Limited,REC2020,REC2084;OPIPE:RGB,10b,Limited,REC2020,REC2100HLG */
+ {0x0a860a1a, {1, 0, 0, 4, 0}},
+ /* IPIPE:RGB,10b,Limited,REC2020,REC2100HLG;OPIPE:RGB,10b,Limited,REC2020,REC2100HLG */
+ {0x0a860a86, {0, 4, 0, 4, 0}},
+ /* IPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG;OPIPE:RGB,10b,Limited,REC2020,REC2100HLG */
+ {0x0a860a8a, {1, 4, 0, 4, 0}},
+ /* IPIPE:RGB,10b,Full,REC2020,REC2084;OPIPE:RGB,10b,Limited,REC2020,REC2100HLG */
+ {0x0a860c16, {0, 1, 0, 4, 0}},
+ /* IPIPE:YCbCr,10b,Full,REC2020,REC2084;OPIPE:RGB,10b,Limited,REC2020,REC2100HLG */
+ {0x0a860c1a, {2, 0, 0, 4, 0}},
+ /* IPIPE:RGB,10b,Full,REC2020,REC2100HLG;OPIPE:RGB,10b,Limited,REC2020,REC2100HLG */
+ {0x0a860c86, {0, 5, 0, 4, 0}},
+ /* IPIPE:YCbCr,10b,Full,REC2020,REC2100HLG;OPIPE:RGB,10b,Limited,REC2020,REC2100HLG */
+ {0x0a860c8a, {2, 4, 0, 4, 0}},
+ /* IPIPE:RGB,10b,Limited,REC709,REC709;OPIPE:RGB,10b,Limited,REC2020,REC2100HLG */
+ {0x0a861226, {0, 2, 1, 4, 0}},
+ /* IPIPE:YCbCr,10b,Limited,REC709,REC709;OPIPE:RGB,10b,Limited,REC2020,REC2100HLG */
+ {0x0a86122a, {3, 2, 1, 4, 0}},
+ /* IPIPE:RGB,10b,Limited,REC709,SRGB;OPIPE:RGB,10b,Limited,REC2020,REC2100HLG */
+ {0x0a861306, {0, 6, 1, 4, 0}},
+ /* IPIPE:RGB,10b,Full,REC709,REC709;OPIPE:RGB,10b,Limited,REC2020,REC2100HLG */
+ {0x0a861426, {0, 3, 1, 4, 0}},
+ /* IPIPE:YCbCr,10b,Full,REC709,REC709;OPIPE:RGB,10b,Limited,REC2020,REC2100HLG */
+ {0x0a86142a, {5, 2, 1, 4, 0}},
+ /* IPIPE:RGB,10b,Full,REC709,SRGB;OPIPE:RGB,10b,Limited,REC2020,REC2100HLG */
+ {0x0a861506, {0, 7, 1, 4, 0}},
+ /* IPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709;OPIPE:RGB,10b,Limited,REC2020,REC2100HLG */
+ {0x0a86222a, {4, 2, 2, 4, 0}},
+ /* IPIPE:YCbCr,10b,Full,REC601_NTSC,REC709;OPIPE:RGB,10b,Limited,REC2020,REC2100HLG */
+ {0x0a86242a, {6, 2, 2, 4, 0}},
+ /* IPIPE:RGB,10b,Limited,REC2020,REC2084;OPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG */
+ {0x0a8a0a16, {0, 0, 0, 10, 2}},
+ /* IPIPE:YCbCr,10b,Limited,REC2020,REC2084;OPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG */
+ {0x0a8a0a1a, {14, 8, 0, 10, 2}},
+ /* IPIPE:RGB,10b,Limited,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG */
+ {0x0a8a0a86, {0, 4, 0, 10, 2}},
+ /* IPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG */
+ {0x0a8a0a8a, {14, 10, 0, 10, 2}},
+ /* IPIPE:RGB,10b,Full,REC2020,REC2084;OPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG */
+ {0x0a8a0c16, {0, 1, 0, 10, 2}},
+ /* IPIPE:YCbCr,10b,Full,REC2020,REC2084;OPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG */
+ {0x0a8a0c1a, {15, 8, 0, 10, 2}},
+ /* IPIPE:RGB,10b,Full,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG */
+ {0x0a8a0c86, {0, 5, 0, 10, 2}},
+ /* IPIPE:YCbCr,10b,Full,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG */
+ {0x0a8a0c8a, {15, 10, 0, 10, 2}},
+ /* IPIPE:RGB,10b,Limited,REC709,REC709;OPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG */
+ {0x0a8a1226, {0, 2, 1, 10, 2}},
+ /* IPIPE:YCbCr,10b,Limited,REC709,REC709;OPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG */
+ {0x0a8a122a, {16, 9, 1, 10, 2}},
+ /* IPIPE:RGB,10b,Limited,REC709,SRGB;OPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG */
+ {0x0a8a1306, {0, 6, 1, 10, 2}},
+ /* IPIPE:RGB,10b,Full,REC709,REC709;OPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG */
+ {0x0a8a1426, {0, 3, 1, 10, 2}},
+ /* IPIPE:YCbCr,10b,Full,REC709,REC709;OPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG */
+ {0x0a8a142a, {18, 9, 1, 10, 2}},
+ /* IPIPE:RGB,10b,Full,REC709,SRGB;OPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG */
+ {0x0a8a1506, {0, 7, 1, 10, 2}},
+ /* IPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709;OPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG */
+ {0x0a8a222a, {17, 9, 2, 10, 2}},
+ /* IPIPE:YCbCr,10b,Full,REC601_NTSC,REC709;OPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG */
+ {0x0a8a242a, {19, 9, 2, 10, 2}},
+ /* IPIPE:RGB,10b,Limited,REC2020,REC2084;OPIPE:RGB,10b,Full,REC2020,REC2084 */
+ {0x0c160a16, {7, 1, 0, 1, 1}},
+ /* IPIPE:YCbCr,10b,Limited,REC2020,REC2084;OPIPE:RGB,10b,Full,REC2020,REC2084 */
+ {0x0c160a1a, {8, 1, 0, 1, 1}},
+ /* IPIPE:RGB,10b,Limited,REC2020,REC2100HLG;OPIPE:RGB,10b,Full,REC2020,REC2084 */
+ {0x0c160a86, {7, 5, 0, 1, 1}},
+ /* IPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG;OPIPE:RGB,10b,Full,REC2020,REC2084 */
+ {0x0c160a8a, {8, 5, 0, 1, 1}},
+ /* IPIPE:RGB,10b,Full,REC2020,REC2084;OPIPE:RGB,10b,Full,REC2020,REC2084 */
+ {0x0c160c16, {0, 1, 0, 1, 1}},
+ /* IPIPE:YCbCr,10b,Full,REC2020,REC2084;OPIPE:RGB,10b,Full,REC2020,REC2084 */
+ {0x0c160c1a, {9, 1, 0, 1, 1}},
+ /* IPIPE:RGB,10b,Full,REC2020,REC2100HLG;OPIPE:RGB,10b,Full,REC2020,REC2084 */
+ {0x0c160c86, {0, 5, 0, 1, 1}},
+ /* IPIPE:YCbCr,10b,Full,REC2020,REC2100HLG;OPIPE:RGB,10b,Full,REC2020,REC2084 */
+ {0x0c160c8a, {9, 5, 0, 1, 1}},
+ /* IPIPE:RGB,10b,Limited,REC709,REC709;OPIPE:RGB,10b,Full,REC2020,REC2084 */
+ {0x0c161226, {7, 3, 1, 1, 1}},
+ /* IPIPE:YCbCr,10b,Limited,REC709,REC709;OPIPE:RGB,10b,Full,REC2020,REC2084 */
+ {0x0c16122a, {10, 3, 1, 1, 1}},
+ /* IPIPE:RGB,10b,Limited,REC709,SRGB;OPIPE:RGB,10b,Full,REC2020,REC2084 */
+ {0x0c161306, {7, 7, 1, 1, 1}},
+ /* IPIPE:RGB,10b,Full,REC709,REC709;OPIPE:RGB,10b,Full,REC2020,REC2084 */
+ {0x0c161426, {0, 3, 1, 1, 1}},
+ /* IPIPE:YCbCr,10b,Full,REC709,REC709;OPIPE:RGB,10b,Full,REC2020,REC2084 */
+ {0x0c16142a, {12, 3, 1, 1, 1}},
+ /* IPIPE:RGB,10b,Full,REC709,SRGB;OPIPE:RGB,10b,Full,REC2020,REC2084 */
+ {0x0c161506, {0, 7, 1, 1, 1}},
+ /* IPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709;OPIPE:RGB,10b,Full,REC2020,REC2084 */
+ {0x0c16222a, {11, 3, 2, 1, 1}},
+ /* IPIPE:YCbCr,10b,Full,REC601_NTSC,REC709;OPIPE:RGB,10b,Full,REC2020,REC2084 */
+ {0x0c16242a, {13, 3, 2, 1, 1}},
+ /* IPIPE:RGB,10b,Limited,REC2020,REC2084;OPIPE:YCbCr,10b,Full,REC2020,REC2084 */
+ {0x0c1a0a16, {7, 1, 0, 1, 3}},
+ /* IPIPE:YCbCr,10b,Limited,REC2020,REC2084;OPIPE:YCbCr,10b,Full,REC2020,REC2084 */
+ {0x0c1a0a1a, {8, 1, 0, 1, 3}},
+ /* IPIPE:RGB,10b,Limited,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Full,REC2020,REC2084 */
+ {0x0c1a0a86, {7, 5, 0, 1, 3}},
+ /* IPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Full,REC2020,REC2084 */
+ {0x0c1a0a8a, {8, 5, 0, 1, 3}},
+ /* IPIPE:RGB,10b,Full,REC2020,REC2084;OPIPE:YCbCr,10b,Full,REC2020,REC2084 */
+ {0x0c1a0c16, {0, 1, 0, 1, 3}},
+ /* IPIPE:YCbCr,10b,Full,REC2020,REC2084;OPIPE:YCbCr,10b,Full,REC2020,REC2084 */
+ {0x0c1a0c1a, {9, 1, 0, 1, 3}},
+ /* IPIPE:RGB,10b,Full,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Full,REC2020,REC2084 */
+ {0x0c1a0c86, {0, 5, 0, 1, 3}},
+ /* IPIPE:YCbCr,10b,Full,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Full,REC2020,REC2084 */
+ {0x0c1a0c8a, {9, 5, 0, 1, 3}},
+ /* IPIPE:RGB,10b,Limited,REC709,REC709;OPIPE:YCbCr,10b,Full,REC2020,REC2084 */
+ {0x0c1a1226, {7, 3, 1, 1, 3}},
+ /* IPIPE:YCbCr,10b,Limited,REC709,REC709;OPIPE:YCbCr,10b,Full,REC2020,REC2084 */
+ {0x0c1a122a, {10, 3, 1, 1, 3}},
+ /* IPIPE:RGB,10b,Limited,REC709,SRGB;OPIPE:YCbCr,10b,Full,REC2020,REC2084 */
+ {0x0c1a1306, {7, 7, 1, 1, 3}},
+ /* IPIPE:RGB,10b,Full,REC709,REC709;OPIPE:YCbCr,10b,Full,REC2020,REC2084 */
+ {0x0c1a1426, {0, 3, 1, 1, 3}},
+ /* IPIPE:YCbCr,10b,Full,REC709,REC709;OPIPE:YCbCr,10b,Full,REC2020,REC2084 */
+ {0x0c1a142a, {12, 3, 1, 1, 3}},
+ /* IPIPE:RGB,10b,Full,REC709,SRGB;OPIPE:YCbCr,10b,Full,REC2020,REC2084 */
+ {0x0c1a1506, {0, 7, 1, 1, 3}},
+ /* IPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709;OPIPE:YCbCr,10b,Full,REC2020,REC2084 */
+ {0x0c1a222a, {11, 3, 2, 1, 3}},
+ /* IPIPE:YCbCr,10b,Full,REC601_NTSC,REC709;OPIPE:YCbCr,10b,Full,REC2020,REC2084 */
+ {0x0c1a242a, {13, 3, 2, 1, 3}},
+ /* IPIPE:RGB,10b,Limited,REC2020,REC2084;OPIPE:RGB,10b,Full,REC2020,REC2100HLG */
+ {0x0c860a16, {7, 1, 0, 5, 1}},
+ /* IPIPE:YCbCr,10b,Limited,REC2020,REC2084;OPIPE:RGB,10b,Full,REC2020,REC2100HLG */
+ {0x0c860a1a, {8, 1, 0, 5, 1}},
+ /* IPIPE:RGB,10b,Limited,REC2020,REC2100HLG;OPIPE:RGB,10b,Full,REC2020,REC2100HLG */
+ {0x0c860a86, {7, 5, 0, 5, 1}},
+ /* IPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG;OPIPE:RGB,10b,Full,REC2020,REC2100HLG */
+ {0x0c860a8a, {8, 5, 0, 5, 1}},
+ /* IPIPE:RGB,10b,Full,REC2020,REC2084;OPIPE:RGB,10b,Full,REC2020,REC2100HLG */
+ {0x0c860c16, {0, 1, 0, 5, 1}},
+ /* IPIPE:YCbCr,10b,Full,REC2020,REC2084;OPIPE:RGB,10b,Full,REC2020,REC2100HLG */
+ {0x0c860c1a, {9, 1, 0, 5, 1}},
+ /* IPIPE:RGB,10b,Full,REC2020,REC2100HLG;OPIPE:RGB,10b,Full,REC2020,REC2100HLG */
+ {0x0c860c86, {0, 5, 0, 5, 1}},
+ /* IPIPE:YCbCr,10b,Full,REC2020,REC2100HLG;OPIPE:RGB,10b,Full,REC2020,REC2100HLG */
+ {0x0c860c8a, {9, 5, 0, 5, 1}},
+ /* IPIPE:RGB,10b,Limited,REC709,REC709;OPIPE:RGB,10b,Full,REC2020,REC2100HLG */
+ {0x0c861226, {7, 3, 1, 5, 1}},
+ /* IPIPE:YCbCr,10b,Limited,REC709,REC709;OPIPE:RGB,10b,Full,REC2020,REC2100HLG */
+ {0x0c86122a, {10, 3, 1, 5, 1}},
+ /* IPIPE:RGB,10b,Limited,REC709,SRGB;OPIPE:RGB,10b,Full,REC2020,REC2100HLG */
+ {0x0c861306, {7, 7, 1, 5, 1}},
+ /* IPIPE:RGB,10b,Full,REC709,REC709;OPIPE:RGB,10b,Full,REC2020,REC2100HLG */
+ {0x0c861426, {0, 3, 1, 5, 1}},
+ /* IPIPE:YCbCr,10b,Full,REC709,REC709;OPIPE:RGB,10b,Full,REC2020,REC2100HLG */
+ {0x0c86142a, {12, 3, 1, 5, 1}},
+ /* IPIPE:RGB,10b,Full,REC709,SRGB;OPIPE:RGB,10b,Full,REC2020,REC2100HLG */
+ {0x0c861506, {0, 7, 1, 5, 1}},
+ /* IPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709;OPIPE:RGB,10b,Full,REC2020,REC2100HLG */
+ {0x0c86222a, {11, 3, 2, 5, 1}},
+ /* IPIPE:YCbCr,10b,Full,REC601_NTSC,REC709;OPIPE:RGB,10b,Full,REC2020,REC2100HLG */
+ {0x0c86242a, {13, 3, 2, 5, 1}},
+ /* IPIPE:RGB,10b,Limited,REC2020,REC2084;OPIPE:YCbCr,10b,Full,REC2020,REC2100HLG */
+ {0x0c8a0a16, {7, 1, 0, 5, 3}},
+ /* IPIPE:YCbCr,10b,Limited,REC2020,REC2084;OPIPE:YCbCr,10b,Full,REC2020,REC2100HLG */
+ {0x0c8a0a1a, {8, 1, 0, 5, 3}},
+ /* IPIPE:RGB,10b,Limited,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Full,REC2020,REC2100HLG */
+ {0x0c8a0a86, {7, 5, 0, 5, 3}},
+ /* IPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Full,REC2020,REC2100HLG */
+ {0x0c8a0a8a, {8, 5, 0, 5, 3}},
+ /* IPIPE:RGB,10b,Full,REC2020,REC2084;OPIPE:YCbCr,10b,Full,REC2020,REC2100HLG */
+ {0x0c8a0c16, {0, 1, 0, 5, 3}},
+ /* IPIPE:YCbCr,10b,Full,REC2020,REC2084;OPIPE:YCbCr,10b,Full,REC2020,REC2100HLG */
+ {0x0c8a0c1a, {9, 1, 0, 5, 3}},
+ /* IPIPE:RGB,10b,Full,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Full,REC2020,REC2100HLG */
+ {0x0c8a0c86, {0, 5, 0, 5, 3}},
+ /* IPIPE:YCbCr,10b,Full,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Full,REC2020,REC2100HLG */
+ {0x0c8a0c8a, {9, 5, 0, 5, 3}},
+ /* IPIPE:RGB,10b,Limited,REC709,REC709;OPIPE:YCbCr,10b,Full,REC2020,REC2100HLG */
+ {0x0c8a1226, {7, 3, 1, 5, 3}},
+ /* IPIPE:YCbCr,10b,Limited,REC709,REC709;OPIPE:YCbCr,10b,Full,REC2020,REC2100HLG */
+ {0x0c8a122a, {10, 3, 1, 5, 3}},
+ /* IPIPE:RGB,10b,Limited,REC709,SRGB;OPIPE:YCbCr,10b,Full,REC2020,REC2100HLG */
+ {0x0c8a1306, {7, 7, 1, 5, 3}},
+ /* IPIPE:RGB,10b,Full,REC709,REC709;OPIPE:YCbCr,10b,Full,REC2020,REC2100HLG */
+ {0x0c8a1426, {0, 3, 1, 5, 3}},
+ /* IPIPE:YCbCr,10b,Full,REC709,REC709;OPIPE:YCbCr,10b,Full,REC2020,REC2100HLG */
+ {0x0c8a142a, {12, 3, 1, 5, 3}},
+ /* IPIPE:RGB,10b,Full,REC709,SRGB;OPIPE:YCbCr,10b,Full,REC2020,REC2100HLG */
+ {0x0c8a1506, {0, 7, 1, 5, 3}},
+ /* IPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709;OPIPE:YCbCr,10b,Full,REC2020,REC2100HLG */
+ {0x0c8a222a, {11, 3, 2, 5, 3}},
+ /* IPIPE:YCbCr,10b,Full,REC601_NTSC,REC709;OPIPE:YCbCr,10b,Full,REC2020,REC2100HLG */
+ {0x0c8a242a, {13, 3, 2, 5, 3}},
+ /* IPIPE:RGB,10b,Limited,REC2020,REC2084;OPIPE:RGB,10b,Limited,REC709,REC709 */
+ {0x12260a16, {0, 0, 0, 2, 0}},
+ /* IPIPE:RGB,10b,Limited,REC2020,REC2100HLG;OPIPE:RGB,10b,Limited,REC709,REC709 */
+ {0x12260a86, {0, 4, 0, 2, 0}},
+ /* IPIPE:RGB,10b,Limited,REC709,REC709;OPIPE:RGB,10b,Limited,REC709,REC709 */
+ {0x12261226, {0, 2, 0, 2, 0}},
+ /* IPIPE:YCbCr,10b,Limited,REC709,REC709;OPIPE:RGB,10b,Limited,REC709,REC709 */
+ {0x1226122a, {3, 2, 0, 2, 0}},
+ /* IPIPE:RGB,10b,Limited,REC709,SRGB;OPIPE:RGB,10b,Limited,REC709,REC709 */
+ {0x12261306, {0, 6, 0, 2, 0}},
+ /* IPIPE:RGB,10b,Full,REC709,REC709;OPIPE:RGB,10b,Limited,REC709,REC709 */
+ {0x12261426, {0, 3, 0, 2, 0}},
+ /* IPIPE:YCbCr,10b,Full,REC709,REC709;OPIPE:RGB,10b,Limited,REC709,REC709 */
+ {0x1226142a, {5, 2, 0, 2, 0}},
+ /* IPIPE:RGB,10b,Full,REC709,SRGB;OPIPE:RGB,10b,Limited,REC709,REC709 */
+ {0x12261506, {0, 7, 0, 2, 0}},
+ /* IPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709;OPIPE:RGB,10b,Limited,REC709,REC709 */
+ {0x1226222a, {4, 2, 3, 2, 0}},
+ /* IPIPE:YCbCr,10b,Full,REC601_NTSC,REC709;OPIPE:RGB,10b,Limited,REC709,REC709 */
+ {0x1226242a, {6, 2, 3, 2, 0}},
+ /* IPIPE:RGB,10b,Limited,REC2020,REC2084;OPIPE:YCbCr,10b,Limited,REC709,REC709 */
+ {0x122a0a16, {0, 0, 0, 9, 4}},
+ /* IPIPE:RGB,10b,Limited,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Limited,REC709,REC709 */
+ {0x122a0a86, {0, 4, 0, 9, 4}},
+ /* IPIPE:RGB,10b,Limited,REC709,REC709;OPIPE:YCbCr,10b,Limited,REC709,REC709 */
+ {0x122a1226, {0, 2, 0, 9, 4}},
+ /* IPIPE:YCbCr,10b,Limited,REC709,REC709;OPIPE:YCbCr,10b,Limited,REC709,REC709 */
+ {0x122a122a, {16, 9, 0, 9, 4}},
+ /* IPIPE:RGB,10b,Limited,REC709,SRGB;OPIPE:YCbCr,10b,Limited,REC709,REC709 */
+ {0x122a1306, {0, 6, 0, 9, 4}},
+ /* IPIPE:RGB,10b,Full,REC709,REC709;OPIPE:YCbCr,10b,Limited,REC709,REC709 */
+ {0x122a1426, {0, 3, 0, 9, 4}},
+ /* IPIPE:YCbCr,10b,Full,REC709,REC709;OPIPE:YCbCr,10b,Limited,REC709,REC709 */
+ {0x122a142a, {18, 9, 0, 9, 4}},
+ /* IPIPE:RGB,10b,Full,REC709,SRGB;OPIPE:YCbCr,10b,Limited,REC709,REC709 */
+ {0x122a1506, {0, 7, 0, 9, 4}},
+ /* IPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709;OPIPE:YCbCr,10b,Limited,REC709,REC709 */
+ {0x122a222a, {17, 9, 3, 9, 4}},
+ /* IPIPE:YCbCr,10b,Full,REC601_NTSC,REC709;OPIPE:YCbCr,10b,Limited,REC709,REC709 */
+ {0x122a242a, {19, 9, 3, 9, 4}},
+ /* IPIPE:RGB,10b,Limited,REC2020,REC2084;OPIPE:RGB,10b,Limited,REC709,SRGB */
+ {0x13060a16, {0, 0, 0, 6, 0}},
+ /* IPIPE:YCbCr,10b,Limited,REC2020,REC2084;OPIPE:RGB,10b,Limited,REC709,SRGB */
+ {0x13060a1a, {1, 0, 0, 6, 0}},
+ /* IPIPE:RGB,10b,Limited,REC2020,REC2100HLG;OPIPE:RGB,10b,Limited,REC709,SRGB */
+ {0x13060a86, {0, 4, 0, 6, 0}},
+ /* IPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG;OPIPE:RGB,10b,Limited,REC709,SRGB */
+ {0x13060a8a, {1, 4, 0, 6, 0}},
+ /* IPIPE:RGB,10b,Full,REC2020,REC2084;OPIPE:RGB,10b,Limited,REC709,SRGB */
+ {0x13060c16, {0, 1, 0, 6, 0}},
+ /* IPIPE:YCbCr,10b,Full,REC2020,REC2084;OPIPE:RGB,10b,Limited,REC709,SRGB */
+ {0x13060c1a, {2, 0, 0, 6, 0}},
+ /* IPIPE:RGB,10b,Full,REC2020,REC2100HLG;OPIPE:RGB,10b,Limited,REC709,SRGB */
+ {0x13060c86, {0, 5, 0, 6, 0}},
+ /* IPIPE:YCbCr,10b,Full,REC2020,REC2100HLG;OPIPE:RGB,10b,Limited,REC709,SRGB */
+ {0x13060c8a, {2, 4, 0, 6, 0}},
+ /* IPIPE:RGB,10b,Limited,REC709,REC709;OPIPE:RGB,10b,Limited,REC709,SRGB */
+ {0x13061226, {0, 2, 0, 6, 0}},
+ /* IPIPE:YCbCr,10b,Limited,REC709,REC709;OPIPE:RGB,10b,Limited,REC709,SRGB */
+ {0x1306122a, {3, 2, 0, 6, 0}},
+ /* IPIPE:RGB,10b,Limited,REC709,SRGB;OPIPE:RGB,10b,Limited,REC709,SRGB */
+ {0x13061306, {0, 6, 0, 6, 0}},
+ /* IPIPE:RGB,10b,Full,REC709,REC709;OPIPE:RGB,10b,Limited,REC709,SRGB */
+ {0x13061426, {0, 3, 0, 6, 0}},
+ /* IPIPE:YCbCr,10b,Full,REC709,REC709;OPIPE:RGB,10b,Limited,REC709,SRGB */
+ {0x1306142a, {5, 2, 0, 6, 0}},
+ /* IPIPE:RGB,10b,Full,REC709,SRGB;OPIPE:RGB,10b,Limited,REC709,SRGB */
+ {0x13061506, {0, 7, 0, 6, 0}},
+ /* IPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709;OPIPE:RGB,10b,Limited,REC709,SRGB */
+ {0x1306222a, {4, 2, 3, 6, 0}},
+ /* IPIPE:YCbCr,10b,Full,REC601_NTSC,REC709;OPIPE:RGB,10b,Limited,REC709,SRGB */
+ {0x1306242a, {6, 2, 3, 6, 0}},
+ /* IPIPE:RGB,10b,Limited,REC2020,REC2084;OPIPE:RGB,10b,Full,REC709,REC709 */
+ {0x14260a16, {7, 1, 0, 3, 1}},
+ /* IPIPE:RGB,10b,Limited,REC2020,REC2100HLG;OPIPE:RGB,10b,Full,REC709,REC709 */
+ {0x14260a86, {7, 5, 0, 3, 1}},
+ /* IPIPE:RGB,10b,Limited,REC709,REC709;OPIPE:RGB,10b,Full,REC709,REC709 */
+ {0x14261226, {7, 3, 0, 3, 1}},
+ /* IPIPE:YCbCr,10b,Limited,REC709,REC709;OPIPE:RGB,10b,Full,REC709,REC709 */
+ {0x1426122a, {10, 3, 0, 3, 1}},
+ /* IPIPE:RGB,10b,Limited,REC709,SRGB;OPIPE:RGB,10b,Full,REC709,REC709 */
+ {0x14261306, {7, 7, 0, 3, 1}},
+ /* IPIPE:RGB,10b,Full,REC709,REC709;OPIPE:RGB,10b,Full,REC709,REC709 */
+ {0x14261426, {0, 3, 0, 3, 1}},
+ /* IPIPE:YCbCr,10b,Full,REC709,REC709;OPIPE:RGB,10b,Full,REC709,REC709 */
+ {0x1426142a, {12, 3, 0, 3, 1}},
+ /* IPIPE:RGB,10b,Full,REC709,SRGB;OPIPE:RGB,10b,Full,REC709,REC709 */
+ {0x14261506, {0, 7, 0, 3, 1}},
+ /* IPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709;OPIPE:RGB,10b,Full,REC709,REC709 */
+ {0x1426222a, {11, 3, 3, 3, 1}},
+ /* IPIPE:YCbCr,10b,Full,REC601_NTSC,REC709;OPIPE:RGB,10b,Full,REC709,REC709 */
+ {0x1426242a, {13, 3, 3, 3, 1}},
+ /* IPIPE:RGB,10b,Limited,REC2020,REC2084;OPIPE:YCbCr,10b,Full,REC709,REC709 */
+ {0x142a0a16, {7, 1, 0, 3, 6}},
+ /* IPIPE:RGB,10b,Limited,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Full,REC709,REC709 */
+ {0x142a0a86, {7, 5, 0, 3, 6}},
+ /* IPIPE:RGB,10b,Limited,REC709,REC709;OPIPE:YCbCr,10b,Full,REC709,REC709 */
+ {0x142a1226, {7, 3, 0, 3, 6}},
+ /* IPIPE:YCbCr,10b,Limited,REC709,REC709;OPIPE:YCbCr,10b,Full,REC709,REC709 */
+ {0x142a122a, {10, 3, 0, 3, 6}},
+ /* IPIPE:RGB,10b,Limited,REC709,SRGB;OPIPE:YCbCr,10b,Full,REC709,REC709 */
+ {0x142a1306, {7, 7, 0, 3, 6}},
+ /* IPIPE:RGB,10b,Full,REC709,REC709;OPIPE:YCbCr,10b,Full,REC709,REC709 */
+ {0x142a1426, {0, 3, 0, 3, 6}},
+ /* IPIPE:YCbCr,10b,Full,REC709,REC709;OPIPE:YCbCr,10b,Full,REC709,REC709 */
+ {0x142a142a, {12, 3, 0, 3, 6}},
+ /* IPIPE:RGB,10b,Full,REC709,SRGB;OPIPE:YCbCr,10b,Full,REC709,REC709 */
+ {0x142a1506, {0, 7, 0, 3, 6}},
+ /* IPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709;OPIPE:YCbCr,10b,Full,REC709,REC709 */
+ {0x142a222a, {11, 3, 3, 3, 6}},
+ /* IPIPE:YCbCr,10b,Full,REC601_NTSC,REC709;OPIPE:YCbCr,10b,Full,REC709,REC709 */
+ {0x142a242a, {13, 3, 3, 3, 6}},
+ /* IPIPE:RGB,10b,Limited,REC2020,REC2084;OPIPE:RGB,10b,Full,REC709,SRGB */
+ {0x15060a16, {7, 1, 0, 7, 1}},
+ /* IPIPE:RGB,10b,Limited,REC2020,REC2100HLG;OPIPE:RGB,10b,Full,REC709,SRGB */
+ {0x15060a86, {7, 5, 0, 7, 1}},
+ /* IPIPE:RGB,10b,Limited,REC709,REC709;OPIPE:RGB,10b,Full,REC709,SRGB */
+ {0x15061226, {7, 3, 0, 7, 1}},
+ /* IPIPE:YCbCr,10b,Limited,REC709,REC709;OPIPE:RGB,10b,Full,REC709,SRGB */
+ {0x1506122a, {10, 3, 0, 7, 1}},
+ /* IPIPE:RGB,10b,Limited,REC709,SRGB;OPIPE:RGB,10b,Full,REC709,SRGB */
+ {0x15061306, {7, 7, 0, 7, 1}},
+ /* IPIPE:RGB,10b,Full,REC709,REC709;OPIPE:RGB,10b,Full,REC709,SRGB */
+ {0x15061426, {0, 3, 0, 7, 1}},
+ /* IPIPE:YCbCr,10b,Full,REC709,REC709;OPIPE:RGB,10b,Full,REC709,SRGB */
+ {0x1506142a, {12, 3, 0, 7, 1}},
+ /* IPIPE:RGB,10b,Full,REC709,SRGB;OPIPE:RGB,10b,Full,REC709,SRGB */
+ {0x15061506, {0, 7, 0, 7, 1}},
+ /* IPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709;OPIPE:RGB,10b,Full,REC709,SRGB */
+ {0x1506222a, {11, 3, 3, 7, 1}},
+ /* IPIPE:YCbCr,10b,Full,REC601_NTSC,REC709;OPIPE:RGB,10b,Full,REC709,SRGB */
+ {0x1506242a, {13, 3, 3, 7, 1}},
+ /* IPIPE:RGB,10b,Limited,REC2020,REC2084;OPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709 */
+ {0x222a0a16, {0, 0, 0, 9, 5}},
+ /* IPIPE:YCbCr,10b,Limited,REC2020,REC2084;OPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709 */
+ {0x222a0a1a, {14, 8, 0, 9, 5}},
+ /* IPIPE:RGB,10b,Limited,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709 */
+ {0x222a0a86, {0, 4, 0, 9, 5}},
+ /* IPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709 */
+ {0x222a0a8a, {14, 10, 0, 9, 5}},
+ /* IPIPE:RGB,10b,Full,REC2020,REC2084;OPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709 */
+ {0x222a0c16, {0, 1, 0, 9, 5}},
+ /* IPIPE:YCbCr,10b,Full,REC2020,REC2084;OPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709 */
+ {0x222a0c1a, {15, 8, 0, 9, 5}},
+ /* IPIPE:RGB,10b,Full,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709 */
+ {0x222a0c86, {0, 5, 0, 9, 5}},
+ /* IPIPE:YCbCr,10b,Full,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709 */
+ {0x222a0c8a, {15, 10, 0, 9, 5}},
+ /* IPIPE:RGB,10b,Limited,REC709,REC709;OPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709 */
+ {0x222a1226, {0, 2, 4, 9, 5}},
+ /* IPIPE:YCbCr,10b,Limited,REC709,REC709;OPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709 */
+ {0x222a122a, {16, 9, 4, 9, 5}},
+ /* IPIPE:RGB,10b,Limited,REC709,SRGB;OPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709 */
+ {0x222a1306, {0, 6, 4, 9, 5}},
+ /* IPIPE:RGB,10b,Full,REC709,REC709;OPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709 */
+ {0x222a1426, {0, 3, 4, 9, 5}},
+ /* IPIPE:YCbCr,10b,Full,REC709,REC709;OPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709 */
+ {0x222a142a, {18, 9, 4, 9, 5}},
+ /* IPIPE:RGB,10b,Full,REC709,SRGB;OPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709 */
+ {0x222a1506, {0, 7, 4, 9, 5}},
+ /* IPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709;OPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709 */
+ {0x222a222a, {17, 9, 0, 9, 5}},
+ /* IPIPE:YCbCr,10b,Full,REC601_NTSC,REC709;OPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709 */
+ {0x222a242a, {19, 9, 0, 9, 5}},
+ /* IPIPE:RGB,10b,Limited,REC2020,REC2084;OPIPE:YCbCr,10b,Full,REC601_NTSC,REC709 */
+ {0x242a0a16, {7, 1, 0, 3, 7}},
+ /* IPIPE:YCbCr,10b,Limited,REC2020,REC2084;OPIPE:YCbCr,10b,Full,REC601_NTSC,REC709 */
+ {0x242a0a1a, {8, 1, 0, 3, 7}},
+ /* IPIPE:RGB,10b,Limited,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Full,REC601_NTSC,REC709 */
+ {0x242a0a86, {7, 5, 0, 3, 7}},
+ /* IPIPE:YCbCr,10b,Limited,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Full,REC601_NTSC,REC709 */
+ {0x242a0a8a, {8, 5, 0, 3, 7}},
+ /* IPIPE:RGB,10b,Full,REC2020,REC2084;OPIPE:YCbCr,10b,Full,REC601_NTSC,REC709 */
+ {0x242a0c16, {0, 1, 0, 3, 7}},
+ /* IPIPE:YCbCr,10b,Full,REC2020,REC2084;OPIPE:YCbCr,10b,Full,REC601_NTSC,REC709 */
+ {0x242a0c1a, {9, 1, 0, 3, 7}},
+ /* IPIPE:RGB,10b,Full,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Full,REC601_NTSC,REC709 */
+ {0x242a0c86, {0, 5, 0, 3, 7}},
+ /* IPIPE:YCbCr,10b,Full,REC2020,REC2100HLG;OPIPE:YCbCr,10b,Full,REC601_NTSC,REC709 */
+ {0x242a0c8a, {9, 5, 0, 3, 7}},
+ /* IPIPE:RGB,10b,Limited,REC709,REC709;OPIPE:YCbCr,10b,Full,REC601_NTSC,REC709 */
+ {0x242a1226, {7, 3, 4, 3, 7}},
+ /* IPIPE:YCbCr,10b,Limited,REC709,REC709;OPIPE:YCbCr,10b,Full,REC601_NTSC,REC709 */
+ {0x242a122a, {10, 3, 4, 3, 7}},
+ /* IPIPE:RGB,10b,Limited,REC709,SRGB;OPIPE:YCbCr,10b,Full,REC601_NTSC,REC709 */
+ {0x242a1306, {7, 7, 4, 3, 7}},
+ /* IPIPE:RGB,10b,Full,REC709,REC709;OPIPE:YCbCr,10b,Full,REC601_NTSC,REC709 */
+ {0x242a1426, {0, 3, 4, 3, 7}},
+ /* IPIPE:YCbCr,10b,Full,REC709,REC709;OPIPE:YCbCr,10b,Full,REC601_NTSC,REC709 */
+ {0x242a142a, {12, 3, 4, 3, 7}},
+ /* IPIPE:RGB,10b,Full,REC709,SRGB;OPIPE:YCbCr,10b,Full,REC601_NTSC,REC709 */
+ {0x242a1506, {0, 7, 4, 3, 7}},
+ /* IPIPE:YCbCr,10b,Limited,REC601_NTSC,REC709;OPIPE:YCbCr,10b,Full,REC601_NTSC,REC709 */
+ {0x242a222a, {11, 3, 0, 3, 7}},
+ /* IPIPE:YCbCr,10b,Full,REC601_NTSC,REC709;OPIPE:YCbCr,10b,Full,REC601_NTSC,REC709 */
+ {0x242a242a, {13, 3, 0, 3, 7}},
+};
+
+#endif /* __DCSS_HDR10_TABLES_H__ */
diff --git a/drivers/gpu/drm/imx/dcss/dcss-hdr10.c b/drivers/gpu/drm/imx/dcss/dcss-hdr10.c
new file mode 100644
index 000000000000..45d3c3125833
--- /dev/null
+++ b/drivers/gpu/drm/imx/dcss/dcss-hdr10.c
@@ -0,0 +1,395 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2019 NXP.
+ */
+
+#include <linux/device.h>
+#include <linux/bitops.h>
+#include <linux/bsearch.h>
+#include <linux/io.h>
+#include <drm/drm_fourcc.h>
+
+#include "dcss-dev.h"
+#include "dcss-hdr10-tables.h"
+
+#define DCSS_HDR10_A0_LUT 0x0000
+#define DCSS_HDR10_A1_LUT 0x1000
+#define DCSS_HDR10_A2_LUT 0x2000
+/* one CSCA and CSCB for each channel(pipe) */
+#define DCSS_HDR10_CSCA_BASE 0x3000
+#define DCSS_HDR10_CSCB_BASE 0x3800
+
+/* one CSCO for all channels(pipes) */
+#define DCSS_HDR10_CSCO_BASE 0x3000
+
+#define DCSS_HDR10_LUT_CONTROL (DCSS_HDR10_CSCA_BASE + 0x80)
+#define LUT_ENABLE BIT(0)
+#define LUT_EN_FOR_ALL_PELS BIT(1)
+#define LUT_BYPASS BIT(15)
+#define DCSS_HDR10_FL2FX (DCSS_HDR10_CSCB_BASE + 0x74)
+#define DCSS_HDR10_LTNL (DCSS_HDR10_CSCO_BASE + 0x74)
+#define LTNL_PASS_THRU BIT(0)
+#define FIX2FLT_DISABLE BIT(1)
+#define LTNL_EN_FOR_ALL_PELS BIT(2)
+#define FIX2FLT_EN_FOR_ALL_PELS BIT(3)
+
+/* following offsets are relative to CSC(A|B|O)_BASE */
+#define DCSS_HDR10_CSC_CONTROL 0x00
+#define CSC_EN BIT(0)
+#define CSC_ALL_PIX_EN BIT(1)
+#define CSC_BYPASS BIT(15)
+#define DCSS_HDR10_CSC_H00 0x04
+#define DCSS_HDR10_CSC_H10 0x08
+#define DCSS_HDR10_CSC_H20 0x0C
+#define DCSS_HDR10_CSC_H01 0x10
+#define DCSS_HDR10_CSC_H11 0x14
+#define DCSS_HDR10_CSC_H21 0x18
+#define DCSS_HDR10_CSC_H02 0x1C
+#define DCSS_HDR10_CSC_H12 0x20
+#define DCSS_HDR10_CSC_H22 0x24
+#define H_COEF_MASK GENMASK(15, 0)
+#define DCSS_HDR10_CSC_IO0 0x28
+#define DCSS_HDR10_CSC_IO1 0x2C
+#define DCSS_HDR10_CSC_IO2 0x30
+#define PRE_OFFSET_MASK GENMASK(9, 0)
+#define DCSS_HDR10_CSC_IO_MIN0 0x34
+#define DCSS_HDR10_CSC_IO_MIN1 0x38
+#define DCSS_HDR10_CSC_IO_MIN2 0x3C
+#define DCSS_HDR10_CSC_IO_MAX0 0x40
+#define DCSS_HDR10_CSC_IO_MAX1 0x44
+#define DCSS_HDR10_CSC_IO_MAX2 0x48
+#define IO_CLIP_MASK GENMASK(9, 0)
+#define DCSS_HDR10_CSC_NORM 0x4C
+#define NORM_MASK GENMASK(4, 0)
+#define DCSS_HDR10_CSC_OO0 0x50
+#define DCSS_HDR10_CSC_OO1 0x54
+#define DCSS_HDR10_CSC_OO2 0x58
+#define POST_OFFSET_MASK GENMASK(27, 0)
+#define DCSS_HDR10_CSC_OMIN0 0x5C
+#define DCSS_HDR10_CSC_OMIN1 0x60
+#define DCSS_HDR10_CSC_OMIN2 0x64
+#define DCSS_HDR10_CSC_OMAX0 0x68
+#define DCSS_HDR10_CSC_OMAX1 0x6C
+#define DCSS_HDR10_CSC_OMAX2 0x70
+#define POST_CLIP_MASK GENMASK(9, 0)
+
+#define HDR10_IPIPE_LUT_MAX_ENTRIES 1024
+#define HDR10_OPIPE_LUT_MAX_ENTRIES 1023
+#define HDR10_CSC_MAX_REGS 29
+
+#define OPIPE_CH_NO 3
+
+/* Pipe config descriptor */
+
+/* bits per component */
+#define HDR10_BPC_POS 0
+#define HDR10_BPC_MASK GENMASK(1, 0)
+/* colorspace */
+#define HDR10_CS_POS 2
+#define HDR10_CS_MASK GENMASK(3, 2)
+/* nonlinearity type */
+#define HDR10_NL_POS 4
+#define HDR10_NL_MASK GENMASK(8, 4)
+/* pixel range */
+#define HDR10_PR_POS 9
+#define HDR10_PR_MASK GENMASK(10, 9)
+/* gamut type */
+#define HDR10_G_POS 11
+#define HDR10_G_MASK GENMASK(15, 11)
+
+/* FW Table Type Descriptor */
+#define HDR10_TT_LUT BIT(0)
+#define HDR10_TT_CSCA BIT(1)
+#define HDR10_TT_CSCB BIT(2)
+/* Pipe type */
+#define HDR10_PT_OUTPUT BIT(3)
+/* Output pipe config descriptor */
+#define HDR10_IPIPE_DESC_POS 4
+#define HDR10_IPIPE_DESC_MASK GENMASK(19, 4)
+/* Input pipe config descriptor */
+#define HDR10_OPIPE_DESC_POS 20
+#define HDR10_OPIPE_DESC_MASK GENMASK(35, 20)
+
+/* config invalid */
+#define HDR10_DESC_INVALID BIT(63)
+
+enum dcss_hdr10_csc {
+ HDR10_CSCA,
+ HDR10_CSCB,
+};
+
+struct dcss_hdr10_ch {
+ struct dcss_hdr10 *hdr10;
+ void __iomem *base_reg;
+ u32 base_ofs;
+
+ u64 old_cfg_desc;
+
+ u32 id;
+};
+
+struct dcss_hdr10 {
+ struct device *dev;
+ struct dcss_ctxld *ctxld;
+
+ u32 ctx_id;
+
+ struct dcss_hdr10_ch ch[4]; /* 4th channel is, actually, OPIPE */
+};
+
+static void dcss_hdr10_write(struct dcss_hdr10_ch *ch, u32 val, u32 ofs)
+{
+ struct dcss_hdr10 *hdr10 = ch->hdr10;
+
+ dcss_ctxld_write(hdr10->ctxld, hdr10->ctx_id, val, ch->base_ofs + ofs);
+}
+
+static void dcss_hdr10_csc_fill(struct dcss_hdr10_ch *ch,
+ enum dcss_hdr10_csc csc_to_use,
+ const u32 *map)
+{
+ int i;
+ u32 csc_base_ofs[] = {
+ DCSS_HDR10_CSCA_BASE + DCSS_HDR10_CSC_CONTROL,
+ DCSS_HDR10_CSCB_BASE + DCSS_HDR10_CSC_CONTROL,
+ };
+
+ for (i = 0; i < HDR10_CSC_MAX_REGS; i++) {
+ u32 reg_ofs = csc_base_ofs[csc_to_use] + i * sizeof(u32);
+
+ dcss_hdr10_write(ch, map[i], reg_ofs);
+ }
+}
+
+static void dcss_hdr10_lut_fill(struct dcss_hdr10_ch *ch, const u16 *map)
+{
+ int i, comp;
+ u32 lut_base_ofs, ctrl_ofs, lut_entries;
+
+ if (ch->id == OPIPE_CH_NO) {
+ ctrl_ofs = DCSS_HDR10_LTNL;
+ lut_entries = HDR10_OPIPE_LUT_MAX_ENTRIES;
+ } else {
+ ctrl_ofs = DCSS_HDR10_LUT_CONTROL;
+ lut_entries = HDR10_IPIPE_LUT_MAX_ENTRIES;
+ }
+
+ if (ch->id != OPIPE_CH_NO)
+ dcss_hdr10_write(ch, *map++, ctrl_ofs);
+
+ for (comp = 0; comp < 3; comp++) {
+ lut_base_ofs = DCSS_HDR10_A0_LUT + comp * 0x1000;
+
+ if (ch->id == OPIPE_CH_NO) {
+ dcss_hdr10_write(ch, map[0], lut_base_ofs);
+ lut_base_ofs += 4;
+ }
+
+ for (i = 0; i < lut_entries; i++) {
+ u32 reg_ofs = lut_base_ofs + i * sizeof(u32);
+
+ dcss_hdr10_write(ch, map[i], reg_ofs);
+ }
+ }
+
+ map += lut_entries;
+
+ if (ch->id != OPIPE_CH_NO)
+ dcss_hdr10_write(ch, *map, DCSS_HDR10_FL2FX);
+ else
+ dcss_hdr10_write(ch, *map, ctrl_ofs);
+}
+
+static int dcss_hdr10_ch_init_all(struct dcss_hdr10 *hdr10,
+ unsigned long hdr10_base)
+{
+ struct dcss_hdr10_ch *ch;
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ ch = &hdr10->ch[i];
+
+ ch->base_ofs = hdr10_base + i * 0x4000;
+
+ ch->base_reg = ioremap(ch->base_ofs, SZ_16K);
+ if (!ch->base_reg) {
+ dev_err(hdr10->dev, "hdr10: unable to remap ch base\n");
+ return -ENOMEM;
+ }
+
+ ch->old_cfg_desc = HDR10_DESC_INVALID;
+
+ ch->id = i;
+ ch->hdr10 = hdr10;
+ }
+
+ return 0;
+}
+
+static int dcss_hdr10_id_compare(const void *a, const void *b)
+{
+ const u32 id = *(const u32 *)a;
+ const u32 tbl_id = *(const u32 *)b;
+
+ if (id == tbl_id)
+ return 0;
+
+ if (id > tbl_id)
+ return 1;
+
+ return -1;
+}
+
+static struct dcss_pipe_cfg *dcss_hdr10_get_pipe_cfg(struct dcss_hdr10 *hdr10,
+ u32 desc)
+{
+ struct dcss_pipe_cfg *res;
+
+ res = bsearch(&desc, dcss_cfg_table, ARRAY_SIZE(dcss_cfg_table),
+ sizeof(dcss_cfg_table[0]), dcss_hdr10_id_compare);
+ if (!res)
+ dev_dbg(hdr10->dev,
+ "hdr10 cfg table doesn't support desc(0x%08x)\n", desc);
+
+ return res;
+}
+
+static int dcss_hdr10_get_tbls(struct dcss_hdr10 *hdr10, u32 desc,
+ const u16 **ilut, const u32 **csca,
+ const u32 **cscb, const u16 **olut,
+ const u32 **csco)
+{
+ struct dcss_pipe_cfg *pipe_cfg;
+
+ pipe_cfg = dcss_hdr10_get_pipe_cfg(hdr10, desc);
+ if (!pipe_cfg) {
+ dev_err(hdr10->dev, "failed to get hdr10 pipe configurations\n");
+ return -EINVAL;
+ }
+
+ dev_dbg(hdr10->dev, "found tbl_id = 0x%08x: (%d, %d, %d, %d, %d)",
+ pipe_cfg->id, pipe_cfg->idx[0], pipe_cfg->idx[1],
+ pipe_cfg->idx[2], pipe_cfg->idx[3], pipe_cfg->idx[4]);
+
+ *csca = dcss_cscas[pipe_cfg->idx[0]];
+ *ilut = dcss_iluts[pipe_cfg->idx[1]];
+ *cscb = dcss_cscbs[pipe_cfg->idx[2]];
+ *olut = dcss_oluts[pipe_cfg->idx[3]];
+ *csco = dcss_cscos[pipe_cfg->idx[4]];
+
+ return 0;
+}
+
+static void dcss_hdr10_write_pipe_tbls(struct dcss_hdr10_ch *ch,
+ const u16 *lut, const u32 *csca,
+ const u32 *cscb)
+{
+ if (csca)
+ dcss_hdr10_csc_fill(ch, HDR10_CSCA, csca);
+
+ if (ch->id != OPIPE_CH_NO && cscb)
+ dcss_hdr10_csc_fill(ch, HDR10_CSCB, cscb);
+
+ if (lut)
+ dcss_hdr10_lut_fill(ch, lut);
+}
+
+int dcss_hdr10_init(struct dcss_dev *dcss, unsigned long hdr10_base)
+{
+ int ret;
+ struct dcss_hdr10 *hdr10;
+
+ hdr10 = kzalloc(sizeof(*hdr10), GFP_KERNEL);
+ if (!hdr10)
+ return -ENOMEM;
+
+ dcss->hdr10 = hdr10;
+ hdr10->dev = dcss->dev;
+ hdr10->ctx_id = CTX_SB_HP;
+ hdr10->ctxld = dcss->ctxld;
+
+ ret = dcss_hdr10_ch_init_all(hdr10, hdr10_base);
+ if (ret) {
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ if (hdr10->ch[i].base_reg)
+ iounmap(hdr10->ch[i].base_reg);
+ }
+
+ goto cleanup;
+ }
+
+ return 0;
+
+cleanup:
+ kfree(hdr10);
+
+ return ret;
+}
+
+void dcss_hdr10_exit(struct dcss_hdr10 *hdr10)
+{
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ if (hdr10->ch[i].base_reg)
+ iounmap(hdr10->ch[i].base_reg);
+ }
+
+ kfree(hdr10);
+}
+
+static u32 dcss_hdr10_pipe_desc(struct dcss_hdr10_pipe_cfg *pipe_cfg)
+{
+ u32 desc;
+
+ desc = 2 << HDR10_BPC_POS;
+ desc |= pipe_cfg->is_yuv ? 2 << HDR10_CS_POS : 1 << HDR10_CS_POS;
+ desc |= ((1 << pipe_cfg->nl) << HDR10_NL_POS) & HDR10_NL_MASK;
+ desc |= ((1 << pipe_cfg->pr) << HDR10_PR_POS) & HDR10_PR_MASK;
+ desc |= ((1 << pipe_cfg->g) << HDR10_G_POS) & HDR10_G_MASK;
+
+ return desc;
+}
+
+static u64 dcss_hdr10_get_desc(struct dcss_hdr10_pipe_cfg *ipipe_cfg,
+ struct dcss_hdr10_pipe_cfg *opipe_cfg)
+{
+ u32 ipipe_desc, opipe_desc;
+
+ ipipe_desc = dcss_hdr10_pipe_desc(ipipe_cfg);
+ opipe_desc = dcss_hdr10_pipe_desc(opipe_cfg);
+
+ return (ipipe_desc & 0xFFFF) | ((opipe_desc & 0xFFFF) << 16);
+}
+
+bool dcss_hdr10_pipe_cfg_is_supported(struct dcss_hdr10 *hdr10,
+ struct dcss_hdr10_pipe_cfg *ipipe_cfg,
+ struct dcss_hdr10_pipe_cfg *opipe_cfg)
+{
+ u32 desc = dcss_hdr10_get_desc(ipipe_cfg, opipe_cfg);
+
+ return !!dcss_hdr10_get_pipe_cfg(hdr10, desc);
+}
+
+void dcss_hdr10_setup(struct dcss_hdr10 *hdr10, int ch_num,
+ struct dcss_hdr10_pipe_cfg *ipipe_cfg,
+ struct dcss_hdr10_pipe_cfg *opipe_cfg)
+{
+ const u16 *ilut, *olut;
+ const u32 *csca, *cscb, *csco;
+ u32 desc = dcss_hdr10_get_desc(ipipe_cfg, opipe_cfg);
+
+ if (hdr10->ch[ch_num].old_cfg_desc == desc)
+ return;
+
+ if (dcss_hdr10_get_tbls(hdr10, desc, &ilut, &csca, &cscb, &olut, &csco))
+ return;
+
+ dcss_hdr10_write_pipe_tbls(&hdr10->ch[ch_num], ilut, csca, cscb);
+
+ hdr10->ch[ch_num].old_cfg_desc = desc;
+
+ dcss_hdr10_write_pipe_tbls(&hdr10->ch[OPIPE_CH_NO], olut, csco, NULL);
+}
diff --git a/drivers/gpu/drm/imx/dcss/dcss-kms.c b/drivers/gpu/drm/imx/dcss/dcss-kms.c
index 8cf3352d8858..eeba8688b339 100644
--- a/drivers/gpu/drm/imx/dcss/dcss-kms.c
+++ b/drivers/gpu/drm/imx/dcss/dcss-kms.c
@@ -3,6 +3,7 @@
* Copyright 2019 NXP.
*/
+#include <drm/bridge/cdns-mhdp.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge_connector.h>
@@ -13,22 +14,44 @@
#include <drm/drm_of.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_vblank.h>
+#include <linux/component.h>
#include "dcss-dev.h"
#include "dcss-kms.h"
DEFINE_DRM_GEM_CMA_FOPS(dcss_cma_fops);
+static int dcss_kms_atomic_check(struct drm_device *dev,
+ struct drm_atomic_state *state)
+{
+ int ret;
+
+ ret = drm_atomic_helper_check_modeset(dev, state);
+ if (ret)
+ return ret;
+
+ ret = drm_atomic_normalize_zpos(dev, state);
+ if (ret)
+ return ret;
+
+ ret = dcss_crtc_setup_opipe(dev, state);
+ if (ret)
+ return ret;
+
+ return drm_atomic_helper_check_planes(dev, state);
+}
+
static const struct drm_mode_config_funcs dcss_drm_mode_config_funcs = {
.fb_create = drm_gem_fb_create,
.output_poll_changed = drm_fb_helper_output_poll_changed,
- .atomic_check = drm_atomic_helper_check,
+ .atomic_check = dcss_kms_atomic_check,
.atomic_commit = drm_atomic_helper_commit,
};
static const struct drm_driver dcss_kms_driver = {
.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
DRM_GEM_CMA_DRIVER_OPS,
+ .gem_prime_import = drm_gem_prime_import,
.fops = &dcss_cma_fops,
.name = "imx-dcss",
.desc = "i.MX8MQ Display Subsystem",
@@ -107,7 +130,7 @@ static int dcss_kms_bridge_connector_init(struct dcss_kms_dev *kms)
return 0;
}
-struct dcss_kms_dev *dcss_kms_attach(struct dcss_dev *dcss)
+struct dcss_kms_dev *dcss_kms_attach(struct dcss_dev *dcss, bool componentized)
{
struct dcss_kms_dev *kms;
struct drm_device *drm;
@@ -130,16 +153,26 @@ struct dcss_kms_dev *dcss_kms_attach(struct dcss_dev *dcss)
if (ret)
goto cleanup_mode_config;
- ret = dcss_kms_bridge_connector_init(kms);
- if (ret)
- goto cleanup_mode_config;
+ if (!componentized) {
+ ret = dcss_kms_bridge_connector_init(kms);
+ if (ret)
+ goto cleanup_mode_config;
+ }
ret = dcss_crtc_init(crtc, drm);
if (ret)
goto cleanup_mode_config;
+ if (componentized) {
+ ret = component_bind_all(dcss->dev, kms);
+ if (ret)
+ goto cleanup_crtc;
+ }
+
drm_mode_config_reset(drm);
+ dcss_crtc_attach_color_mgmt_properties(crtc);
+
drm_kms_helper_poll_init(drm);
ret = drm_dev_register(drm, 0);
@@ -151,7 +184,8 @@ struct dcss_kms_dev *dcss_kms_attach(struct dcss_dev *dcss)
return kms;
cleanup_crtc:
- drm_bridge_connector_disable_hpd(kms->connector);
+ if (!componentized)
+ drm_bridge_connector_disable_hpd(kms->connector);
drm_kms_helper_poll_fini(drm);
dcss_crtc_deinit(crtc, drm);
@@ -162,16 +196,20 @@ cleanup_mode_config:
return ERR_PTR(ret);
}
-void dcss_kms_detach(struct dcss_kms_dev *kms)
+void dcss_kms_detach(struct dcss_kms_dev *kms, bool componentized)
{
struct drm_device *drm = &kms->base;
+ struct dcss_dev *dcss = drm->dev_private;
drm_dev_unregister(drm);
- drm_bridge_connector_disable_hpd(kms->connector);
+ if (!componentized)
+ drm_bridge_connector_disable_hpd(kms->connector);
drm_kms_helper_poll_fini(drm);
drm_atomic_helper_shutdown(drm);
drm_crtc_vblank_off(&kms->crtc.base);
drm_mode_config_cleanup(drm);
dcss_crtc_deinit(&kms->crtc, drm);
+ if (componentized)
+ component_unbind_all(dcss->dev, drm);
drm->dev_private = NULL;
}
diff --git a/drivers/gpu/drm/imx/dcss/dcss-kms.h b/drivers/gpu/drm/imx/dcss/dcss-kms.h
index dfe5dd99eea3..ded51fe64cb4 100644
--- a/drivers/gpu/drm/imx/dcss/dcss-kms.h
+++ b/drivers/gpu/drm/imx/dcss/dcss-kms.h
@@ -6,23 +6,40 @@
#ifndef _DCSS_KMS_H_
#define _DCSS_KMS_H_
+#include <linux/kernel.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_device.h>
#include <drm/drm_encoder.h>
+#include "dcss-dev.h"
+
struct dcss_plane {
struct drm_plane base;
+ uint64_t dtrc_table_ofs_val;
+ struct drm_property *dtrc_table_ofs_prop;
+
int ch_num;
+
+ enum drm_plane_type type;
+ bool use_dtrc;
};
struct dcss_crtc {
struct drm_crtc base;
- struct drm_crtc_state *state;
-
struct dcss_plane *plane[3];
-
int irq;
+ bool disable_ctxld_kick_irq;
+};
- bool disable_ctxld_kick_irq;
+struct dcss_crtc_state {
+ struct drm_crtc_state base;
+ enum dcss_pixel_pipe_output output_encoding;
+ enum dcss_hdr10_nonlinearity opipe_nl;
+ enum dcss_hdr10_gamut opipe_g;
+ enum dcss_hdr10_pixel_range opipe_pr;
};
struct dcss_kms_dev {
@@ -32,13 +49,27 @@ struct dcss_kms_dev {
struct drm_connector *connector;
};
-struct dcss_kms_dev *dcss_kms_attach(struct dcss_dev *dcss);
-void dcss_kms_detach(struct dcss_kms_dev *kms);
+static inline struct dcss_crtc *to_dcss_crtc(struct drm_crtc *crtc)
+{
+ return container_of(crtc, struct dcss_crtc, base);
+}
+
+static inline struct dcss_crtc_state *
+to_dcss_crtc_state(struct drm_crtc_state *state)
+{
+ return container_of(state, struct dcss_crtc_state, base);
+}
+
+struct dcss_kms_dev *dcss_kms_attach(struct dcss_dev *dcss, bool componetized);
+void dcss_kms_detach(struct dcss_kms_dev *kms, bool componetized);
+int dcss_crtc_setup_opipe(struct drm_device *dev,
+ struct drm_atomic_state *state);
int dcss_crtc_init(struct dcss_crtc *crtc, struct drm_device *drm);
void dcss_crtc_deinit(struct dcss_crtc *crtc, struct drm_device *drm);
struct dcss_plane *dcss_plane_init(struct drm_device *drm,
unsigned int possible_crtcs,
enum drm_plane_type type,
unsigned int zpos);
+void dcss_crtc_attach_color_mgmt_properties(struct dcss_crtc *crtc);
#endif
diff --git a/drivers/gpu/drm/imx/dcss/dcss-plane.c b/drivers/gpu/drm/imx/dcss/dcss-plane.c
index ac45d54acd4e..5de3c2367673 100644
--- a/drivers/gpu/drm/imx/dcss/dcss-plane.c
+++ b/drivers/gpu/drm/imx/dcss/dcss-plane.c
@@ -1,10 +1,12 @@
// SPDX-License-Identifier: GPL-2.0
/*
- * Copyright 2019 NXP.
+ * Copyright 2019-2022 NXP.
*/
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
+#include <linux/dma-buf.h>
+#include <drm/drm_drv.h>
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_gem_atomic_helper.h>
#include <drm/drm_gem_cma_helper.h>
@@ -30,16 +32,36 @@ static const u32 dcss_common_formats[] = {
DRM_FORMAT_ABGR2101010,
DRM_FORMAT_RGBA1010102,
DRM_FORMAT_BGRA1010102,
+
+ /* YUV444 */
+ DRM_FORMAT_AYUV,
+
+ /* YUV422 */
+ DRM_FORMAT_UYVY,
+ DRM_FORMAT_VYUY,
+ DRM_FORMAT_YUYV,
+ DRM_FORMAT_YVYU,
+
+ /* YUV420 */
+ DRM_FORMAT_NV12,
+ DRM_FORMAT_NV21,
+ DRM_FORMAT_NV15,
};
-static const u64 dcss_video_format_modifiers[] = {
+static const u64 dcss_overlay_format_modifiers[] = {
+ DRM_FORMAT_MOD_VSI_G1_TILED,
+ DRM_FORMAT_MOD_VSI_G2_TILED,
+ DRM_FORMAT_MOD_VSI_G2_TILED_COMPRESSED,
+ DRM_FORMAT_MOD_VIVANTE_TILED,
+ DRM_FORMAT_MOD_VIVANTE_SUPER_TILED,
DRM_FORMAT_MOD_LINEAR,
DRM_FORMAT_MOD_INVALID,
};
-static const u64 dcss_graphics_format_modifiers[] = {
+static const u64 dcss_primary_format_modifiers[] = {
DRM_FORMAT_MOD_VIVANTE_TILED,
DRM_FORMAT_MOD_VIVANTE_SUPER_TILED,
+ DRM_FORMAT_MOD_VIVANTE_SUPER_TILED_FC,
DRM_FORMAT_MOD_LINEAR,
DRM_FORMAT_MOD_INVALID,
};
@@ -65,6 +87,36 @@ static void dcss_plane_destroy(struct drm_plane *plane)
kfree(dcss_plane);
}
+static int dcss_plane_atomic_set_property(struct drm_plane *plane,
+ struct drm_plane_state *state,
+ struct drm_property *property,
+ uint64_t val)
+{
+ struct dcss_plane *dcss_plane = to_dcss_plane(plane);
+
+ if (property == dcss_plane->dtrc_table_ofs_prop)
+ dcss_plane->dtrc_table_ofs_val = val;
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+static int dcss_plane_atomic_get_property(struct drm_plane *plane,
+ const struct drm_plane_state *state,
+ struct drm_property *property,
+ uint64_t *val)
+{
+ struct dcss_plane *dcss_plane = to_dcss_plane(plane);
+
+ if (property == dcss_plane->dtrc_table_ofs_prop)
+ *val = dcss_plane->dtrc_table_ofs_val;
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
static bool dcss_plane_format_mod_supported(struct drm_plane *plane,
u32 format,
u64 modifier)
@@ -77,16 +129,36 @@ static bool dcss_plane_format_mod_supported(struct drm_plane *plane,
case DRM_FORMAT_ARGB2101010:
return modifier == DRM_FORMAT_MOD_LINEAR ||
modifier == DRM_FORMAT_MOD_VIVANTE_TILED ||
- modifier == DRM_FORMAT_MOD_VIVANTE_SUPER_TILED;
+ modifier == DRM_FORMAT_MOD_VIVANTE_SUPER_TILED ||
+ modifier == DRM_FORMAT_MOD_VIVANTE_SUPER_TILED_FC;
default:
return modifier == DRM_FORMAT_MOD_LINEAR;
}
break;
case DRM_PLANE_TYPE_OVERLAY:
- return modifier == DRM_FORMAT_MOD_LINEAR;
+ switch (format) {
+ case DRM_FORMAT_NV12:
+ case DRM_FORMAT_NV21:
+ case DRM_FORMAT_NV15:
+ return modifier == DRM_FORMAT_MOD_LINEAR ||
+ modifier == DRM_FORMAT_MOD_VSI_G1_TILED ||
+ modifier == DRM_FORMAT_MOD_VSI_G2_TILED ||
+ modifier == DRM_FORMAT_MOD_VSI_G2_TILED_COMPRESSED;
+ case DRM_FORMAT_ARGB8888:
+ case DRM_FORMAT_XRGB8888:
+ case DRM_FORMAT_ARGB2101010:
+ return modifier == DRM_FORMAT_MOD_LINEAR ||
+ modifier == DRM_FORMAT_MOD_VIVANTE_TILED ||
+ modifier == DRM_FORMAT_MOD_VIVANTE_SUPER_TILED;
+ default:
+ return modifier == DRM_FORMAT_MOD_LINEAR;
+ }
+ break;
default:
return false;
}
+
+ return false;
}
static const struct drm_plane_funcs dcss_plane_funcs = {
@@ -96,6 +168,8 @@ static const struct drm_plane_funcs dcss_plane_funcs = {
.reset = drm_atomic_helper_plane_reset,
.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
+ .atomic_set_property = dcss_plane_atomic_set_property,
+ .atomic_get_property = dcss_plane_atomic_get_property,
.format_mod_supported = dcss_plane_format_mod_supported,
};
@@ -111,7 +185,8 @@ static bool dcss_plane_can_rotate(const struct drm_format_info *format,
DRM_MODE_REFLECT_MASK;
else if (!format->is_yuv &&
(modifier == DRM_FORMAT_MOD_VIVANTE_TILED ||
- modifier == DRM_FORMAT_MOD_VIVANTE_SUPER_TILED))
+ modifier == DRM_FORMAT_MOD_VIVANTE_SUPER_TILED ||
+ modifier == DRM_FORMAT_MOD_VIVANTE_SUPER_TILED_FC))
supported_rotation = DRM_MODE_ROTATE_MASK |
DRM_MODE_REFLECT_MASK;
else if (format->is_yuv && linear_format &&
@@ -119,14 +194,79 @@ static bool dcss_plane_can_rotate(const struct drm_format_info *format,
format->format == DRM_FORMAT_NV21))
supported_rotation = DRM_MODE_ROTATE_0 | DRM_MODE_ROTATE_180 |
DRM_MODE_REFLECT_MASK;
+ else if (format->is_yuv && linear_format &&
+ format->format == DRM_FORMAT_NV15)
+ supported_rotation = DRM_MODE_ROTATE_0 | DRM_MODE_REFLECT_Y;
return !!(rotation & supported_rotation);
}
+static void dcss_plane_get_hdr10_pipe_cfg(struct drm_plane_state *plane_state,
+ struct drm_crtc_state *crtc_state,
+ struct dcss_hdr10_pipe_cfg *ipipe_cfg,
+ struct dcss_hdr10_pipe_cfg *opipe_cfg)
+{
+ struct dcss_crtc_state *dcss_crtc_state = to_dcss_crtc_state(crtc_state);
+ struct drm_framebuffer *fb = plane_state->fb;
+
+ opipe_cfg->is_yuv =
+ dcss_crtc_state->output_encoding != DCSS_PIPE_OUTPUT_RGB;
+ opipe_cfg->g = dcss_crtc_state->opipe_g;
+ opipe_cfg->nl = dcss_crtc_state->opipe_nl;
+ opipe_cfg->pr = dcss_crtc_state->opipe_pr;
+
+ ipipe_cfg->is_yuv = fb->format->is_yuv;
+
+ if (!fb->format->is_yuv) {
+ ipipe_cfg->pr = PR_FULL;
+ if (fb->format->depth == 30) {
+ ipipe_cfg->nl = NL_REC2084;
+ ipipe_cfg->g = G_REC2020;
+ } else {
+ ipipe_cfg->nl = NL_REC709;
+ ipipe_cfg->g = G_REC709;
+ }
+ return;
+ }
+
+ switch (plane_state->color_encoding) {
+ case DRM_COLOR_YCBCR_BT709:
+ ipipe_cfg->nl = NL_REC709;
+ ipipe_cfg->g = G_REC709;
+ break;
+ case DRM_COLOR_YCBCR_BT2020:
+ ipipe_cfg->nl = NL_REC2084;
+ ipipe_cfg->g = G_REC2020;
+ break;
+ default:
+ ipipe_cfg->nl = NL_REC709;
+ ipipe_cfg->g = G_REC709;
+ break;
+ }
+
+ ipipe_cfg->pr = plane_state->color_range == DRM_COLOR_YCBCR_FULL_RANGE ?
+ PR_FULL : PR_LIMITED;
+}
+
+static bool
+dcss_plane_hdr10_pipe_cfg_is_supported(struct drm_plane_state *plane_state,
+ struct drm_crtc_state *crtc_state)
+{
+ struct dcss_dev *dcss = plane_state->plane->dev->dev_private;
+ struct dcss_hdr10_pipe_cfg ipipe_cfg, opipe_cfg;
+
+ dcss_plane_get_hdr10_pipe_cfg(plane_state, crtc_state,
+ &ipipe_cfg, &opipe_cfg);
+
+ return dcss_hdr10_pipe_cfg_is_supported(dcss->hdr10,
+ &ipipe_cfg, &opipe_cfg);
+}
+
static bool dcss_plane_is_source_size_allowed(u16 src_w, u16 src_h, u32 pix_fmt)
{
if (src_w < 64 &&
- (pix_fmt == DRM_FORMAT_NV12 || pix_fmt == DRM_FORMAT_NV21))
+ (pix_fmt == DRM_FORMAT_NV12 || pix_fmt == DRM_FORMAT_NV21 ||
+ pix_fmt == DRM_FORMAT_NV15))
return false;
else if (src_w < 32 &&
(pix_fmt == DRM_FORMAT_UYVY || pix_fmt == DRM_FORMAT_VYUY ||
@@ -136,6 +276,18 @@ static bool dcss_plane_is_source_size_allowed(u16 src_w, u16 src_h, u32 pix_fmt)
return src_w >= 16 && src_h >= 8;
}
+static inline bool dcss_plane_use_dtrc(struct drm_framebuffer *fb,
+ enum drm_plane_type type)
+{
+ u64 pix_format = fb->format->format;
+
+ return !dcss_plane_fb_is_linear(fb) &&
+ type == DRM_PLANE_TYPE_OVERLAY &&
+ (pix_format == DRM_FORMAT_NV12 ||
+ pix_format == DRM_FORMAT_NV21 ||
+ pix_format == DRM_FORMAT_NV15);
+}
+
static int dcss_plane_atomic_check(struct drm_plane *plane,
struct drm_atomic_state *state)
{
@@ -144,7 +296,6 @@ static int dcss_plane_atomic_check(struct drm_plane *plane,
struct dcss_plane *dcss_plane = to_dcss_plane(plane);
struct dcss_dev *dcss = plane->dev->dev_private;
struct drm_framebuffer *fb = new_plane_state->fb;
- bool is_primary_plane = plane->type == DRM_PLANE_TYPE_PRIMARY;
struct drm_gem_cma_object *cma_obj;
struct drm_crtc_state *crtc_state;
int hdisplay, vdisplay;
@@ -174,8 +325,7 @@ static int dcss_plane_atomic_check(struct drm_plane *plane,
&min, &max);
ret = drm_atomic_helper_check_plane_state(new_plane_state, crtc_state,
- min, max, !is_primary_plane,
- false);
+ min, max, true, false);
if (ret)
return ret;
@@ -190,11 +340,9 @@ static int dcss_plane_atomic_check(struct drm_plane *plane,
return -EINVAL;
}
- if ((new_plane_state->crtc_x < 0 || new_plane_state->crtc_y < 0 ||
- new_plane_state->crtc_x + new_plane_state->crtc_w > hdisplay ||
- new_plane_state->crtc_y + new_plane_state->crtc_h > vdisplay) &&
- !dcss_plane_fb_is_linear(fb)) {
- DRM_DEBUG_KMS("requested cropping operation is not allowed!\n");
+ if (!dcss_plane_hdr10_pipe_cfg_is_supported(new_plane_state,
+ crtc_state)) {
+ DRM_DEBUG_KMS("requested hdr10 pipe cfg is not supported!\n");
return -EINVAL;
}
@@ -206,9 +354,100 @@ static int dcss_plane_atomic_check(struct drm_plane *plane,
return -EINVAL;
}
+ if (fb->modifier == DRM_FORMAT_MOD_VSI_G2_TILED_COMPRESSED &&
+ dcss_plane->dtrc_table_ofs_val == 0) {
+ DRM_ERROR_RATELIMITED("No DTRC decompression table offset set, reject plane.\n");
+ return -EINVAL;
+ }
+
+ dcss_plane->use_dtrc = dcss_plane_use_dtrc(fb, plane->type);
+
return 0;
}
+static struct drm_gem_object *dcss_plane_gem_import(struct drm_device *dev,
+ struct dma_buf *dma_buf)
+{
+ struct drm_gem_object *obj;
+
+ if (IS_ERR(dma_buf))
+ return ERR_CAST(dma_buf);
+
+ mutex_lock(&dev->object_name_lock);
+
+ obj = dev->driver->gem_prime_import(dev, dma_buf);
+
+ mutex_unlock(&dev->object_name_lock);
+
+ return obj;
+}
+
+static void dcss_plane_set_primary_base(struct dcss_plane *dcss_plane,
+ u32 baddr)
+{
+ struct drm_plane *plane = &dcss_plane->base;
+ struct dcss_dev *dcss = plane->dev->dev_private;
+ struct drm_plane_state *state = plane->state;
+ struct drm_framebuffer *fb = state->fb;
+ struct drm_gem_cma_object *cma_obj = drm_fb_cma_get_gem_obj(fb, 0);
+ struct dma_buf *dma_buf = cma_obj->base.dma_buf;
+ struct drm_gem_object *gem_obj;
+ dma_addr_t caddr;
+ bool compressed = true;
+ u32 compressed_format = _VIV_CFMT_ARGB8;
+ _VIV_VIDMEM_METADATA *mdata;
+
+ if (dcss_plane_fb_is_linear(fb) ||
+ ((fb->flags & DRM_MODE_FB_MODIFIERS) &&
+ (fb->modifier == DRM_FORMAT_MOD_VIVANTE_TILED ||
+ fb->modifier == DRM_FORMAT_MOD_VIVANTE_SUPER_TILED))) {
+ dcss_dec400d_bypass(dcss->dec400d);
+ return;
+ }
+
+ if (!dma_buf) {
+ caddr = cma_obj->paddr + ALIGN(fb->height, 64) * fb->pitches[0];
+ } else {
+ mdata = dma_buf->priv;
+ if (!mdata || mdata->magic != VIV_VIDMEM_METADATA_MAGIC)
+ return;
+
+ gem_obj = dcss_plane_gem_import(plane->dev, mdata->ts_dma_buf);
+ if (IS_ERR(gem_obj))
+ return;
+
+ caddr = to_drm_gem_cma_obj(gem_obj)->paddr;
+
+ /* release gem_obj */
+ drm_gem_object_put(gem_obj);
+
+ dcss_dec400d_fast_clear_config(dcss->dec400d, mdata->fc_value,
+ mdata->fc_enabled);
+
+ compressed = !!mdata->compressed;
+ compressed_format = mdata->compress_format;
+ }
+
+ dcss_dec400d_read_config(dcss->dec400d, 0, compressed,
+ compressed_format);
+ dcss_dec400d_addr_set(dcss->dec400d, baddr, caddr);
+}
+
+static void dcss_plane_set_dtrc_base(struct dcss_plane *dcss_plane,
+ u32 p1_ba, u32 p2_ba)
+{
+ struct drm_plane *plane = &dcss_plane->base;
+ struct dcss_dev *dcss = plane->dev->dev_private;
+
+ if (!dcss_plane->use_dtrc) {
+ dcss_dtrc_bypass(dcss->dtrc, dcss_plane->ch_num);
+ return;
+ }
+
+ dcss_dtrc_addr_set(dcss->dtrc, dcss_plane->ch_num,
+ p1_ba, p2_ba, dcss_plane->dtrc_table_ofs_val);
+}
+
static void dcss_plane_atomic_set_base(struct dcss_plane *dcss_plane)
{
struct drm_plane *plane = &dcss_plane->base;
@@ -218,26 +457,45 @@ static void dcss_plane_atomic_set_base(struct dcss_plane *dcss_plane)
const struct drm_format_info *format = fb->format;
struct drm_gem_cma_object *cma_obj = drm_fb_cma_get_gem_obj(fb, 0);
unsigned long p1_ba = 0, p2_ba = 0;
+ u16 x1, y1;
+
+ x1 = state->src.x1 >> 16;
+ y1 = state->src.y1 >> 16;
if (!format->is_yuv ||
format->format == DRM_FORMAT_NV12 ||
format->format == DRM_FORMAT_NV21)
p1_ba = cma_obj->paddr + fb->offsets[0] +
- fb->pitches[0] * (state->src.y1 >> 16) +
- format->char_per_block[0] * (state->src.x1 >> 16);
+ fb->pitches[0] * y1 +
+ format->char_per_block[0] * x1;
+ else if (format->format == DRM_FORMAT_NV15)
+ p1_ba = cma_obj->paddr + fb->offsets[0] +
+ fb->pitches[0] * y1 +
+ format->char_per_block[0] * (x1 >> 2);
else if (format->format == DRM_FORMAT_UYVY ||
format->format == DRM_FORMAT_VYUY ||
format->format == DRM_FORMAT_YUYV ||
format->format == DRM_FORMAT_YVYU)
p1_ba = cma_obj->paddr + fb->offsets[0] +
- fb->pitches[0] * (state->src.y1 >> 16) +
- 2 * format->char_per_block[0] * (state->src.x1 >> 17);
+ fb->pitches[0] * y1 +
+ 2 * format->char_per_block[0] * (x1 >> 1);
if (format->format == DRM_FORMAT_NV12 ||
format->format == DRM_FORMAT_NV21)
p2_ba = cma_obj->paddr + fb->offsets[1] +
- (((fb->pitches[1] >> 1) * (state->src.y1 >> 17) +
- (state->src.x1 >> 17)) << 1);
+ (((fb->pitches[1] >> 1) * (y1 >> 1) +
+ (x1 >> 1)) << 1);
+ else if (format->format == DRM_FORMAT_NV15)
+ p2_ba = cma_obj->paddr + fb->offsets[1] +
+ (((fb->pitches[1] >> 1) * (y1 >> 1)) << 1) +
+ format->char_per_block[1] * (x1 >> 2);
+
+ if (plane->type == DRM_PLANE_TYPE_PRIMARY)
+ dcss_plane_set_primary_base(dcss_plane, p1_ba);
+ else
+ dcss_plane_set_dtrc_base(dcss_plane,
+ cma_obj->paddr + fb->offsets[0],
+ cma_obj->paddr + fb->offsets[1]);
dcss_dpr_addr_set(dcss->dpr, dcss_plane->ch_num, p1_ba, p2_ba,
fb->pitches[0]);
@@ -263,6 +521,20 @@ static bool dcss_plane_needs_setup(struct drm_plane_state *state,
state->scaling_filter != old_state->scaling_filter;
}
+static void dcss_plane_setup_hdr10_pipes(struct drm_plane *plane)
+{
+ struct dcss_dev *dcss = plane->dev->dev_private;
+ struct dcss_plane *dcss_plane = to_dcss_plane(plane);
+ struct dcss_hdr10_pipe_cfg ipipe_cfg, opipe_cfg;
+
+ dcss_plane_get_hdr10_pipe_cfg(plane->state,
+ plane->state->crtc->state,
+ &ipipe_cfg, &opipe_cfg);
+
+ dcss_hdr10_setup(dcss->hdr10, dcss_plane->ch_num,
+ &ipipe_cfg, &opipe_cfg);
+}
+
static void dcss_plane_atomic_update(struct drm_plane *plane,
struct drm_atomic_state *state)
{
@@ -287,8 +559,12 @@ static void dcss_plane_atomic_update(struct drm_plane *plane,
modifiers_present = !!(fb->flags & DRM_MODE_FB_MODIFIERS);
if (old_state->fb && !drm_atomic_crtc_needs_modeset(crtc_state) &&
- !dcss_plane_needs_setup(new_state, old_state)) {
+ !dcss_plane_needs_setup(new_state, old_state) &&
+ !dcss_dtg_global_alpha_changed(dcss->dtg, dcss_plane->ch_num,
+ new_state->alpha >> 8)) {
dcss_plane_atomic_set_base(dcss_plane);
+ if (plane->type == DRM_PLANE_TYPE_PRIMARY)
+ dcss_dec400d_shadow_trig(dcss->dec400d);
return;
}
@@ -303,15 +579,21 @@ static void dcss_plane_atomic_update(struct drm_plane *plane,
dst_w = drm_rect_width(&dst);
dst_h = drm_rect_height(&dst);
- if (plane->type == DRM_PLANE_TYPE_OVERLAY &&
- modifiers_present && fb->modifier == DRM_FORMAT_MOD_LINEAR)
- modifiers_present = false;
-
dcss_dpr_format_set(dcss->dpr, dcss_plane->ch_num,
new_state->fb->format,
modifiers_present ? fb->modifier :
DRM_FORMAT_MOD_LINEAR);
- dcss_dpr_set_res(dcss->dpr, dcss_plane->ch_num, src_w, src_h);
+
+ if (dcss_plane->use_dtrc) {
+ u32 dtrc_w, dtrc_h;
+
+ dcss_dtrc_set_res(dcss->dtrc, dcss_plane->ch_num, new_state,
+ &dtrc_w, &dtrc_h);
+ dcss_dpr_set_res(dcss->dpr, dcss_plane->ch_num, dtrc_w, dtrc_h);
+ } else {
+ dcss_dpr_set_res(dcss->dpr, dcss_plane->ch_num, src_w, src_h);
+ }
+
dcss_dpr_set_rotation(dcss->dpr, dcss_plane->ch_num,
new_state->rotation);
@@ -330,11 +612,18 @@ static void dcss_plane_atomic_update(struct drm_plane *plane,
dst_w, dst_h,
drm_mode_vrefresh(&crtc_state->mode));
+ dcss_plane_setup_hdr10_pipes(plane);
+
dcss_dtg_plane_pos_set(dcss->dtg, dcss_plane->ch_num,
dst.x1, dst.y1, dst_w, dst_h);
dcss_dtg_plane_alpha_set(dcss->dtg, dcss_plane->ch_num,
fb->format, new_state->alpha >> 8);
+ if (plane->type == DRM_PLANE_TYPE_PRIMARY)
+ dcss_dec400d_enable(dcss->dec400d);
+ else if (dcss_plane->use_dtrc)
+ dcss_dtrc_enable(dcss->dtrc, dcss_plane->ch_num, true);
+
if (!dcss_plane->ch_num && (new_state->alpha >> 8) == 0)
enable = false;
@@ -354,6 +643,8 @@ static void dcss_plane_atomic_disable(struct drm_plane *plane,
struct dcss_plane *dcss_plane = to_dcss_plane(plane);
struct dcss_dev *dcss = plane->dev->dev_private;
+ if (dcss_plane->use_dtrc)
+ dcss_dtrc_enable(dcss->dtrc, dcss_plane->ch_num, false);
dcss_dpr_enable(dcss->dpr, dcss_plane->ch_num, false);
dcss_scaler_ch_enable(dcss->scaler, dcss_plane->ch_num, false);
dcss_dtg_plane_pos_set(dcss->dtg, dcss_plane->ch_num, 0, 0, 0, 0);
@@ -372,7 +663,8 @@ struct dcss_plane *dcss_plane_init(struct drm_device *drm,
unsigned int zpos)
{
struct dcss_plane *dcss_plane;
- const u64 *format_modifiers = dcss_video_format_modifiers;
+ const u64 *format_modifiers = dcss_overlay_format_modifiers;
+ struct drm_property *prop;
int ret;
if (zpos > 2)
@@ -385,7 +677,7 @@ struct dcss_plane *dcss_plane_init(struct drm_device *drm,
}
if (type == DRM_PLANE_TYPE_PRIMARY)
- format_modifiers = dcss_graphics_format_modifiers;
+ format_modifiers = dcss_primary_format_modifiers;
ret = drm_universal_plane_init(drm, &dcss_plane->base, possible_crtcs,
&dcss_plane_funcs, dcss_common_formats,
@@ -416,7 +708,21 @@ struct dcss_plane *dcss_plane_init(struct drm_device *drm,
DRM_MODE_REFLECT_X |
DRM_MODE_REFLECT_Y);
- dcss_plane->ch_num = zpos;
+ dcss_plane->ch_num = 2 - zpos;
+ dcss_plane->type = type;
+
+ if (type == DRM_PLANE_TYPE_PRIMARY)
+ return dcss_plane;
+
+ prop = drm_property_create_range(drm, 0, "dtrc_table_ofs",
+ 0, ULLONG_MAX);
+ if (!prop) {
+ DRM_ERROR("cannot create dtrc_table_ofs property\n");
+ return ERR_PTR(-ENOMEM);
+ }
+
+ dcss_plane->dtrc_table_ofs_prop = prop;
+ drm_object_attach_property(&dcss_plane->base.base, prop, 0);
return dcss_plane;
}
diff --git a/drivers/gpu/drm/imx/dcss/dcss-rdsrc.c b/drivers/gpu/drm/imx/dcss/dcss-rdsrc.c
new file mode 100644
index 000000000000..ef695e584acf
--- /dev/null
+++ b/drivers/gpu/drm/imx/dcss/dcss-rdsrc.c
@@ -0,0 +1,119 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2019 NXP.
+ */
+
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/dma-mapping.h>
+#include <linux/seq_file.h>
+
+#include "dcss-dev.h"
+
+#define DCSS_RDSRC_CTRL_STATUS 0x00
+#define RDSRC_RD_ERR BIT(31)
+#define RDSRC_FRAME_COMP BIT(30)
+#define RDSRC_FIFO_SIZE_POS 16
+#define RDSRC_FIFO_SIZE_MASK GENMASK(22, 16)
+#define RDSRC_RD_ERR_EN BIT(15)
+#define RDSRC_FRAME_COMP_EN BIT(14)
+#define RDSRC_P_SIZE_POS 7
+#define RDSRC_P_SIZE_MASK GENMASK(9, 7)
+#define RDSRC_T_SIZE_POS 5
+#define RDSRC_T_SIZE_MASK GENMASK(6, 5)
+#define RDSRC_BPP_POS 2
+#define RDSRC_BPP_MASK GENMASK(4, 2)
+#define RDSRC_ENABLE BIT(0)
+#define DCSS_RDSRC_BASE_ADDR 0x10
+#define DCSS_RDSRC_PITCH 0x14
+#define DCSS_RDSRC_WIDTH 0x18
+#define DCSS_RDSRC_HEIGHT 0x1C
+
+struct dcss_rdsrc {
+ struct device *dev;
+
+ void __iomem *base_reg;
+ u32 base_ofs;
+
+ struct dcss_ctxld *ctxld;
+ u32 ctx_id;
+
+ u32 buf_addr;
+
+ u32 ctrl_status;
+};
+
+static void dcss_rdsrc_write(struct dcss_rdsrc *rdsrc, u32 val, u32 ofs)
+{
+ dcss_ctxld_write(rdsrc->ctxld, rdsrc->ctx_id, val,
+ rdsrc->base_ofs + ofs);
+}
+
+int dcss_rdsrc_init(struct dcss_dev *dcss, unsigned long rdsrc_base)
+{
+ struct dcss_rdsrc *rdsrc;
+
+ rdsrc = devm_kzalloc(dcss->dev, sizeof(*rdsrc), GFP_KERNEL);
+ if (!rdsrc)
+ return -ENOMEM;
+
+ rdsrc->base_reg = devm_ioremap(dcss->dev, rdsrc_base, SZ_4K);
+ if (!rdsrc->base_reg) {
+ dev_err(dcss->dev, "rdsrc: unable to remap base\n");
+ devm_kfree(dcss->dev, rdsrc);
+ return -ENOMEM;
+ }
+
+ dcss->rdsrc = rdsrc;
+
+ rdsrc->dev = dcss->dev;
+ rdsrc->base_ofs = rdsrc_base;
+ rdsrc->ctxld = dcss->ctxld;
+ rdsrc->ctx_id = CTX_SB_HP;
+
+ return 0;
+}
+
+void dcss_rdsrc_exit(struct dcss_rdsrc *rdsrc)
+{
+ devm_iounmap(rdsrc->dev, rdsrc->base_reg);
+ devm_kfree(rdsrc->dev, rdsrc);
+}
+
+void dcss_rdsrc_setup(struct dcss_rdsrc *rdsrc, u32 pix_format, u32 dst_xres,
+ u32 dst_yres, u32 base_addr)
+{
+ u32 buf_size, pitch, bpp;
+
+ /* since the scaler output is YUV444, the RDSRC output has to match */
+ bpp = 4;
+
+ rdsrc->ctrl_status = FIFO_512 << RDSRC_FIFO_SIZE_POS;
+ rdsrc->ctrl_status |= PSIZE_256 << RDSRC_P_SIZE_POS;
+ rdsrc->ctrl_status |= TSIZE_256 << RDSRC_T_SIZE_POS;
+ rdsrc->ctrl_status |= BPP_32_10BIT_OUTPUT << RDSRC_BPP_POS;
+
+ buf_size = dst_xres * dst_yres * bpp;
+ pitch = dst_xres * bpp;
+
+ rdsrc->buf_addr = base_addr;
+
+ dcss_rdsrc_write(rdsrc, rdsrc->buf_addr, DCSS_RDSRC_BASE_ADDR);
+ dcss_rdsrc_write(rdsrc, pitch, DCSS_RDSRC_PITCH);
+ dcss_rdsrc_write(rdsrc, dst_xres, DCSS_RDSRC_WIDTH);
+ dcss_rdsrc_write(rdsrc, dst_yres, DCSS_RDSRC_HEIGHT);
+}
+
+void dcss_rdsrc_enable(struct dcss_rdsrc *rdsrc)
+{
+ dcss_rdsrc_write(rdsrc, rdsrc->ctrl_status, DCSS_RDSRC_CTRL_STATUS);
+}
+
+void dcss_rdsrc_disable(struct dcss_rdsrc *rdsrc)
+{
+ /* RDSRC is turned off by setting the width and height to 0 */
+ dcss_rdsrc_write(rdsrc, 0, DCSS_RDSRC_WIDTH);
+ dcss_rdsrc_write(rdsrc, 0, DCSS_RDSRC_HEIGHT);
+
+ dcss_rdsrc_write(rdsrc, rdsrc->ctrl_status, DCSS_RDSRC_CTRL_STATUS);
+}
diff --git a/drivers/gpu/drm/imx/dcss/dcss-scaler.c b/drivers/gpu/drm/imx/dcss/dcss-scaler.c
index 47852b9dd5ea..8eaf42eae9cf 100644
--- a/drivers/gpu/drm/imx/dcss/dcss-scaler.c
+++ b/drivers/gpu/drm/imx/dcss/dcss-scaler.c
@@ -79,6 +79,7 @@ struct dcss_scaler_ch {
u32 c_hstart;
bool use_nn_interpolation;
+ int ch_num;
};
struct dcss_scaler {
@@ -88,6 +89,10 @@ struct dcss_scaler {
u32 ctx_id;
struct dcss_scaler_ch ch[3];
+
+ struct dcss_wrscl *wrscl;
+ struct dcss_rdsrc *rdsrc;
+ int ch_using_wrscl;
};
/* scaler coefficients generator */
@@ -309,6 +314,7 @@ static int dcss_scaler_ch_init_all(struct dcss_scaler *scl,
}
ch->scl = scl;
+ ch->ch_num = i;
}
return 0;
@@ -326,6 +332,9 @@ int dcss_scaler_init(struct dcss_dev *dcss, unsigned long scaler_base)
scaler->dev = dcss->dev;
scaler->ctxld = dcss->ctxld;
scaler->ctx_id = CTX_SB_HP;
+ scaler->wrscl = dcss->wrscl;
+ scaler->rdsrc = dcss->rdsrc;
+ scaler->ch_using_wrscl = -1;
if (dcss_scaler_ch_init_all(scaler, scaler_base)) {
int i;
@@ -364,7 +373,19 @@ void dcss_scaler_ch_enable(struct dcss_scaler *scl, int ch_num, bool en)
struct dcss_scaler_ch *ch = &scl->ch[ch_num];
u32 scaler_ctrl;
- scaler_ctrl = en ? SCALER_EN | REPEAT_EN : 0;
+ if (scl->ch_using_wrscl == ch_num) {
+ if (en) {
+ scaler_ctrl = SCALE2MEM_EN | MEM2OFIFO_EN | REPEAT_EN;
+ } else {
+ dcss_wrscl_disable(scl->wrscl);
+ dcss_rdsrc_disable(scl->rdsrc);
+
+ scl->ch_using_wrscl = -1;
+ scaler_ctrl = 0;
+ }
+ } else {
+ scaler_ctrl = en ? SCALER_EN | REPEAT_EN : 0;
+ }
if (en)
dcss_scaler_write(ch, ch->sdata_ctrl, DCSS_SCALER_SDATA_CTRL);
@@ -445,7 +466,8 @@ static void dcss_scaler_res_set(struct dcss_scaler_ch *ch,
csrc_xres >>= 1;
src_is_444 = false;
} else if (pix_format == DRM_FORMAT_NV12 ||
- pix_format == DRM_FORMAT_NV21) {
+ pix_format == DRM_FORMAT_NV21 ||
+ pix_format == DRM_FORMAT_NV15) {
csrc_xres >>= 1;
csrc_yres >>= 1;
src_is_444 = false;
@@ -486,7 +508,11 @@ static const struct dcss_scaler_factors dcss_scaler_factors[] = {
{3, 8}, {5, 8}, {5, 8},
};
-static void dcss_scaler_fractions_set(struct dcss_scaler_ch *ch,
+static const struct dcss_scaler_factors dcss_scaler_wrscl_factors[] = {
+ {5, 8}, {7, 8}, {7, 8},
+};
+
+static bool dcss_scaler_fractions_set(struct dcss_scaler_ch *ch,
int src_xres, int src_yres,
int dst_xres, int dst_yres,
u32 src_format, u32 dst_format,
@@ -495,6 +521,7 @@ static void dcss_scaler_fractions_set(struct dcss_scaler_ch *ch,
int src_c_xres, src_c_yres, dst_c_xres, dst_c_yres;
u32 l_vinc, l_hinc, c_vinc, c_hinc;
u32 c_vstart, c_hstart;
+ u8 upscale_factor, downscale_factor;
src_c_xres = src_xres;
src_c_yres = src_yres;
@@ -571,13 +598,27 @@ static void dcss_scaler_fractions_set(struct dcss_scaler_ch *ch,
dcss_scaler_write(ch, c_hstart, DCSS_SCALER_H_CHR_START);
dcss_scaler_write(ch, c_hinc, DCSS_SCALER_H_CHR_INC);
+
+ downscale_factor = dcss_scaler_factors[ch->ch_num].downscale;
+ upscale_factor = dcss_scaler_factors[ch->ch_num].upscale;
+
+ /* return if WR_SCL/RD_SRC is needed to scale */
+ return l_vinc > downscale_fp(downscale_factor, 13) ||
+ l_vinc < upscale_fp(upscale_factor, 13) ||
+ l_hinc > downscale_fp(downscale_factor, 13) ||
+ l_hinc < upscale_fp(upscale_factor, 13);
}
int dcss_scaler_get_min_max_ratios(struct dcss_scaler *scl, int ch_num,
int *min, int *max)
{
- *min = upscale_fp(dcss_scaler_factors[ch_num].upscale, 16);
- *max = downscale_fp(dcss_scaler_factors[ch_num].downscale, 16);
+ const struct dcss_scaler_factors *factors_map = dcss_scaler_factors;
+
+ if (scl->ch_using_wrscl == -1 || scl->ch_using_wrscl == ch_num)
+ factors_map = dcss_scaler_wrscl_factors;
+
+ *min = upscale_fp(factors_map[ch_num].upscale, 16);
+ *max = downscale_fp(factors_map[ch_num].downscale, 16);
return 0;
}
@@ -780,6 +821,43 @@ void dcss_scaler_set_filter(struct dcss_scaler *scl, int ch_num,
ch->use_nn_interpolation = scaling_filter == DRM_SCALING_FILTER_NEAREST_NEIGHBOR;
}
+static void dcss_scaler_setup_path(struct dcss_scaler_ch *ch,
+ u32 pix_format, int dst_xres,
+ int dst_yres, u32 vrefresh_hz,
+ bool wrscl_needed)
+{
+ struct dcss_scaler *scl = ch->scl;
+ u32 base_addr;
+
+ /* nothing to do if WRSCL path is needed but it's already used */
+ if (wrscl_needed && scl->ch_using_wrscl != -1 &&
+ scl->ch_using_wrscl != ch->ch_num)
+ return;
+
+ if (!wrscl_needed) {
+ /* Channel has finished using WRSCL. Release WRSCL/RDSRC. */
+ if (scl->ch_using_wrscl == ch->ch_num) {
+ dcss_wrscl_disable(scl->wrscl);
+ dcss_rdsrc_disable(scl->rdsrc);
+
+ scl->ch_using_wrscl = -1;
+ }
+
+ return;
+ }
+
+ base_addr = dcss_wrscl_setup(scl->wrscl, pix_format, vrefresh_hz,
+ dst_xres, dst_yres);
+
+ dcss_rdsrc_setup(scl->rdsrc, pix_format, dst_xres, dst_yres,
+ base_addr);
+
+ dcss_wrscl_enable(scl->wrscl);
+ dcss_rdsrc_enable(scl->rdsrc);
+
+ scl->ch_using_wrscl = ch->ch_num;
+}
+
void dcss_scaler_setup(struct dcss_scaler *scl, int ch_num,
const struct drm_format_info *format,
int src_xres, int src_yres, int dst_xres, int dst_yres,
@@ -792,12 +870,14 @@ void dcss_scaler_setup(struct dcss_scaler *scl, int ch_num,
enum buffer_format src_format = BUF_FMT_ARGB8888_YUV444;
enum buffer_format dst_format = BUF_FMT_ARGB8888_YUV444;
u32 pix_format = format->format;
+ bool use_wrscl;
if (format->is_yuv) {
dcss_scaler_yuv_enable(ch, true);
if (pix_format == DRM_FORMAT_NV12 ||
- pix_format == DRM_FORMAT_NV21) {
+ pix_format == DRM_FORMAT_NV21 ||
+ pix_format == DRM_FORMAT_NV15) {
rtr_8line_en = true;
src_format = BUF_FMT_YUV420;
} else if (pix_format == DRM_FORMAT_UYVY ||
@@ -808,15 +888,18 @@ void dcss_scaler_setup(struct dcss_scaler *scl, int ch_num,
}
use_5_taps = !rtr_8line_en;
+
+ if (pix_format == DRM_FORMAT_NV15)
+ pixel_depth = 30;
} else {
dcss_scaler_yuv_enable(ch, false);
pixel_depth = format->depth;
}
- dcss_scaler_fractions_set(ch, src_xres, src_yres, dst_xres,
- dst_yres, src_format, dst_format,
- PSC_LOC_HORZ_0_VERT_1_OVER_4);
+ use_wrscl = dcss_scaler_fractions_set(ch, src_xres, src_yres, dst_xres,
+ dst_yres, src_format, dst_format,
+ PSC_LOC_HORZ_0_VERT_1_OVER_4);
if (format->is_yuv)
dcss_scaler_yuv_coef_set(ch, src_format, dst_format,
@@ -832,6 +915,9 @@ void dcss_scaler_setup(struct dcss_scaler *scl, int ch_num,
dcss_scaler_format_set(ch, src_format, dst_format);
dcss_scaler_res_set(ch, src_xres, src_yres, dst_xres, dst_yres,
pix_format, dst_format);
+
+ dcss_scaler_setup_path(ch, pix_format, dst_xres,
+ dst_yres, vrefresh_hz, use_wrscl);
}
/* This function will be called from interrupt context. */
diff --git a/drivers/gpu/drm/imx/dcss/dcss-ss.c b/drivers/gpu/drm/imx/dcss/dcss-ss.c
index 8ddf08da911b..79061d4d51b4 100644
--- a/drivers/gpu/drm/imx/dcss/dcss-ss.c
+++ b/drivers/gpu/drm/imx/dcss/dcss-ss.c
@@ -115,12 +115,29 @@ void dcss_ss_exit(struct dcss_ss *ss)
kfree(ss);
}
-void dcss_ss_subsam_set(struct dcss_ss *ss)
+void dcss_ss_subsam_set(struct dcss_ss *ss,
+ enum dcss_pixel_pipe_output output_encoding)
{
- dcss_ss_write(ss, 0x41614161, DCSS_SS_COEFF);
- dcss_ss_write(ss, 0, DCSS_SS_MODE);
- dcss_ss_write(ss, 0x03ff0000, DCSS_SS_CLIP_CB);
- dcss_ss_write(ss, 0x03ff0000, DCSS_SS_CLIP_CR);
+ u32 ss_coeff = 0x41614161;
+ u32 ss_mode = 0;
+ u32 ss_clip = 0x03ff0000;
+
+ if (output_encoding == DCSS_PIPE_OUTPUT_YUV420) {
+ ss_coeff = 0x21612161;
+ ss_mode = 2;
+ ss_clip = 0x03c00040;
+ } else if (output_encoding == DCSS_PIPE_OUTPUT_YUV422) {
+ ss_coeff = 0x33a333a3;
+ ss_mode = 1;
+ ss_clip = 0x03c00040;
+ } else if (output_encoding == DCSS_PIPE_OUTPUT_YUV444) {
+ ss_clip = 0x03c00040;
+ }
+
+ dcss_ss_write(ss, ss_coeff, DCSS_SS_COEFF);
+ dcss_ss_write(ss, ss_mode, DCSS_SS_MODE);
+ dcss_ss_write(ss, ss_clip, DCSS_SS_CLIP_CB);
+ dcss_ss_write(ss, ss_clip, DCSS_SS_CLIP_CR);
}
void dcss_ss_sync_set(struct dcss_ss *ss, struct videomode *vm,
diff --git a/drivers/gpu/drm/imx/dcss/dcss-wrscl.c b/drivers/gpu/drm/imx/dcss/dcss-wrscl.c
new file mode 100644
index 000000000000..8228f8f46cae
--- /dev/null
+++ b/drivers/gpu/drm/imx/dcss/dcss-wrscl.c
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2019 NXP.
+ */
+
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/dma-mapping.h>
+#include <linux/seq_file.h>
+
+#include "dcss-dev.h"
+
+#define DCSS_WRSCL_CTRL_STATUS 0x00
+#define WRSCL_ERR BIT(31)
+#define WRSCL_ERR_EN BIT(30)
+#define WRSCL_FRAME_COMP BIT(29)
+#define WRSCL_FRAME_COMP_EN BIT(28)
+#define WRSCL_FIFO_SIZE_POS 18
+#define WRSCL_FIFO_SIZE_MASK GENMAK(24, 18)
+#define WRSCL_P_FREQ_POS 10
+#define WRSCL_P_FREQ_MASK GENMASK(17, 10)
+#define WRSCL_P_SIZE_POS 7
+#define WRSCL_P_SIZE_MASK GENMASK(9, 7)
+#define WRSCL_T_SIZE_POS 5
+#define WRSCL_T_SIZE_MASK GENMASK(6, 5)
+#define WRSCL_BPP_POS 2
+#define WRSCL_BPP_MASK GENMASK(4, 2)
+#define WRSCL_REPEAT BIT(1)
+#define WRSCL_ENABLE BIT(0)
+#define DCSS_WRSCL_BASE_ADDR 0x10
+#define DCSS_WRSCL_PITCH 0x14
+
+struct dcss_wrscl {
+ struct device *dev;
+
+ void __iomem *base_reg;
+ u32 base_ofs;
+
+ struct dcss_ctxld *ctxld;
+ u32 ctx_id;
+
+ u32 buf_size;
+ u32 buf_addr;
+ void *buf_vaddr;
+
+ struct clk *bclk;
+
+ u32 ctrl_status;
+};
+
+static void dcss_wrscl_write(struct dcss_wrscl *wrscl, u32 val, u32 ofs)
+{
+ dcss_ctxld_write(wrscl->ctxld, wrscl->ctx_id,
+ val, wrscl->base_ofs + ofs);
+}
+
+int dcss_wrscl_init(struct dcss_dev *dcss, unsigned long wrscl_base)
+{
+ struct dcss_wrscl *wrscl;
+
+ wrscl = devm_kzalloc(dcss->dev, sizeof(*wrscl), GFP_KERNEL);
+ if (!wrscl)
+ return -ENOMEM;
+
+ wrscl->base_reg = devm_ioremap(dcss->dev, wrscl_base, SZ_4K);
+ if (!wrscl->base_reg) {
+ dev_err(dcss->dev, "wrscl: unable to remap base\n");
+ devm_kfree(dcss->dev, wrscl);
+ return -ENOMEM;
+ }
+
+ dcss->wrscl = wrscl;
+
+ wrscl->dev = dcss->dev;
+ wrscl->base_ofs = wrscl_base;
+ wrscl->ctxld = dcss->ctxld;
+ wrscl->ctx_id = CTX_SB_HP;
+ wrscl->bclk = dcss->axi_clk;
+
+ return 0;
+}
+
+void dcss_wrscl_exit(struct dcss_wrscl *wrscl)
+{
+ devm_iounmap(wrscl->dev, wrscl->base_reg);
+ devm_kfree(wrscl->dev, wrscl);
+}
+
+static const u16 dcss_wrscl_psize_map[] = {64, 128, 256, 512, 1024, 2048, 4096};
+
+u32 dcss_wrscl_setup(struct dcss_wrscl *wrscl, u32 pix_format, u32 vrefresh_hz,
+ u32 dst_xres, u32 dst_yres)
+{
+ u32 pitch, p_size, p_freq, bpp;
+ dma_addr_t dma_handle;
+ u32 bclk_rate = clk_get_rate(wrscl->bclk);
+
+ /* we'd better release the old buffer */
+ if (wrscl->buf_addr)
+ dmam_free_coherent(wrscl->dev, wrscl->buf_size,
+ wrscl->buf_vaddr, wrscl->buf_addr);
+
+ p_size = PSIZE_256;
+
+ /* scaler output is YUV444 */
+ bpp = 4;
+
+ /* spread the load over the entire frame */
+ p_freq = ((u64)bclk_rate * dcss_wrscl_psize_map[p_size]) /
+ ((u64)dst_xres * dst_yres * vrefresh_hz * bpp * 8);
+
+ /* choose a slightly smaller p_freq */
+ p_freq = p_freq - 3 > 255 ? 255 : p_freq - 3;
+
+ wrscl->ctrl_status = FIFO_512 << WRSCL_FIFO_SIZE_POS;
+ wrscl->ctrl_status |= p_size << WRSCL_P_SIZE_POS;
+ wrscl->ctrl_status |= TSIZE_256 << WRSCL_T_SIZE_POS;
+ wrscl->ctrl_status |= BPP_32_10BIT_OUTPUT << WRSCL_BPP_POS;
+ wrscl->ctrl_status |= p_freq << WRSCL_P_FREQ_POS;
+
+ wrscl->buf_size = dst_xres * dst_yres * bpp;
+ pitch = dst_xres * bpp;
+
+ wrscl->buf_vaddr = dmam_alloc_coherent(wrscl->dev, wrscl->buf_size,
+ &dma_handle, GFP_KERNEL);
+ if (!wrscl->buf_vaddr) {
+ dev_err(wrscl->dev, "wrscl: cannot alloc buf mem\n");
+ return 0;
+ }
+
+ wrscl->buf_addr = dma_handle;
+
+ dcss_wrscl_write(wrscl, wrscl->buf_addr, DCSS_WRSCL_BASE_ADDR);
+ dcss_wrscl_write(wrscl, pitch, DCSS_WRSCL_PITCH);
+
+ return wrscl->buf_addr;
+}
+
+void dcss_wrscl_enable(struct dcss_wrscl *wrscl)
+{
+ wrscl->ctrl_status |= WRSCL_ENABLE | WRSCL_REPEAT;
+
+ dcss_wrscl_write(wrscl, wrscl->ctrl_status, DCSS_WRSCL_CTRL_STATUS);
+}
+
+void dcss_wrscl_disable(struct dcss_wrscl *wrscl)
+{
+ wrscl->ctrl_status &= ~(WRSCL_ENABLE | WRSCL_REPEAT);
+
+ dcss_wrscl_write(wrscl, wrscl->ctrl_status, DCSS_WRSCL_CTRL_STATUS);
+
+ if (wrscl->buf_addr) {
+ dmam_free_coherent(wrscl->dev, wrscl->buf_size,
+ wrscl->buf_vaddr, wrscl->buf_addr);
+ wrscl->buf_addr = 0;
+ }
+}
diff --git a/drivers/gpu/drm/imx/dpu/Kconfig b/drivers/gpu/drm/imx/dpu/Kconfig
new file mode 100644
index 000000000000..ad480cdbe503
--- /dev/null
+++ b/drivers/gpu/drm/imx/dpu/Kconfig
@@ -0,0 +1,6 @@
+config DRM_IMX_DPU
+ tristate "Freescale i.MX DPU DRM support"
+ depends on DRM_IMX
+ depends on IMX_DPU_CORE
+ default y if DRM_IMX=y
+ default m if DRM_IMX=m
diff --git a/drivers/gpu/drm/imx/dpu/Makefile b/drivers/gpu/drm/imx/dpu/Makefile
new file mode 100644
index 000000000000..22ff7e916b6f
--- /dev/null
+++ b/drivers/gpu/drm/imx/dpu/Makefile
@@ -0,0 +1,8 @@
+ccflags-y += -I $(srctree)/$(src)/../
+
+imx-dpu-crtc-objs := dpu-crtc.o dpu-kms.o dpu-plane.o
+imx-dpu-crtc-$(CONFIG_DEBUG_FS) := dpu-crc.o
+obj-$(CONFIG_DRM_IMX_DPU) += imx-dpu-crtc.o
+
+imx-dpu-render-objs := dpu-blit.o
+obj-$(CONFIG_DRM_IMX_DPU) += imx-dpu-render.o
diff --git a/drivers/gpu/drm/imx/dpu/dpu-blit.c b/drivers/gpu/drm/imx/dpu/dpu-blit.c
new file mode 100644
index 000000000000..35d8429a400f
--- /dev/null
+++ b/drivers/gpu/drm/imx/dpu/dpu-blit.c
@@ -0,0 +1,346 @@
+/*
+ * Copyright 2017,2021-2022 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <drm/drm_vblank.h>
+#include <drm/drm_print.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_ioctl.h>
+#include <drm/imx_drm.h>
+#include <linux/component.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/export.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <video/dpu.h>
+
+#include "imx-drm.h"
+
+struct imx_drm_dpu_bliteng {
+ struct dpu_bliteng *dpu_be;
+ struct list_head list;
+};
+
+static DEFINE_MUTEX(imx_drm_dpu_bliteng_lock);
+static LIST_HEAD(imx_drm_dpu_bliteng_list);
+
+static int imx_dpu_num;
+
+int dpu_be_get(struct dpu_bliteng *dpu_be);
+void dpu_be_put(struct dpu_bliteng *dpu_be);
+s32 dpu_bliteng_get_id(struct dpu_bliteng *dpu_be);
+void dpu_be_configure_prefetch(struct dpu_bliteng *dpu_be,
+ u32 width, u32 height,
+ u32 x_offset, u32 y_offset,
+ u32 stride, u32 format, u64 modifier,
+ u64 baddr, u64 uv_addr);
+u32 *dpu_bliteng_get_cmd_list(struct dpu_bliteng *dpu_be);
+void dpu_be_wait(struct dpu_bliteng *dpu_be);
+int dpu_bliteng_get_empty_instance(struct dpu_bliteng **dpu_be,
+ struct device *dev);
+void dpu_bliteng_set_id(struct dpu_bliteng *dpu_be, int id);
+void dpu_bliteng_set_dev(struct dpu_bliteng *dpu_be, struct device *dev);
+int dpu_bliteng_init(struct dpu_bliteng *dpu_bliteng);
+void dpu_bliteng_fini(struct dpu_bliteng *dpu_bliteng);
+int dpu_be_blit(struct dpu_bliteng *dpu_be,
+ u32 *cmdlist, u32 cmdnum);
+int dpu_be_get_fence(struct dpu_bliteng *dpu_be);
+int dpu_be_set_fence(struct dpu_bliteng *dpu_be, int fd);
+
+static struct imx_drm_dpu_bliteng *imx_drm_dpu_bliteng_find_by_id(s32 id)
+{
+ struct imx_drm_dpu_bliteng *bliteng;
+
+ mutex_lock(&imx_drm_dpu_bliteng_lock);
+
+ list_for_each_entry(bliteng, &imx_drm_dpu_bliteng_list, list) {
+ if (id == dpu_bliteng_get_id(bliteng->dpu_be)) {
+ mutex_unlock(&imx_drm_dpu_bliteng_lock);
+ return bliteng;
+ }
+ }
+
+ mutex_unlock(&imx_drm_dpu_bliteng_lock);
+
+ return NULL;
+}
+
+static int imx_drm_dpu_set_cmdlist_ioctl(struct drm_device *drm_dev, void *data,
+ struct drm_file *file)
+{
+ struct drm_imx_dpu_set_cmdlist *req;
+ struct imx_drm_dpu_bliteng *bliteng;
+ struct dpu_bliteng *dpu_be;
+ u32 cmd_nr, *cmd, *cmd_list;
+ void *user_data;
+ s32 id = 0;
+ struct drm_imx_dpu_frame_info frame_info;
+ int ret;
+
+ req = data;
+ user_data = (void *)(unsigned long)req->user_data;
+ if (copy_from_user(&id, (void __user *)user_data,
+ sizeof(id))) {
+ return -EFAULT;
+ }
+
+ if (id != 0 && id != 1)
+ return -EINVAL;
+
+ user_data += sizeof(id);
+ if (copy_from_user(&frame_info, (void __user *)user_data,
+ sizeof(frame_info))) {
+ return -EFAULT;
+ }
+
+ bliteng = imx_drm_dpu_bliteng_find_by_id(id);
+ if (!bliteng) {
+ DRM_ERROR("Failed to get dpu_bliteng\n");
+ return -ENODEV;
+ }
+
+ dpu_be = bliteng->dpu_be;
+
+ ret = dpu_be_get(dpu_be);
+
+ cmd_nr = req->cmd_nr;
+ cmd = (u32 *)(unsigned long)req->cmd;
+ cmd_list = dpu_bliteng_get_cmd_list(dpu_be);
+
+ if (copy_from_user(cmd_list, (void __user *)cmd,
+ sizeof(*cmd) * cmd_nr)) {
+ ret = -EFAULT;
+ goto err;
+ }
+
+ dpu_be_configure_prefetch(dpu_be, frame_info.width, frame_info.height,
+ frame_info.x_offset, frame_info.y_offset,
+ frame_info.stride, frame_info.format,
+ frame_info.modifier, frame_info.baddr,
+ frame_info.uv_addr);
+
+ ret = dpu_be_blit(dpu_be, cmd_list, cmd_nr);
+
+err:
+ dpu_be_put(dpu_be);
+
+ return ret;
+}
+
+static int imx_drm_dpu_wait_ioctl(struct drm_device *drm_dev, void *data,
+ struct drm_file *file)
+{
+ struct drm_imx_dpu_wait *wait;
+ struct imx_drm_dpu_bliteng *bliteng;
+ struct dpu_bliteng *dpu_be;
+ void *user_data;
+ s32 id = 0;
+ int ret;
+
+ wait = data;
+ user_data = (void *)(unsigned long)wait->user_data;
+ if (copy_from_user(&id, (void __user *)user_data,
+ sizeof(id))) {
+ return -EFAULT;
+ }
+
+ if (id != 0 && id != 1)
+ return -EINVAL;
+
+ bliteng = imx_drm_dpu_bliteng_find_by_id(id);
+ if (!bliteng) {
+ DRM_ERROR("Failed to get dpu_bliteng\n");
+ return -ENODEV;
+ }
+
+ dpu_be = bliteng->dpu_be;
+
+ ret = dpu_be_get(dpu_be);
+
+ dpu_be_wait(dpu_be);
+
+ dpu_be_put(dpu_be);
+
+ return ret;
+}
+
+static int imx_drm_dpu_get_param_ioctl(struct drm_device *drm_dev, void *data,
+ struct drm_file *file)
+{
+ enum drm_imx_dpu_param *param = data;
+ struct imx_drm_dpu_bliteng *bliteng;
+ struct dpu_bliteng *dpu_be;
+ int ret, id, fd = -1;
+
+ switch (*param) {
+ case (DRM_IMX_MAX_DPUS):
+ ret = imx_dpu_num;
+ break;
+ case DRM_IMX_GET_FENCE:
+ for (id = 0; id < imx_dpu_num; id++) {
+ bliteng = imx_drm_dpu_bliteng_find_by_id(id);
+ if (!bliteng) {
+ DRM_ERROR("Failed to get dpu_bliteng\n");
+ return -ENODEV;
+ }
+
+ dpu_be = bliteng->dpu_be;
+ ret = dpu_be_get(dpu_be);
+
+ if (fd == -1)
+ fd = dpu_be_get_fence(dpu_be);
+
+ dpu_be_set_fence(dpu_be, fd);
+ dpu_be_put(dpu_be);
+ }
+ ret = fd;
+ break;
+ default:
+ ret = -EINVAL;
+ DRM_ERROR("Unknown param![%d]\n", *param);
+ break;
+ }
+
+ return ret;
+}
+
+const struct drm_ioctl_desc imx_drm_dpu_ioctls[3] = {
+ DRM_IOCTL_DEF_DRV(IMX_DPU_SET_CMDLIST, imx_drm_dpu_set_cmdlist_ioctl,
+ DRM_RENDER_ALLOW),
+ DRM_IOCTL_DEF_DRV(IMX_DPU_WAIT, imx_drm_dpu_wait_ioctl,
+ DRM_RENDER_ALLOW),
+ DRM_IOCTL_DEF_DRV(IMX_DPU_GET_PARAM, imx_drm_dpu_get_param_ioctl,
+ DRM_RENDER_ALLOW),
+};
+
+static int dpu_bliteng_bind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct imx_drm_dpu_bliteng *bliteng;
+ struct dpu_bliteng *dpu_bliteng = NULL;
+ int ret;
+
+ bliteng = devm_kzalloc(dev, sizeof(*bliteng), GFP_KERNEL);
+ if (!bliteng)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&bliteng->list);
+
+ ret = dpu_bliteng_get_empty_instance(&dpu_bliteng, dev);
+ if (ret)
+ return ret;
+
+ dpu_bliteng_set_id(dpu_bliteng, imx_dpu_num);
+ dpu_bliteng_set_dev(dpu_bliteng, dev);
+
+ ret = dpu_bliteng_init(dpu_bliteng);
+ if (ret)
+ return ret;
+
+ mutex_lock(&imx_drm_dpu_bliteng_lock);
+ bliteng->dpu_be = dpu_bliteng;
+ list_add_tail(&bliteng->list, &imx_drm_dpu_bliteng_list);
+ mutex_unlock(&imx_drm_dpu_bliteng_lock);
+
+ dev_set_drvdata(dev, dpu_bliteng);
+
+ imx_dpu_num++;
+
+ return 0;
+}
+
+static void dpu_bliteng_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct imx_drm_dpu_bliteng *bliteng;
+ struct dpu_bliteng *dpu_bliteng = dev_get_drvdata(dev);
+ s32 id = dpu_bliteng_get_id(dpu_bliteng);
+
+ bliteng = imx_drm_dpu_bliteng_find_by_id(id);
+ list_del(&bliteng->list);
+
+ dpu_bliteng_fini(dpu_bliteng);
+ dev_set_drvdata(dev, NULL);
+
+ imx_dpu_num--;
+}
+
+static const struct component_ops dpu_bliteng_ops = {
+ .bind = dpu_bliteng_bind,
+ .unbind = dpu_bliteng_unbind,
+};
+
+static int dpu_bliteng_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+
+ if (!dev->platform_data)
+ return -EINVAL;
+
+ return component_add(dev, &dpu_bliteng_ops);
+}
+
+static int dpu_bliteng_remove(struct platform_device *pdev)
+{
+ component_del(&pdev->dev, &dpu_bliteng_ops);
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int dpu_bliteng_suspend(struct device *dev)
+{
+ struct dpu_bliteng *dpu_bliteng = dev_get_drvdata(dev);
+ int ret;
+
+ if (dpu_bliteng == NULL)
+ return 0;
+
+ ret = dpu_be_get(dpu_bliteng);
+
+ dpu_be_wait(dpu_bliteng);
+
+ dpu_be_put(dpu_bliteng);
+
+ dpu_bliteng_fini(dpu_bliteng);
+
+ return 0;
+}
+
+static int dpu_bliteng_resume(struct device *dev)
+{
+ struct dpu_bliteng *dpu_bliteng = dev_get_drvdata(dev);
+
+ if (dpu_bliteng != NULL)
+ dpu_bliteng_init(dpu_bliteng);
+
+ return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(dpu_bliteng_pm_ops,
+ dpu_bliteng_suspend, dpu_bliteng_resume);
+
+struct platform_driver dpu_bliteng_driver = {
+ .driver = {
+ .name = "imx-drm-dpu-bliteng",
+ .pm = &dpu_bliteng_pm_ops,
+ },
+ .probe = dpu_bliteng_probe,
+ .remove = dpu_bliteng_remove,
+};
+
+module_platform_driver(dpu_bliteng_driver);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("NXP Semiconductor");
+MODULE_DESCRIPTION("i.MX DRM DPU BLITENG");
diff --git a/drivers/gpu/drm/imx/dpu/dpu-blit.h b/drivers/gpu/drm/imx/dpu/dpu-blit.h
new file mode 100644
index 000000000000..cf429086cdf4
--- /dev/null
+++ b/drivers/gpu/drm/imx/dpu/dpu-blit.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+
+/*
+ * Copyright 2021 NXP
+ */
+
+#ifndef _DPU_DRM_BLIT_H_
+#define _DPU_DRM_BLIT_H_
+
+#include <drm/drm_ioctl.h>
+
+#ifdef CONFIG_DRM_IMX_DPU
+extern const struct drm_ioctl_desc imx_drm_dpu_ioctls[3];
+#else
+const struct drm_ioctl_desc imx_drm_dpu_ioctls[] = {};
+#endif
+
+#endif /* _DPU_DRM_BLIT_H_ */
diff --git a/drivers/gpu/drm/imx/dpu/dpu-crc.c b/drivers/gpu/drm/imx/dpu/dpu-crc.c
new file mode 100644
index 000000000000..cba4e1705e1c
--- /dev/null
+++ b/drivers/gpu/drm/imx/dpu/dpu-crc.c
@@ -0,0 +1,385 @@
+/*
+ * Copyright 2019-2021 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_device.h>
+#include <drm/drm_plane.h>
+#include <linux/interrupt.h>
+#include <linux/types.h>
+#include <video/dpu.h>
+#include "dpu-crc.h"
+#include "dpu-crtc.h"
+
+static inline void get_left(struct drm_rect *r, struct drm_display_mode *m)
+{
+ r->x1 = 0;
+ r->y1 = 0;
+ r->x2 = m->hdisplay >> 1;
+ r->y2 = m->vdisplay;
+}
+
+static inline void get_right(struct drm_rect *r, struct drm_display_mode *m)
+{
+ r->x1 = m->hdisplay >> 1;
+ r->y1 = 0;
+ r->x2 = m->hdisplay;
+ r->y2 = m->vdisplay;
+}
+
+static void
+dpu_enable_signature_roi(struct dpu_signature *sig, struct drm_rect *roi)
+{
+ signature_continuous_mode(sig, true);
+ signature_win(sig, 0, roi->x1, roi->y1, roi->x2, roi->y2);
+ signature_eval_win(sig, 0, true);
+ signature_shdldreq(sig, 0x1);
+}
+
+static void dpu_disable_signature(struct dpu_signature *sig)
+{
+ signature_continuous_mode(sig, false);
+ signature_wait_for_idle(sig);
+ signature_eval_win(sig, 0, false);
+}
+
+/*
+ * Supported modes and source names:
+ * 1) auto mode:
+ * "auto" should be selected as the source name.
+ * The evaluation window is the same to the display region as
+ * indicated by drm_crtc_state->adjusted_mode.
+ *
+ * 2) region of interest(ROI) mode:
+ * "roi:x1,y1,x2,y2" should be selected as the source name.
+ * The region of interest is defined by the inclusive upper left
+ * position at (x1, y1) and the exclusive lower right position
+ * at (x2, y2), see struct drm_rect for the same idea.
+ * The evaluation window is the region of interest.
+ */
+static int
+dpu_crc_parse_source(const char *source_name, enum dpu_crc_source *s,
+ struct drm_rect *roi)
+{
+ static const char roi_prefix[] = "roi:";
+
+ if (!source_name) {
+ *s = DPU_CRC_SRC_NONE;
+ } else if (!strcmp(source_name, "auto")) {
+ *s = DPU_CRC_SRC_FRAMEGEN;
+ } else if (strstarts(source_name, roi_prefix)) {
+ char *options, *opt;
+ int len = strlen(roi_prefix);
+ int params[4];
+ int i = 0, ret;
+
+ options = kstrdup(source_name + len, GFP_KERNEL);
+
+ while ((opt = strsep(&options, ",")) != NULL) {
+ if (i > 3)
+ return -EINVAL;
+
+ ret = kstrtouint(opt, 10, &params[i]);
+ if (ret < 0)
+ return ret;
+
+ if (params[i] < 0)
+ return -EINVAL;
+
+ i++;
+ }
+
+ if (i != 4)
+ return -EINVAL;
+
+ roi->x1 = params[0];
+ roi->y1 = params[1];
+ roi->x2 = params[2];
+ roi->y2 = params[3];
+
+ if (!drm_rect_visible(roi))
+ return -EINVAL;
+
+ *s = DPU_CRC_SRC_FRAMEGEN_ROI;
+ } else {
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int dpu_crtc_verify_crc_source(struct drm_crtc *crtc, const char *source_name,
+ size_t *values_cnt)
+{
+ struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc);
+ struct imx_crtc_state *imx_crtc_state;
+ struct dpu_crtc_state *dcstate;
+ struct drm_rect roi;
+ enum dpu_crc_source source;
+ int ret;
+
+ if (dpu_crc_parse_source(source_name, &source, &roi) < 0) {
+ dev_dbg(dpu_crtc->dev, "unknown source %s\n", source_name);
+ return -EINVAL;
+ }
+
+ ret = drm_modeset_lock_single_interruptible(&crtc->mutex);
+ if (ret)
+ return ret;
+
+ imx_crtc_state = to_imx_crtc_state(crtc->state);
+ dcstate = to_dpu_crtc_state(imx_crtc_state);
+ *values_cnt = dcstate->use_pc ? 6 : 3;
+
+ drm_modeset_unlock(&crtc->mutex);
+
+ return ret;
+}
+
+int dpu_crtc_set_crc_source(struct drm_crtc *crtc, const char *source_name)
+{
+ struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc);
+ struct drm_modeset_acquire_ctx ctx;
+ struct drm_crtc_state *crtc_state;
+ struct drm_atomic_state *state;
+ struct drm_rect roi = {0, 0, 0, 0};
+ enum dpu_crc_source source;
+ int ret;
+
+ if (dpu_crc_parse_source(source_name, &source, &roi) < 0) {
+ dev_dbg(dpu_crtc->dev, "unknown source %s\n", source_name);
+ return -EINVAL;
+ }
+
+ /* Perform an atomic commit to set the CRC source. */
+ drm_modeset_acquire_init(&ctx, 0);
+
+ state = drm_atomic_state_alloc(crtc->dev);
+ if (!state) {
+ ret = -ENOMEM;
+ goto unlock;
+ }
+
+ state->acquire_ctx = &ctx;
+
+retry:
+ crtc_state = drm_atomic_get_crtc_state(state, crtc);
+ if (!IS_ERR(crtc_state)) {
+ struct imx_crtc_state *imx_crtc_state;
+ struct dpu_crtc_state *dcstate;
+
+ imx_crtc_state = to_imx_crtc_state(crtc_state);
+ dcstate = to_dpu_crtc_state(imx_crtc_state);
+
+ if ((dcstate->use_pc && crtc->crc.values_cnt != 6) ||
+ (!dcstate->use_pc && crtc->crc.values_cnt != 3)) {
+ ret = -EINVAL;
+ goto put;
+ }
+
+ dcstate->crc.source = source;
+ dpu_copy_roi(&roi, &dcstate->crc.roi);
+ dpu_crtc->use_dual_crc = dcstate->use_pc;
+
+ ret = drm_atomic_commit(state);
+ } else {
+ ret = PTR_ERR(crtc_state);
+ }
+
+ if (ret == -EDEADLK) {
+ drm_atomic_state_clear(state);
+ drm_modeset_backoff(&ctx);
+ goto retry;
+ }
+
+put:
+ drm_atomic_state_put(state);
+
+unlock:
+ drm_modeset_drop_locks(&ctx);
+ drm_modeset_acquire_fini(&ctx);
+
+ return ret;
+}
+
+irqreturn_t dpu_crc_valid_irq_threaded_handler(int irq, void *dev_id)
+{
+ struct dpu_crtc *dpu_crtc = dev_id;
+ struct dpu_signature *sig = dpu_crtc->sig;
+ struct dpu_crtc *aux_dpu_crtc = dpu_crtc_get_aux_dpu_crtc(dpu_crtc);
+ bool dual_crc = dpu_crtc->use_dual_crc;
+ unsigned long ret;
+ uint32_t crcs[6] = {0, 0, 0, 0, 0, 0};
+
+ dev_dbg(dpu_crtc->dev, "CRC valid irq threaded handler\n");
+
+ signature_crc_value(sig, 0, &dpu_crtc->crc_red,
+ &dpu_crtc->crc_green,
+ &dpu_crtc->crc_blue);
+
+ if (dual_crc && dpu_crtc->stream_id == 1) {
+ complete(&aux_dpu_crtc->aux_crc_done);
+ return IRQ_HANDLED;
+ }
+
+ if (!dual_crc ||
+ (dual_crc && dpu_crtc->dual_crc_flag != DPU_DUAL_CRC_FLAG_RIGHT)) {
+ crcs[2] = dpu_crtc->crc_red;
+ crcs[1] = dpu_crtc->crc_green;
+ crcs[0] = dpu_crtc->crc_blue;
+ }
+
+ if (dual_crc && dpu_crtc->stream_id == 0) {
+ ret = wait_for_completion_timeout(&dpu_crtc->aux_crc_done,
+ HZ / 20);
+ if (ret == 0)
+ dev_warn(dpu_crtc->dev,
+ "wait for auxiliary CRC done timeout\n");
+
+ if (dpu_crtc->dual_crc_flag != DPU_DUAL_CRC_FLAG_LEFT) {
+ crcs[5] = aux_dpu_crtc->crc_red;
+ crcs[4] = aux_dpu_crtc->crc_green;
+ crcs[3] = aux_dpu_crtc->crc_blue;
+ }
+ }
+
+ drm_crtc_add_crc_entry(&dpu_crtc->base, false, 0, crcs);
+
+ return IRQ_HANDLED;
+}
+
+void dpu_crtc_enable_crc_source(struct drm_crtc *crtc,
+ enum dpu_crc_source source,
+ struct drm_rect *roi)
+{
+ struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc);
+ struct dpu_crtc *aux_dpu_crtc = dpu_crtc_get_aux_dpu_crtc(dpu_crtc);
+ struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc->state);
+ struct dpu_crtc_state *dcstate = to_dpu_crtc_state(imx_crtc_state);
+ struct drm_display_mode *mode = &crtc->state->adjusted_mode;
+ struct completion *shdld_done;
+ struct drm_rect left, right;
+ struct drm_rect r, aux_r, clip;
+ bool dual_crc = dpu_crtc->use_dual_crc;
+ bool use_left, use_right;
+ int half_hdisplay;
+ unsigned long ret;
+
+ if (source == DPU_CRC_SRC_NONE)
+ return;
+
+ if (dual_crc != dcstate->use_pc)
+ return;
+
+ if (dpu_crtc->crc_is_enabled)
+ return;
+
+ if (dual_crc) {
+ half_hdisplay = mode->hdisplay >> 1;
+
+ get_left(&left, mode);
+ get_right(&right, mode);
+
+ dpu_copy_roi(&left, &clip);
+ if (drm_rect_intersect(&clip, roi)) {
+ dpu_copy_roi(&clip, &r);
+ use_left = true;
+ } else {
+ dpu_copy_roi(&left, &r);
+ use_left = false;
+ }
+
+ if (drm_rect_intersect(&right, roi)) {
+ right.x1 -= half_hdisplay;
+ right.x2 -= half_hdisplay;
+ dpu_copy_roi(&right, &aux_r);
+ use_right = true;
+ } else {
+ dpu_copy_roi(&left, &aux_r);
+ use_right = false;
+ }
+
+ if (use_left && !use_right) {
+ dpu_crtc->dual_crc_flag = DPU_DUAL_CRC_FLAG_LEFT;
+ } else if (!use_left && use_right) {
+ dpu_crtc->dual_crc_flag = DPU_DUAL_CRC_FLAG_RIGHT;
+ } else if (use_left && use_right) {
+ dpu_crtc->dual_crc_flag = DPU_DUAL_CRC_FLAG_DUAL;
+ } else {
+ dpu_crtc->dual_crc_flag = DPU_DUAL_CRC_FLAG_ERR_NONE;
+ dev_err(dpu_crtc->dev, "error flag for dual CRC\n");
+ return;
+ }
+ } else {
+ dpu_copy_roi(roi, &r);
+ }
+
+ enable_irq(dpu_crtc->crc_valid_irq);
+ enable_irq(dpu_crtc->crc_shdld_irq);
+ disengcfg_sig_select(dpu_crtc->dec, DEC_SIG_SEL_FRAMEGEN);
+ dpu_enable_signature_roi(dpu_crtc->sig, &r);
+
+ if (dual_crc) {
+ aux_dpu_crtc->use_dual_crc = dual_crc;
+ enable_irq(aux_dpu_crtc->crc_valid_irq);
+ enable_irq(aux_dpu_crtc->crc_shdld_irq);
+ disengcfg_sig_select(dpu_crtc->aux_dec, DEC_SIG_SEL_FRAMEGEN);
+ dpu_enable_signature_roi(dpu_crtc->aux_sig, &aux_r);
+ }
+
+ shdld_done = &dpu_crtc->crc_shdld_done;
+ ret = wait_for_completion_timeout(shdld_done, HZ);
+ if (ret == 0)
+ dev_warn(dpu_crtc->dev, "wait for CRC shdld done timeout\n");
+
+ if (dual_crc) {
+ shdld_done = &aux_dpu_crtc->crc_shdld_done;
+ ret = wait_for_completion_timeout(shdld_done, HZ);
+ if (ret == 0)
+ dev_warn(dpu_crtc->dev,
+ "wait for auxiliary CRC shdld done timeout\n");
+ }
+
+ disable_irq(dpu_crtc->crc_shdld_irq);
+ if (dual_crc)
+ disable_irq(aux_dpu_crtc->crc_shdld_irq);
+
+ dpu_crtc->crc_is_enabled = true;
+
+ dev_dbg(dpu_crtc->dev, "enable CRC source %d, ROI:" DRM_RECT_FMT "\n",
+ source, DRM_RECT_ARG(roi));
+}
+
+void dpu_crtc_disable_crc_source(struct drm_crtc *crtc, bool dual_crc)
+{
+ struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc);
+ struct dpu_crtc *aux_dpu_crtc = dpu_crtc_get_aux_dpu_crtc(dpu_crtc);
+
+ if (!dpu_crtc->crc_is_enabled)
+ return;
+
+ dpu_disable_signature(dpu_crtc->sig);
+ if (dual_crc)
+ dpu_disable_signature(dpu_crtc->aux_sig);
+
+ disable_irq(dpu_crtc->crc_valid_irq);
+ if (dual_crc) {
+ disable_irq(aux_dpu_crtc->crc_valid_irq);
+ reinit_completion(&dpu_crtc->aux_crc_done);
+ }
+
+ dpu_crtc->crc_is_enabled = false;
+
+ dev_dbg(dpu_crtc->dev, "disable CRC source\n");
+}
diff --git a/drivers/gpu/drm/imx/dpu/dpu-crc.h b/drivers/gpu/drm/imx/dpu/dpu-crc.h
new file mode 100644
index 000000000000..25c03937470e
--- /dev/null
+++ b/drivers/gpu/drm/imx/dpu/dpu-crc.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2019,2020 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef _DPU_CRC_H_
+#define _DPU_CRC_H_
+
+#include "dpu-crtc.h"
+
+enum {
+ DPU_DUAL_CRC_FLAG_DUAL,
+ DPU_DUAL_CRC_FLAG_LEFT,
+ DPU_DUAL_CRC_FLAG_RIGHT,
+ DPU_DUAL_CRC_FLAG_ERR_NONE,
+};
+
+static inline bool to_enable_dpu_crc(struct dpu_crtc_state *new_dcstate,
+ struct dpu_crtc_state *old_dcstate)
+{
+ return old_dcstate->crc.source == DPU_CRC_SRC_NONE &&
+ new_dcstate->crc.source != DPU_CRC_SRC_NONE;
+}
+
+static inline bool to_disable_dpu_crc(struct dpu_crtc_state *new_dcstate,
+ struct dpu_crtc_state *old_dcstate)
+{
+ return old_dcstate->crc.source != DPU_CRC_SRC_NONE &&
+ new_dcstate->crc.source == DPU_CRC_SRC_NONE;
+}
+
+static inline void dpu_copy_roi(struct drm_rect *from, struct drm_rect *to)
+{
+ to->x1 = from->x1;
+ to->y1 = from->y1;
+ to->x2 = from->x2;
+ to->y2 = from->y2;
+}
+
+#ifdef CONFIG_DEBUG_FS
+int dpu_crtc_verify_crc_source(struct drm_crtc *crtc, const char *source_name,
+ size_t *values_cnt);
+int dpu_crtc_set_crc_source(struct drm_crtc *crtc, const char *source_name);
+irqreturn_t dpu_crc_valid_irq_threaded_handler(int irq, void *dev_id);
+void dpu_crtc_enable_crc_source(struct drm_crtc *crtc,
+ enum dpu_crc_source source,
+ struct drm_rect *roi);
+void dpu_crtc_disable_crc_source(struct drm_crtc *crtc, bool dual_crc);
+#else
+#define dpu_crtc_verify_crc_source NULL
+#define dpu_crtc_set_crc_source NULL
+irqreturn_t dpu_crc_valid_irq_threaded_handler(int irq, void *dev_id)
+{
+ return IRQ_HANDLED;
+}
+void dpu_crtc_enable_crc_source(struct drm_crtc *crtc,
+ enum dpu_crc_source source,
+ struct drm_rect *roi)
+{
+}
+void dpu_crtc_disable_crc_source(struct drm_crtc *crtc, bool dual_crc)
+{
+}
+#endif
+
+#endif
diff --git a/drivers/gpu/drm/imx/dpu/dpu-crtc.c b/drivers/gpu/drm/imx/dpu/dpu-crtc.c
new file mode 100644
index 000000000000..f0a388bdffac
--- /dev/null
+++ b/drivers/gpu/drm/imx/dpu/dpu-crtc.c
@@ -0,0 +1,1466 @@
+/*
+ * Copyright 2017-2022 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <drm/drm_vblank.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <linux/component.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/export.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <video/dpu.h>
+#include <video/imx8-pc.h>
+#include <video/imx8-prefetch.h>
+#include "dpu-crc.h"
+#include "dpu-crtc.h"
+#include "dpu-kms.h"
+#include "dpu-plane.h"
+#include "../imx-drm.h"
+
+static inline struct dpu_plane_state **
+alloc_dpu_plane_states(struct dpu_crtc *dpu_crtc)
+{
+ struct dpu_plane_state **states;
+
+ states = kcalloc(dpu_crtc->hw_plane_num, sizeof(*states), GFP_KERNEL);
+ if (!states)
+ return ERR_PTR(-ENOMEM);
+
+ return states;
+}
+
+static void dpu_crtc_queue_state_event(struct drm_crtc *crtc)
+{
+ struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc);
+
+ spin_lock_irq(&crtc->dev->event_lock);
+ if (crtc->state->event) {
+ WARN_ON(drm_crtc_vblank_get(crtc));
+ WARN_ON(dpu_crtc->event);
+ dpu_crtc->event = crtc->state->event;
+ crtc->state->event = NULL;
+ }
+ spin_unlock_irq(&crtc->dev->event_lock);
+}
+
+struct dpu_plane_state **
+crtc_state_get_dpu_plane_states(struct drm_crtc_state *state)
+{
+ struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(state);
+ struct dpu_crtc_state *dcstate = to_dpu_crtc_state(imx_crtc_state);
+
+ return dcstate->dpu_plane_states;
+}
+
+struct dpu_crtc *dpu_crtc_get_aux_dpu_crtc(struct dpu_crtc *dpu_crtc)
+{
+ struct drm_crtc *crtc = &dpu_crtc->base, *tmp_crtc;
+ struct drm_device *dev = crtc->dev;
+ struct dpu_crtc *aux_dpu_crtc = NULL;
+
+ drm_for_each_crtc(tmp_crtc, dev) {
+ if (tmp_crtc == crtc)
+ continue;
+
+ aux_dpu_crtc = to_dpu_crtc(tmp_crtc);
+
+ if (dpu_crtc->crtc_grp_id == aux_dpu_crtc->crtc_grp_id)
+ break;
+ }
+
+ BUG_ON(!aux_dpu_crtc);
+
+ return aux_dpu_crtc;
+}
+
+static void dpu_crtc_atomic_enable(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+{
+ struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc);
+ struct dpu_crtc *aux_dpu_crtc = dpu_crtc_get_aux_dpu_crtc(dpu_crtc);
+ struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc->state);
+ struct dpu_crtc_state *dcstate = to_dpu_crtc_state(imx_crtc_state);
+ struct dpu_plane *dplane = to_dpu_plane(crtc->primary);
+ struct dpu_plane_res *res = &dplane->grp->res;
+ struct dpu_extdst *plane_ed = res->ed[dplane->stream_id];
+ struct dpu_extdst *aux_plane_ed = dpu_aux_ed_peek(plane_ed);
+ struct dpu_extdst *m_plane_ed = NULL, *s_plane_ed;
+ struct completion *shdld_done;
+ struct completion *m_safety_shdld_done, *s_safety_shdld_done;
+ struct completion *m_content_shdld_done, *s_content_shdld_done;
+ struct completion *m_dec_shdld_done, *s_dec_shdld_done;
+ unsigned long ret, flags;
+
+ drm_crtc_vblank_on(crtc);
+
+ if (dcstate->use_pc) {
+ tcon_enable_pc(dpu_crtc->tcon);
+
+ if (extdst_is_master(plane_ed)) {
+ m_plane_ed = plane_ed;
+ s_plane_ed = aux_plane_ed;
+ } else {
+ m_plane_ed = aux_plane_ed;
+ s_plane_ed = plane_ed;
+ }
+ extdst_pixengcfg_syncmode_master(m_plane_ed, true);
+ extdst_pixengcfg_syncmode_master(s_plane_ed, false);
+ } else {
+ extdst_pixengcfg_syncmode_master(plane_ed, false);
+ }
+
+ enable_irq(dpu_crtc->safety_shdld_irq);
+ enable_irq(dpu_crtc->content_shdld_irq);
+ enable_irq(dpu_crtc->dec_shdld_irq);
+ if (dcstate->use_pc) {
+ enable_irq(aux_dpu_crtc->safety_shdld_irq);
+ enable_irq(aux_dpu_crtc->content_shdld_irq);
+ enable_irq(aux_dpu_crtc->dec_shdld_irq);
+ }
+
+ if (dcstate->use_pc) {
+ framegen_enable_clock(dpu_crtc->stream_id ?
+ dpu_crtc->aux_fg : dpu_crtc->fg);
+ extdst_pixengcfg_sync_trigger(m_plane_ed);
+ framegen_shdtokgen(dpu_crtc->m_fg);
+
+ /* don't relinquish CPU until TCONs are set to operation mode */
+ local_irq_save(flags);
+ preempt_disable();
+ /* First turn on the slave stream, second the master stream. */
+ framegen_enable(dpu_crtc->s_fg);
+ framegen_enable(dpu_crtc->m_fg);
+ /*
+ * TKT320590:
+ * Turn TCONs into operation mode as soon as the first dumb
+ * frame is generated by DPU from the master stream(we don't
+ * relinquish CPU to ensure this). This makes DPRs/PRGs of
+ * the dual stream be able to evade the dumb frames of the
+ * dual stream respectively.
+ */
+ framegen_wait_for_frame_counter_moving(dpu_crtc->m_fg);
+ /* again, slave first, then master */
+ tcon_set_operation_mode(dpu_crtc->s_tcon);
+ tcon_set_operation_mode(dpu_crtc->m_tcon);
+ local_irq_restore(flags);
+ preempt_enable();
+
+ framegen_enable_pixel_link(dpu_crtc->s_fg);
+ framegen_enable_pixel_link(dpu_crtc->m_fg);
+
+ if (dpu_crtc->aux_is_master) {
+ m_safety_shdld_done = &aux_dpu_crtc->safety_shdld_done;
+ m_content_shdld_done = &aux_dpu_crtc->content_shdld_done;
+ m_dec_shdld_done = &aux_dpu_crtc->dec_shdld_done;
+ s_safety_shdld_done = &dpu_crtc->safety_shdld_done;
+ s_content_shdld_done = &dpu_crtc->content_shdld_done;
+ s_dec_shdld_done = &dpu_crtc->dec_shdld_done;
+ } else {
+ m_safety_shdld_done = &dpu_crtc->safety_shdld_done;
+ m_content_shdld_done = &dpu_crtc->content_shdld_done;
+ m_dec_shdld_done = &dpu_crtc->dec_shdld_done;
+ s_safety_shdld_done = &aux_dpu_crtc->safety_shdld_done;
+ s_content_shdld_done = &aux_dpu_crtc->content_shdld_done;
+ s_dec_shdld_done = &aux_dpu_crtc->dec_shdld_done;
+ }
+
+ ret = wait_for_completion_timeout(m_safety_shdld_done, HZ);
+ if (ret == 0)
+ DRM_WARN("[CRTC:%d:%s] %s: wait for master safety shdld done timeout\n",
+ crtc->base.id, crtc->name, __func__);
+ ret = wait_for_completion_timeout(m_content_shdld_done, HZ);
+ if (ret == 0)
+ DRM_WARN("[CRTC:%d:%s] %s: wait for master content shdld done timeout\n",
+ crtc->base.id, crtc->name, __func__);
+ ret = wait_for_completion_timeout(m_dec_shdld_done, HZ);
+ if (ret == 0)
+ DRM_WARN("[CRTC:%d:%s] %s: wait for master DEC shdld done timeout\n",
+ crtc->base.id, crtc->name, __func__);
+
+ ret = wait_for_completion_timeout(s_safety_shdld_done, HZ);
+ if (ret == 0)
+ DRM_WARN("[CRTC:%d:%s] %s: wait for slave safety shdld done timeout\n",
+ crtc->base.id, crtc->name, __func__);
+ ret = wait_for_completion_timeout(s_content_shdld_done, HZ);
+ if (ret == 0)
+ DRM_WARN("[CRTC:%d:%s] %s: wait for slave content shdld done timeout\n",
+ crtc->base.id, crtc->name, __func__);
+ ret = wait_for_completion_timeout(s_dec_shdld_done, HZ);
+ if (ret == 0)
+ DRM_WARN("[CRTC:%d:%s] %s: wait for slave DEC shdld done timeout\n",
+ crtc->base.id, crtc->name, __func__);
+ } else {
+ framegen_enable_clock(dpu_crtc->fg);
+ extdst_pixengcfg_sync_trigger(plane_ed);
+ extdst_pixengcfg_sync_trigger(dpu_crtc->ed);
+ framegen_shdtokgen(dpu_crtc->fg);
+
+ /* don't relinquish CPU until TCON is set to operation mode */
+ local_irq_save(flags);
+ preempt_disable();
+ framegen_enable(dpu_crtc->fg);
+ /*
+ * TKT320590:
+ * Turn TCON into operation mode as soon as the first dumb
+ * frame is generated by DPU(we don't relinquish CPU to ensure
+ * this). This makes DPR/PRG be able to evade the frame.
+ */
+ framegen_wait_for_frame_counter_moving(dpu_crtc->fg);
+ tcon_set_operation_mode(dpu_crtc->tcon);
+ local_irq_restore(flags);
+ preempt_enable();
+
+ framegen_enable_pixel_link(dpu_crtc->fg);
+
+ shdld_done = &dpu_crtc->safety_shdld_done;
+ ret = wait_for_completion_timeout(shdld_done, HZ);
+ if (ret == 0)
+ DRM_WARN("[CRTC:%d:%s] %s: wait for safety shdld done timeout\n",
+ crtc->base.id, crtc->name, __func__);
+ shdld_done = &dpu_crtc->content_shdld_done;
+ ret = wait_for_completion_timeout(shdld_done, HZ);
+ if (ret == 0)
+ DRM_WARN("[CRTC:%d:%s] %s: wait for content shdld done timeout\n",
+ crtc->base.id, crtc->name, __func__);
+ shdld_done = &dpu_crtc->dec_shdld_done;
+ ret = wait_for_completion_timeout(shdld_done, HZ);
+ if (ret == 0)
+ DRM_WARN("[CRTC:%d:%s] %s: wait for DEC shdld done timeout\n",
+ crtc->base.id, crtc->name, __func__);
+ }
+
+ disable_irq(dpu_crtc->safety_shdld_irq);
+ disable_irq(dpu_crtc->content_shdld_irq);
+ disable_irq(dpu_crtc->dec_shdld_irq);
+ if (dcstate->use_pc) {
+ disable_irq(aux_dpu_crtc->safety_shdld_irq);
+ disable_irq(aux_dpu_crtc->content_shdld_irq);
+ disable_irq(aux_dpu_crtc->dec_shdld_irq);
+ }
+
+ dpu_crtc_queue_state_event(crtc);
+
+ if (dcstate->use_pc) {
+ framegen_wait_for_secondary_syncup(dpu_crtc->m_fg);
+ framegen_wait_for_secondary_syncup(dpu_crtc->s_fg);
+
+ if (framegen_secondary_requests_to_read_empty_fifo(dpu_crtc->m_fg)) {
+ framegen_secondary_clear_channel_status(dpu_crtc->m_fg);
+ DRM_WARN("[CRTC:%d:%s] %s: master FrameGen requests to read empty FIFO\n",
+ crtc->base.id, crtc->name, __func__);
+ }
+ if (framegen_secondary_requests_to_read_empty_fifo(dpu_crtc->s_fg)) {
+ framegen_secondary_clear_channel_status(dpu_crtc->s_fg);
+ DRM_WARN("[CRTC:%d:%s] %s: slave FrameGen requests to read empty FIFO\n",
+ crtc->base.id, crtc->name, __func__);
+ }
+ } else {
+ framegen_wait_for_secondary_syncup(dpu_crtc->fg);
+
+ if (framegen_secondary_requests_to_read_empty_fifo(dpu_crtc->fg)) {
+ framegen_secondary_clear_channel_status(dpu_crtc->fg);
+ DRM_WARN("[CRTC:%d:%s] %s: FrameGen requests to read empty FIFO\n",
+ crtc->base.id, crtc->name, __func__);
+ }
+ }
+
+ if (dcstate->crc.source != DPU_CRC_SRC_NONE)
+ dpu_crtc_enable_crc_source(crtc,
+ dcstate->crc.source, &dcstate->crc.roi);
+}
+
+static void dpu_crtc_atomic_disable(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+{
+ struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc);
+ struct drm_crtc_state *old_crtc_state =
+ drm_atomic_get_old_crtc_state(state, crtc);
+ struct imx_crtc_state *imx_crtc_state =
+ to_imx_crtc_state(old_crtc_state);
+ struct dpu_crtc_state *dcstate = to_dpu_crtc_state(imx_crtc_state);
+ struct drm_display_mode *adjusted_mode = &old_crtc_state->adjusted_mode;
+ struct dpu_plane *dplane = to_dpu_plane(crtc->primary);
+ struct dpu_plane_res *res = &dplane->grp->res;
+ struct dpu_plane_state *dpstate;
+ struct dpu_fetchunit *fu;
+ unsigned long flags;
+ int i;
+
+ if (dcstate->crc.source != DPU_CRC_SRC_NONE)
+ dpu_crtc_disable_crc_source(crtc, dcstate->use_pc);
+
+ if (dcstate->use_pc) {
+ tcon_disable_pc(dpu_crtc->tcon);
+
+ framegen_disable_pixel_link(dpu_crtc->m_fg);
+ framegen_disable_pixel_link(dpu_crtc->s_fg);
+
+ /* don't relinquish CPU until DPRC repeat_en is disabled */
+ local_irq_save(flags);
+ preempt_disable();
+ /*
+ * Sync to FrameGen frame counter moving so that
+ * FrameGen can be disabled in the next frame.
+ */
+ framegen_wait_for_frame_counter_moving(dpu_crtc->m_fg);
+
+ /* First turn off the master stream, second the slave stream. */
+ framegen_disable(dpu_crtc->m_fg);
+ framegen_disable(dpu_crtc->s_fg);
+
+ /*
+ * There is one frame leftover after FrameGen disablement.
+ * Sync to FrameGen frame counter moving so that
+ * DPRC repeat_en can be disabled in the next frame.
+ */
+ framegen_wait_for_frame_counter_moving(dpu_crtc->m_fg);
+
+ for (i = 0; i < dpu_crtc->hw_plane_num; i++) {
+ lb_sec_sel_t source;
+ bool aux_source_flag;
+ bool use_prefetch;
+
+ dpstate = dcstate->dpu_plane_states[i];
+ if (!dpstate)
+ continue;
+
+ aux_source_flag = false;
+again:
+ source = aux_source_flag ? dpstate->aux_source :
+ dpstate->source;
+ use_prefetch = aux_source_flag ?
+ dpstate->use_aux_prefetch :
+ dpstate->use_prefetch;
+ fu = source_to_fu(res, source);
+ if (!fu) {
+ local_irq_restore(flags);
+ preempt_enable();
+ return;
+ }
+
+ if (fu->dprc && use_prefetch)
+ dprc_disable_repeat_en(fu->dprc);
+
+ if (dpstate->need_aux_source && !aux_source_flag) {
+ aux_source_flag = true;
+ goto again;
+ }
+ }
+ local_irq_restore(flags);
+ preempt_enable();
+
+ framegen_wait_done(dpu_crtc->m_fg, adjusted_mode);
+ framegen_wait_done(dpu_crtc->s_fg, adjusted_mode);
+
+ framegen_disable_clock(dpu_crtc->stream_id ?
+ dpu_crtc->aux_fg : dpu_crtc->fg);
+ } else {
+ framegen_disable_pixel_link(dpu_crtc->fg);
+
+ /* don't relinquish CPU until DPRC repeat_en is disabled */
+ local_irq_save(flags);
+ preempt_disable();
+ /*
+ * Sync to FrameGen frame counter moving so that
+ * FrameGen can be disabled in the next frame.
+ */
+ framegen_wait_for_frame_counter_moving(dpu_crtc->fg);
+ framegen_disable(dpu_crtc->fg);
+ /*
+ * There is one frame leftover after FrameGen disablement.
+ * Sync to FrameGen frame counter moving so that
+ * DPRC repeat_en can be disabled in the next frame.
+ */
+ framegen_wait_for_frame_counter_moving(dpu_crtc->fg);
+
+ for (i = 0; i < dpu_crtc->hw_plane_num; i++) {
+ dpstate = dcstate->dpu_plane_states[i];
+ if (!dpstate)
+ continue;
+
+ fu = source_to_fu(res, dpstate->source);
+ if (!fu) {
+ local_irq_restore(flags);
+ preempt_enable();
+ return;
+ }
+
+ if (fu->dprc && dpstate->use_prefetch)
+ dprc_disable_repeat_en(fu->dprc);
+ }
+ local_irq_restore(flags);
+ preempt_enable();
+
+ framegen_wait_done(dpu_crtc->fg, adjusted_mode);
+ framegen_disable_clock(dpu_crtc->fg);
+ }
+
+ drm_crtc_vblank_off(crtc);
+
+ spin_lock_irq(&crtc->dev->event_lock);
+ if (crtc->state->event && !crtc->state->active) {
+ drm_crtc_send_vblank_event(crtc, crtc->state->event);
+ crtc->state->event = NULL;
+ }
+ spin_unlock_irq(&crtc->dev->event_lock);
+}
+
+static void dpu_drm_crtc_reset(struct drm_crtc *crtc)
+{
+ struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc);
+ struct imx_crtc_state *imx_crtc_state;
+ struct dpu_crtc_state *state;
+
+ if (crtc->state) {
+ __drm_atomic_helper_crtc_destroy_state(crtc->state);
+
+ imx_crtc_state = to_imx_crtc_state(crtc->state);
+ state = to_dpu_crtc_state(imx_crtc_state);
+ kfree(state->dpu_plane_states);
+ kfree(state);
+ crtc->state = NULL;
+ }
+
+ state = kzalloc(sizeof(*state), GFP_KERNEL);
+ if (state) {
+ state->crc.source = DPU_CRC_SRC_NONE;
+ state->crc.roi.x1 = 0;
+ state->crc.roi.y1 = 0;
+ state->crc.roi.x2 = 0;
+ state->crc.roi.y2 = 0;
+
+ crtc->state = &state->imx_crtc_state.base;
+ crtc->state->crtc = crtc;
+
+ state->dpu_plane_states = alloc_dpu_plane_states(dpu_crtc);
+ if (IS_ERR(state->dpu_plane_states))
+ kfree(state);
+ }
+}
+
+static struct drm_crtc_state *
+dpu_drm_crtc_duplicate_state(struct drm_crtc *crtc)
+{
+ struct imx_crtc_state *imx_crtc_state;
+ struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc);
+ struct dpu_crtc_state *state, *copy;
+
+ if (WARN_ON(!crtc->state))
+ return NULL;
+
+ copy = kzalloc(sizeof(*copy), GFP_KERNEL);
+ if (!copy)
+ return NULL;
+
+ copy->dpu_plane_states = alloc_dpu_plane_states(dpu_crtc);
+ if (IS_ERR(copy->dpu_plane_states)) {
+ kfree(copy);
+ return NULL;
+ }
+
+ __drm_atomic_helper_crtc_duplicate_state(crtc,
+ &copy->imx_crtc_state.base);
+ imx_crtc_state = to_imx_crtc_state(crtc->state);
+ state = to_dpu_crtc_state(imx_crtc_state);
+ copy->use_pc = state->use_pc;
+ copy->crc.source = state->crc.source;
+ dpu_copy_roi(&state->crc.roi, &copy->crc.roi);
+
+ return &copy->imx_crtc_state.base;
+}
+
+static void dpu_drm_crtc_destroy_state(struct drm_crtc *crtc,
+ struct drm_crtc_state *state)
+{
+ struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(state);
+ struct dpu_crtc_state *dcstate;
+
+ if (state) {
+ __drm_atomic_helper_crtc_destroy_state(state);
+ dcstate = to_dpu_crtc_state(imx_crtc_state);
+ kfree(dcstate->dpu_plane_states);
+ kfree(dcstate);
+ }
+}
+
+static int dpu_enable_vblank(struct drm_crtc *crtc)
+{
+ struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc);
+
+ enable_irq(dpu_crtc->vbl_irq);
+
+ return 0;
+}
+
+static void dpu_disable_vblank(struct drm_crtc *crtc)
+{
+ struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc);
+
+ disable_irq_nosync(dpu_crtc->vbl_irq);
+}
+
+static const struct drm_crtc_funcs dpu_crtc_funcs = {
+ .set_config = drm_atomic_helper_set_config,
+ .destroy = drm_crtc_cleanup,
+ .page_flip = drm_atomic_helper_page_flip,
+ .reset = dpu_drm_crtc_reset,
+ .atomic_duplicate_state = dpu_drm_crtc_duplicate_state,
+ .atomic_destroy_state = dpu_drm_crtc_destroy_state,
+ .enable_vblank = dpu_enable_vblank,
+ .disable_vblank = dpu_disable_vblank,
+ .set_crc_source = dpu_crtc_set_crc_source,
+ .verify_crc_source = dpu_crtc_verify_crc_source,
+};
+
+static irqreturn_t dpu_vbl_irq_handler(int irq, void *dev_id)
+{
+ struct dpu_crtc *dpu_crtc = dev_id;
+ struct drm_crtc *crtc = &dpu_crtc->base;
+ unsigned long flags;
+
+ drm_crtc_handle_vblank(crtc);
+
+ spin_lock_irqsave(&crtc->dev->event_lock, flags);
+ if (dpu_crtc->event) {
+ drm_crtc_send_vblank_event(crtc, dpu_crtc->event);
+ dpu_crtc->event = NULL;
+ drm_crtc_vblank_put(crtc);
+ }
+ spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t dpu_safety_shdld_irq_handler(int irq, void *dev_id)
+{
+ struct dpu_crtc *dpu_crtc = dev_id;
+
+ complete(&dpu_crtc->safety_shdld_done);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t dpu_content_shdld_irq_handler(int irq, void *dev_id)
+{
+ struct dpu_crtc *dpu_crtc = dev_id;
+
+ complete(&dpu_crtc->content_shdld_done);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t dpu_dec_shdld_irq_handler(int irq, void *dev_id)
+{
+ struct dpu_crtc *dpu_crtc = dev_id;
+
+ complete(&dpu_crtc->dec_shdld_done);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t dpu_crc_shdld_irq_handler(int irq, void *dev_id)
+{
+ struct dpu_crtc *dpu_crtc = dev_id;
+
+ complete(&dpu_crtc->crc_shdld_done);
+
+ return IRQ_HANDLED;
+}
+
+static int dpu_crtc_atomic_check(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+{
+ struct drm_device *dev = crtc->dev;
+ struct drm_encoder *encoder;
+ struct drm_plane *plane;
+ struct drm_plane_state *plane_state;
+ struct dpu_plane_state *dpstate;
+ struct drm_crtc_state *crtc_state =
+ drm_atomic_get_new_crtc_state(state, crtc);
+ struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc_state);
+ struct dpu_crtc_state *dcstate = to_dpu_crtc_state(imx_crtc_state);
+ struct imx_crtc_state *old_imx_crtc_state =
+ to_imx_crtc_state(crtc->state);
+ struct dpu_crtc_state *old_dcstate =
+ to_dpu_crtc_state(old_imx_crtc_state);
+ struct drm_display_mode *mode = &crtc_state->adjusted_mode;
+ struct videomode vm;
+ unsigned long encoder_type = DRM_MODE_ENCODER_NONE;
+ u32 encoder_mask;
+ int i = 0;
+
+ list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
+ encoder_mask = 1 << drm_encoder_index(encoder);
+
+ if (!(crtc_state->encoder_mask & encoder_mask))
+ continue;
+
+ encoder_type = encoder->encoder_type;
+ }
+
+ if (crtc_state->enable && dcstate->use_pc) {
+ if (encoder_type != DRM_MODE_ENCODER_TMDS) {
+ DRM_DEBUG_KMS("[CRTC:%d:%s] enc type %lu doesn't support pc\n",
+ crtc->base.id, crtc->name, encoder_type);
+ return -EINVAL;
+ }
+
+ drm_display_mode_to_videomode(mode, &vm);
+ if ((vm.hactive % 2) || (vm.hfront_porch % 2) ||
+ (vm.hsync_len % 2) || (vm.hback_porch % 2)) {
+ DRM_DEBUG_KMS("[CRTC:%d:%s] video mode is invalid\n",
+ crtc->base.id, crtc->name);
+ return -EINVAL;
+ }
+ }
+
+ /* disallow to enable CRC when CRTC keeps at inactive status */
+ if (!crtc->state->active && !crtc_state->enable &&
+ to_enable_dpu_crc(dcstate, old_dcstate))
+ return -EINVAL;
+
+ if (crtc_state->enable && dcstate->crc.source == DPU_CRC_SRC_FRAMEGEN) {
+ dcstate->crc.roi.x1 = 0;
+ dcstate->crc.roi.y1 = 0;
+ dcstate->crc.roi.x2 = mode->hdisplay;
+ dcstate->crc.roi.y2 = mode->vdisplay;
+ }
+
+ if (crtc_state->enable && dcstate->crc.source != DPU_CRC_SRC_NONE) {
+ if (dcstate->crc.roi.x1 < 0 || dcstate->crc.roi.y1 < 0)
+ return -EINVAL;
+
+ if (dcstate->crc.roi.x2 > mode->hdisplay ||
+ dcstate->crc.roi.y2 > mode->vdisplay)
+ return -EINVAL;
+
+ if (!drm_rect_visible(&dcstate->crc.roi))
+ return -EINVAL;
+ }
+
+ /*
+ * cache the plane states so that the planes can be disabled in
+ * ->atomic_begin.
+ */
+ drm_for_each_plane_mask(plane, crtc->dev, crtc_state->plane_mask) {
+ plane_state =
+ drm_atomic_get_plane_state(crtc_state->state, plane);
+ if (IS_ERR(plane_state))
+ return PTR_ERR(plane_state);
+
+ dpstate = to_dpu_plane_state(plane_state);
+ dcstate->dpu_plane_states[i++] = dpstate;
+ }
+
+ return 0;
+}
+
+static void dpu_crtc_atomic_begin(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+{
+ struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc);
+ struct drm_crtc_state *old_crtc_state =
+ drm_atomic_get_old_crtc_state(state, crtc);
+ struct imx_crtc_state *old_imx_crtc_state =
+ to_imx_crtc_state(old_crtc_state);
+ struct dpu_crtc_state *old_dcstate =
+ to_dpu_crtc_state(old_imx_crtc_state);
+ int i;
+
+ /*
+ * Disable all planes' resources in SHADOW only.
+ * Whether any of them would be disabled or kept running depends
+ * on new plane states' commit.
+ */
+ for (i = 0; i < dpu_crtc->hw_plane_num; i++) {
+ struct dpu_plane_state *old_dpstate;
+ struct drm_plane_state *plane_state;
+ struct dpu_plane *dplane;
+ struct dpu_plane_res *res;
+ struct dpu_fetchunit *fu;
+ struct dpu_fetchunit *fe = NULL;
+ struct dpu_hscaler *hs = NULL;
+ struct dpu_vscaler *vs = NULL;
+ struct dpu_layerblend *lb;
+ struct dpu_extdst *ed;
+ extdst_src_sel_t ed_src;
+ dpu_block_id_t blend;
+ lb_sec_sel_t source;
+ unsigned int stream_id;
+ int lb_id;
+ bool release_aux_source;
+
+ old_dpstate = old_dcstate->dpu_plane_states[i];
+ if (!old_dpstate)
+ continue;
+
+ plane_state = &old_dpstate->base;
+ dplane = to_dpu_plane(plane_state->plane);
+ res = &dplane->grp->res;
+
+ release_aux_source = false;
+again:
+ if (old_dcstate->use_pc) {
+ if (release_aux_source) {
+ source = old_dpstate->aux_source;
+ blend = old_dpstate->aux_blend;
+ stream_id = 1;
+ } else {
+ source = old_dpstate->source;
+ blend = old_dpstate->blend;
+ stream_id = old_dpstate->left_src_w ? 0 : 1;
+ }
+ } else {
+ source = old_dpstate->source;
+ blend = old_dpstate->blend;
+ stream_id = dplane->stream_id;
+ }
+
+ fu = source_to_fu(res, source);
+ if (!fu)
+ return;
+
+ lb_id = blend_to_id(blend);
+ if (lb_id < 0)
+ return;
+
+ lb = res->lb[lb_id];
+
+ layerblend_pixengcfg_clken(lb, CLKEN__DISABLE);
+ if (fetchunit_is_fetchdecode(fu)) {
+ fe = fetchdecode_get_fetcheco(fu);
+ hs = fetchdecode_get_hscaler(fu);
+ vs = fetchdecode_get_vscaler(fu);
+ hscaler_pixengcfg_clken(hs, CLKEN__DISABLE);
+ vscaler_pixengcfg_clken(vs, CLKEN__DISABLE);
+ hscaler_mode(hs, SCALER_NEUTRAL);
+ vscaler_mode(vs, SCALER_NEUTRAL);
+ }
+ if ((!old_dcstate->use_pc && old_dpstate->is_top) ||
+ (old_dcstate->use_pc &&
+ ((!stream_id && old_dpstate->is_left_top) ||
+ (stream_id && old_dpstate->is_right_top)))) {
+ ed = res->ed[stream_id];
+ ed_src = stream_id ?
+ ED_SRC_CONSTFRAME1 : ED_SRC_CONSTFRAME0;
+ extdst_pixengcfg_src_sel(ed, ed_src);
+ }
+
+ fu->ops->disable_src_buf(fu);
+ if (fetchunit_is_fetchdecode(fu)) {
+ fetchdecode_pixengcfg_dynamic_src_sel(fu,
+ FD_SRC_DISABLE);
+ fe->ops->disable_src_buf(fe);
+ }
+
+ if (old_dpstate->need_aux_source && !release_aux_source) {
+ release_aux_source = true;
+ goto again;
+ }
+ }
+}
+
+static void dpu_crtc_atomic_flush(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+{
+ struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc), *aux_dpu_crtc = NULL;
+ struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc->state);
+ struct drm_crtc_state *old_crtc_state =
+ drm_atomic_get_old_crtc_state(state, crtc);
+ struct imx_crtc_state *old_imx_crtc_state =
+ to_imx_crtc_state(old_crtc_state);
+ struct dpu_crtc_state *dcstate = to_dpu_crtc_state(imx_crtc_state);
+ struct dpu_crtc_state *old_dcstate =
+ to_dpu_crtc_state(old_imx_crtc_state);
+ struct dpu_plane *dplane = to_dpu_plane(crtc->primary);
+ struct dpu_plane_state *old_dpstate;
+ struct dpu_plane_res *res = &dplane->grp->res;
+ struct dpu_extdst *ed = res->ed[dplane->stream_id], *aux_ed;
+ struct dpu_fetchunit *fu;
+ lb_sec_sel_t source;
+ struct completion *shdld_done;
+ struct completion *m_content_shdld_done = NULL;
+ struct completion *s_content_shdld_done = NULL;
+ unsigned long ret, flags;
+ int i;
+ bool need_modeset = drm_atomic_crtc_needs_modeset(crtc->state);
+ bool need_wait4fgfcm = false, need_aux_wait4fgfcm = false;
+ bool use_prefetch;
+
+ if (!crtc->state->active && !old_crtc_state->active)
+ return;
+
+ if (!need_modeset && to_disable_dpu_crc(dcstate, old_dcstate))
+ dpu_crtc_disable_crc_source(crtc, old_dcstate->use_pc);
+
+ /*
+ * Scan over old plane fetchunits to determine if we
+ * need to wait for FrameGen frame counter moving in
+ * the next loop prior to DPRC repeat_en disablement
+ * or not.
+ */
+ for (i = 0; i < dpu_crtc->hw_plane_num; i++) {
+ bool aux_source_flag;
+
+ old_dpstate = old_dcstate->dpu_plane_states[i];
+ if (!old_dpstate)
+ continue;
+
+ aux_source_flag = false;
+again1:
+ source = aux_source_flag ?
+ old_dpstate->aux_source : old_dpstate->source;
+ use_prefetch = aux_source_flag ?
+ old_dpstate->use_aux_prefetch :
+ old_dpstate->use_prefetch;
+ fu = source_to_fu(res, source);
+ if (!fu)
+ return;
+
+ if (!fu->ops->is_enabled(fu) && use_prefetch && !need_modeset) {
+ if (aux_source_flag)
+ need_aux_wait4fgfcm = true;
+ else
+ need_wait4fgfcm = true;
+ }
+
+ if (old_dpstate->need_aux_source && !aux_source_flag) {
+ aux_source_flag = true;
+ goto again1;
+ }
+ }
+
+ /*
+ * Sync with FrameGen frame counter moving so that we may disable
+ * repeat_en of DPRC(s) and fetchunit(s) correctly.
+ */
+ if (need_wait4fgfcm || need_aux_wait4fgfcm) {
+ /*
+ * don't relinquish CPU until DPRC repeat_en is disabled and
+ * ExtDst shadow load is initiated to disable fetchunit(s).
+ */
+ local_irq_save(flags);
+ preempt_disable();
+ framegen_wait_for_frame_counter_moving(dcstate->use_pc ?
+ dpu_crtc->m_fg :
+ dpu_crtc->fg);
+ }
+
+ for (i = 0; i < dpu_crtc->hw_plane_num; i++) {
+ struct dpu_fetchunit *fe;
+ struct dpu_hscaler *hs;
+ struct dpu_vscaler *vs;
+ bool aux_source_disable;
+
+ old_dpstate = old_dcstate->dpu_plane_states[i];
+ if (!old_dpstate)
+ continue;
+
+ aux_source_disable = false;
+again2:
+ source = aux_source_disable ?
+ old_dpstate->aux_source : old_dpstate->source;
+ use_prefetch = aux_source_disable ?
+ old_dpstate->use_aux_prefetch :
+ old_dpstate->use_prefetch;
+ fu = source_to_fu(res, source);
+ if (!fu) {
+ if (need_wait4fgfcm || need_aux_wait4fgfcm) {
+ local_irq_restore(flags);
+ preempt_enable();
+ }
+ return;
+ }
+
+ if (!fu->ops->is_enabled(fu)) {
+ fu->ops->set_stream_id(fu, DPU_PLANE_SRC_DISABLED);
+ if (fu->dprc && use_prefetch)
+ dprc_disable_repeat_en(fu->dprc);
+ }
+
+ if (!fetchunit_is_fetchdecode(fu))
+ continue;
+
+ fe = fetchdecode_get_fetcheco(fu);
+ if (!fe->ops->is_enabled(fe))
+ fe->ops->set_stream_id(fe, DPU_PLANE_SRC_DISABLED);
+
+ hs = fetchdecode_get_hscaler(fu);
+ if (!hscaler_is_enabled(hs))
+ hscaler_set_stream_id(hs, DPU_PLANE_SRC_DISABLED);
+
+ vs = fetchdecode_get_vscaler(fu);
+ if (!vscaler_is_enabled(vs))
+ vscaler_set_stream_id(vs, DPU_PLANE_SRC_DISABLED);
+
+ if (old_dpstate->need_aux_source && !aux_source_disable) {
+ aux_source_disable = true;
+ goto again2;
+ }
+ }
+
+ if (dcstate->use_pc) {
+ aux_dpu_crtc = dpu_crtc_get_aux_dpu_crtc(dpu_crtc);
+
+ if (dpu_crtc->aux_is_master) {
+ m_content_shdld_done = &aux_dpu_crtc->content_shdld_done;
+ s_content_shdld_done = &dpu_crtc->content_shdld_done;
+ } else {
+ m_content_shdld_done = &dpu_crtc->content_shdld_done;
+ s_content_shdld_done = &aux_dpu_crtc->content_shdld_done;
+ }
+ }
+
+ if (!need_modeset) {
+ enable_irq(dpu_crtc->content_shdld_irq);
+ if (dcstate->use_pc)
+ enable_irq(aux_dpu_crtc->content_shdld_irq);
+
+ if (dcstate->use_pc) {
+ if (extdst_is_master(ed)) {
+ extdst_pixengcfg_sync_trigger(ed);
+ } else {
+ aux_ed = dpu_aux_ed_peek(ed);
+ extdst_pixengcfg_sync_trigger(aux_ed);
+ }
+ } else {
+ extdst_pixengcfg_sync_trigger(ed);
+ }
+
+ if (need_wait4fgfcm || need_aux_wait4fgfcm) {
+ local_irq_restore(flags);
+ preempt_enable();
+ }
+
+ if (dcstate->use_pc) {
+ shdld_done = m_content_shdld_done;
+ ret = wait_for_completion_timeout(shdld_done, HZ);
+ if (ret == 0)
+ DRM_WARN("[CRTC:%d:%s] %s: wait for master content shdld done timeout\n",
+ crtc->base.id, crtc->name, __func__);
+
+ shdld_done = s_content_shdld_done;
+ ret = wait_for_completion_timeout(shdld_done, HZ);
+ if (ret == 0)
+ DRM_WARN("[CRTC:%d:%s] %s: wait for slave content shdld done timeout\n",
+ crtc->base.id, crtc->name, __func__);
+ } else {
+ shdld_done = &dpu_crtc->content_shdld_done;
+ ret = wait_for_completion_timeout(shdld_done, HZ);
+ if (ret == 0)
+ DRM_WARN("[CRTC:%d:%s] %s: wait for content shdld done timeout\n",
+ crtc->base.id, crtc->name, __func__);
+ }
+
+ disable_irq(dpu_crtc->content_shdld_irq);
+ if (dcstate->use_pc)
+ disable_irq(aux_dpu_crtc->content_shdld_irq);
+
+ if (dcstate->use_pc) {
+ if (framegen_secondary_requests_to_read_empty_fifo(dpu_crtc->m_fg)) {
+ framegen_secondary_clear_channel_status(dpu_crtc->m_fg);
+ DRM_WARN("[CRTC:%d:%s] %s: master FrameGen requests to read empty FIFO\n",
+ crtc->base.id, crtc->name, __func__);
+ }
+ if (framegen_secondary_requests_to_read_empty_fifo(dpu_crtc->s_fg)) {
+ framegen_secondary_clear_channel_status(dpu_crtc->s_fg);
+ DRM_WARN("[CRTC:%d:%s] %s: slave FrameGen requests to read empty FIFO\n",
+ crtc->base.id, crtc->name, __func__);
+ }
+ } else {
+ if (framegen_secondary_requests_to_read_empty_fifo(dpu_crtc->fg)) {
+ framegen_secondary_clear_channel_status(dpu_crtc->fg);
+ DRM_WARN("[CRTC:%d:%s] %s: FrameGen requests to read empty FIFO\n",
+ crtc->base.id, crtc->name, __func__);
+ }
+ }
+
+ dpu_crtc_queue_state_event(crtc);
+ } else if (!crtc->state->active) {
+ if (old_dcstate->use_pc) {
+ if (extdst_is_master(ed)) {
+ extdst_pixengcfg_sync_trigger(ed);
+ } else {
+ aux_ed = dpu_aux_ed_peek(ed);
+ extdst_pixengcfg_sync_trigger(aux_ed);
+ }
+ } else {
+ extdst_pixengcfg_sync_trigger(ed);
+ }
+ }
+
+ if (!need_modeset && to_enable_dpu_crc(dcstate, old_dcstate))
+ dpu_crtc_enable_crc_source(crtc,
+ dcstate->crc.source, &dcstate->crc.roi);
+}
+
+static void dpu_crtc_mode_set_nofb(struct drm_crtc *crtc)
+{
+ struct drm_device *dev = crtc->dev;
+ struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc);
+ struct dpu_crtc *aux_dpu_crtc = dpu_crtc_get_aux_dpu_crtc(dpu_crtc);
+ struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc->state);
+ struct dpu_crtc_state *dcstate = to_dpu_crtc_state(imx_crtc_state);
+ struct drm_display_mode *mode = &crtc->state->adjusted_mode;
+ struct dpu_plane *dplane = to_dpu_plane(crtc->primary);
+ struct dpu_plane_res *res = &dplane->grp->res;
+ struct dpu_constframe *pa_cf, *sa_cf;
+ struct dpu_disengcfg *dec;
+ struct dpu_extdst *ed, *plane_ed;
+ struct dpu_framegen *fg;
+ struct dpu_tcon *tcon;
+ struct dpu_store *st;
+ struct drm_encoder *encoder;
+ unsigned long encoder_type = DRM_MODE_ENCODER_NONE;
+ unsigned int stream_id;
+ int crtc_hdisplay = dcstate->use_pc ?
+ (mode->crtc_hdisplay >> 1) : mode->crtc_hdisplay;
+ extdst_src_sel_t ed_src;
+ bool cfg_aux_pipe = false;
+
+ DRM_DEBUG_KMS("[CRTC:%d:%s] %s: mode->hdisplay: %d\n",
+ crtc->base.id, crtc->name, __func__, mode->hdisplay);
+ DRM_DEBUG_KMS("[CRTC:%d:%s] %s: mode->vdisplay: %d\n",
+ crtc->base.id, crtc->name, __func__, mode->vdisplay);
+ DRM_DEBUG_KMS("[CRTC:%d:%s] %s: mode->clock: %dKHz\n",
+ crtc->base.id, crtc->name, __func__, mode->clock);
+ DRM_DEBUG_KMS("[CRTC:%d:%s] %s: mode->vrefresh: %dHz\n",
+ crtc->base.id, crtc->name, __func__,
+ drm_mode_vrefresh(mode));
+ if (dcstate->use_pc)
+ DRM_DEBUG_KMS("[CRTC:%d:%s] %s: use pixel combiner\n",
+ crtc->base.id, crtc->name, __func__);
+
+ list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
+ if (encoder->crtc == crtc) {
+ encoder_type = encoder->encoder_type;
+ break;
+ }
+ }
+
+again:
+ if (cfg_aux_pipe) {
+ pa_cf = dpu_crtc->aux_pa_cf;
+ sa_cf = dpu_crtc->aux_sa_cf;
+ dec = dpu_crtc->aux_dec;
+ ed = dpu_crtc->aux_ed;
+ fg = dpu_crtc->aux_fg;
+ tcon = dpu_crtc->aux_tcon;
+ st = aux_dpu_crtc->st;
+ stream_id = dpu_crtc->stream_id ^ 1;
+ } else {
+ pa_cf = dpu_crtc->pa_cf;
+ sa_cf = dpu_crtc->sa_cf;
+ dec = dpu_crtc->dec;
+ ed = dpu_crtc->ed;
+ fg = dpu_crtc->fg;
+ tcon = dpu_crtc->tcon;
+ st = dpu_crtc->st;
+ stream_id = dpu_crtc->stream_id;
+ }
+
+ if (dcstate->use_pc) {
+ store_pixengcfg_syncmode_fixup(st, true);
+ framegen_syncmode_fixup(fg,
+ framegen_is_master(fg) ? false : true);
+ framegen_syncmode(fg, framegen_is_master(fg) ?
+ FGSYNCMODE__MASTER : FGSYNCMODE__SLAVE_ONCE);
+ } else {
+ store_pixengcfg_syncmode_fixup(st, false);
+ framegen_syncmode_fixup(fg, false);
+ framegen_syncmode(fg, FGSYNCMODE__OFF);
+ }
+
+ framegen_cfg_videomode(fg, mode, dcstate->use_pc, encoder_type);
+ framegen_displaymode(fg, FGDM__SEC_ON_TOP);
+
+ framegen_panic_displaymode(fg, FGDM__TEST);
+
+ tcon_cfg_videomode(tcon, mode, dcstate->use_pc);
+ tcon_set_fmt(tcon, imx_crtc_state->bus_format);
+ tcon_configure_pc(tcon, stream_id, mode->crtc_hdisplay,
+ dcstate->use_pc ? PC_COMBINE : PC_BYPASS, 0);
+
+ constframe_framedimensions(pa_cf, crtc_hdisplay, mode->crtc_vdisplay);
+ constframe_framedimensions(sa_cf, crtc_hdisplay, mode->crtc_vdisplay);
+ constframe_constantcolor(sa_cf, 0, 0, 0, 0);
+
+ ed_src = stream_id ? ED_SRC_CONSTFRAME5 : ED_SRC_CONSTFRAME4;
+ extdst_pixengcfg_src_sel(ed, ed_src);
+
+ plane_ed = res->ed[stream_id];
+ ed_src = stream_id ? ED_SRC_CONSTFRAME1 : ED_SRC_CONSTFRAME0;
+ extdst_pixengcfg_src_sel(plane_ed, ed_src);
+
+ if (dcstate->use_pc && !cfg_aux_pipe) {
+ cfg_aux_pipe = true;
+ goto again;
+ }
+}
+
+static const struct drm_crtc_helper_funcs dpu_helper_funcs = {
+ .mode_set_nofb = dpu_crtc_mode_set_nofb,
+ .atomic_check = dpu_crtc_atomic_check,
+ .atomic_begin = dpu_crtc_atomic_begin,
+ .atomic_flush = dpu_crtc_atomic_flush,
+ .atomic_enable = dpu_crtc_atomic_enable,
+ .atomic_disable = dpu_crtc_atomic_disable,
+};
+
+static void dpu_crtc_put_resources(struct dpu_crtc *dpu_crtc)
+{
+ if (!IS_ERR_OR_NULL(dpu_crtc->pa_cf))
+ dpu_cf_put(dpu_crtc->pa_cf);
+ if (!IS_ERR_OR_NULL(dpu_crtc->sa_cf))
+ dpu_cf_put(dpu_crtc->sa_cf);
+ if (!IS_ERR_OR_NULL(dpu_crtc->dec))
+ dpu_dec_put(dpu_crtc->dec);
+ if (!IS_ERR_OR_NULL(dpu_crtc->ed))
+ dpu_ed_put(dpu_crtc->ed);
+ if (!IS_ERR_OR_NULL(dpu_crtc->fg))
+ dpu_fg_put(dpu_crtc->fg);
+ if (!IS_ERR_OR_NULL(dpu_crtc->sig))
+ dpu_sig_put(dpu_crtc->sig);
+ if (!IS_ERR_OR_NULL(dpu_crtc->tcon))
+ dpu_tcon_put(dpu_crtc->tcon);
+}
+
+static int dpu_crtc_get_resources(struct dpu_crtc *dpu_crtc)
+{
+ struct dpu_soc *dpu = dev_get_drvdata(dpu_crtc->dev->parent);
+ unsigned int stream_id = dpu_crtc->stream_id;
+ int ret;
+
+ dpu_crtc->pa_cf = dpu_cf_get(dpu, stream_id + 4);
+ if (IS_ERR(dpu_crtc->pa_cf)) {
+ ret = PTR_ERR(dpu_crtc->pa_cf);
+ goto err_out;
+ }
+ dpu_crtc->aux_pa_cf = dpu_aux_cf_peek(dpu_crtc->pa_cf);
+
+ dpu_crtc->sa_cf = dpu_cf_get(dpu, stream_id);
+ if (IS_ERR(dpu_crtc->sa_cf)) {
+ ret = PTR_ERR(dpu_crtc->sa_cf);
+ goto err_out;
+ }
+ dpu_crtc->aux_sa_cf = dpu_aux_cf_peek(dpu_crtc->sa_cf);
+
+ dpu_crtc->dec = dpu_dec_get(dpu, stream_id);
+ if (IS_ERR(dpu_crtc->dec)) {
+ ret = PTR_ERR(dpu_crtc->dec);
+ goto err_out;
+ }
+ dpu_crtc->aux_dec = dpu_aux_dec_peek(dpu_crtc->dec);
+
+ dpu_crtc->ed = dpu_ed_get(dpu, stream_id + 4);
+ if (IS_ERR(dpu_crtc->ed)) {
+ ret = PTR_ERR(dpu_crtc->ed);
+ goto err_out;
+ }
+ dpu_crtc->aux_ed = dpu_aux_ed_peek(dpu_crtc->ed);
+
+ dpu_crtc->fg = dpu_fg_get(dpu, stream_id);
+ if (IS_ERR(dpu_crtc->fg)) {
+ ret = PTR_ERR(dpu_crtc->fg);
+ goto err_out;
+ }
+ dpu_crtc->aux_fg = dpu_aux_fg_peek(dpu_crtc->fg);
+
+ dpu_crtc->sig = dpu_sig_get(dpu, stream_id);
+ if (IS_ERR(dpu_crtc->sig)) {
+ ret = PTR_ERR(dpu_crtc->sig);
+ goto err_out;
+ }
+ dpu_crtc->aux_sig = dpu_aux_sig_peek(dpu_crtc->sig);
+
+ dpu_crtc->tcon = dpu_tcon_get(dpu, stream_id);
+ if (IS_ERR(dpu_crtc->tcon)) {
+ ret = PTR_ERR(dpu_crtc->tcon);
+ goto err_out;
+ }
+ dpu_crtc->aux_tcon = dpu_aux_tcon_peek(dpu_crtc->tcon);
+
+ if (dpu_crtc->aux_is_master) {
+ dpu_crtc->m_pa_cf = dpu_crtc->aux_pa_cf;
+ dpu_crtc->m_sa_cf = dpu_crtc->aux_sa_cf;
+ dpu_crtc->m_dec = dpu_crtc->aux_dec;
+ dpu_crtc->m_ed = dpu_crtc->aux_ed;
+ dpu_crtc->m_fg = dpu_crtc->aux_fg;
+ dpu_crtc->m_tcon = dpu_crtc->aux_tcon;
+
+ dpu_crtc->s_pa_cf = dpu_crtc->pa_cf;
+ dpu_crtc->s_sa_cf = dpu_crtc->sa_cf;
+ dpu_crtc->s_dec = dpu_crtc->dec;
+ dpu_crtc->s_ed = dpu_crtc->ed;
+ dpu_crtc->s_fg = dpu_crtc->fg;
+ dpu_crtc->s_tcon = dpu_crtc->tcon;
+ } else {
+ dpu_crtc->m_pa_cf = dpu_crtc->pa_cf;
+ dpu_crtc->m_sa_cf = dpu_crtc->sa_cf;
+ dpu_crtc->m_dec = dpu_crtc->dec;
+ dpu_crtc->m_ed = dpu_crtc->ed;
+ dpu_crtc->m_fg = dpu_crtc->fg;
+ dpu_crtc->m_tcon = dpu_crtc->tcon;
+
+ dpu_crtc->s_pa_cf = dpu_crtc->aux_pa_cf;
+ dpu_crtc->s_sa_cf = dpu_crtc->aux_sa_cf;
+ dpu_crtc->s_dec = dpu_crtc->aux_dec;
+ dpu_crtc->s_ed = dpu_crtc->aux_ed;
+ dpu_crtc->s_fg = dpu_crtc->aux_fg;
+ dpu_crtc->s_tcon = dpu_crtc->aux_tcon;
+ }
+
+ return 0;
+err_out:
+ dpu_crtc_put_resources(dpu_crtc);
+
+ return ret;
+}
+
+static int dpu_crtc_init(struct dpu_crtc *dpu_crtc,
+ struct dpu_client_platformdata *pdata, struct drm_device *drm)
+{
+ struct dpu_soc *dpu = dev_get_drvdata(dpu_crtc->dev->parent);
+ struct device *dev = dpu_crtc->dev;
+ struct drm_crtc *crtc = &dpu_crtc->base;
+ struct dpu_plane_grp *plane_grp = pdata->plane_grp;
+ unsigned int stream_id = pdata->stream_id;
+ int i, ret;
+
+ init_completion(&dpu_crtc->safety_shdld_done);
+ init_completion(&dpu_crtc->content_shdld_done);
+ init_completion(&dpu_crtc->dec_shdld_done);
+ init_completion(&dpu_crtc->crc_shdld_done);
+ init_completion(&dpu_crtc->aux_crc_done);
+
+ dpu_crtc->stream_id = stream_id;
+ dpu_crtc->crtc_grp_id = pdata->di_grp_id;
+ dpu_crtc->hw_plane_num = plane_grp->hw_plane_num;
+ dpu_crtc->syncmode_min_prate = dpu_get_syncmode_min_prate(dpu);
+ dpu_crtc->singlemode_max_width = dpu_get_singlemode_max_width(dpu);
+ dpu_crtc->master_stream_id = dpu_get_master_stream_id(dpu);
+ dpu_crtc->aux_is_master = !(dpu_crtc->master_stream_id == stream_id);
+ dpu_crtc->st = pdata->st9;
+
+ dpu_crtc->plane = devm_kcalloc(dev, dpu_crtc->hw_plane_num,
+ sizeof(*dpu_crtc->plane), GFP_KERNEL);
+ if (!dpu_crtc->plane)
+ return -ENOMEM;
+
+ ret = dpu_crtc_get_resources(dpu_crtc);
+ if (ret) {
+ DRM_DEV_ERROR(dev, "getting resources failed with %d.\n", ret);
+ return ret;
+ }
+
+ plane_grp->res.fg[stream_id] = dpu_crtc->fg;
+ dpu_crtc->plane[0] = dpu_plane_create(drm, 0, stream_id, plane_grp,
+ DRM_PLANE_TYPE_PRIMARY);
+ if (IS_ERR(dpu_crtc->plane[0])) {
+ ret = PTR_ERR(dpu_crtc->plane[0]);
+ DRM_DEV_ERROR(dev,
+ "initializing plane0 failed with %d.\n", ret);
+ goto err_put_resources;
+ }
+
+ crtc->port = pdata->of_node;
+ drm_crtc_helper_add(crtc, &dpu_helper_funcs);
+ ret = drm_crtc_init_with_planes(drm, crtc, &dpu_crtc->plane[0]->base, NULL,
+ &dpu_crtc_funcs, NULL);
+ if (ret) {
+ DRM_DEV_ERROR(dev, "adding crtc failed with %d.\n", ret);
+ goto err_put_resources;
+ }
+
+ for (i = 1; i < dpu_crtc->hw_plane_num; i++) {
+ dpu_crtc->plane[i] = dpu_plane_create(drm,
+ drm_crtc_mask(&dpu_crtc->base),
+ stream_id, plane_grp,
+ DRM_PLANE_TYPE_OVERLAY);
+ if (IS_ERR(dpu_crtc->plane[i])) {
+ ret = PTR_ERR(dpu_crtc->plane[i]);
+ DRM_DEV_ERROR(dev,
+ "initializing plane%d failed with %d.\n",
+ i, ret);
+ goto err_put_resources;
+ }
+ }
+
+ dpu_crtc->vbl_irq = dpu_map_irq(dpu, stream_id ?
+ IRQ_DISENGCFG_FRAMECOMPLETE1 :
+ IRQ_DISENGCFG_FRAMECOMPLETE0);
+ irq_set_status_flags(dpu_crtc->vbl_irq, IRQ_DISABLE_UNLAZY);
+ ret = devm_request_irq(dev, dpu_crtc->vbl_irq, dpu_vbl_irq_handler, 0,
+ "imx_drm", dpu_crtc);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dev, "vblank irq request failed with %d.\n", ret);
+ goto err_put_resources;
+ }
+ disable_irq(dpu_crtc->vbl_irq);
+
+ dpu_crtc->safety_shdld_irq = dpu_map_irq(dpu, stream_id ?
+ IRQ_EXTDST5_SHDLOAD : IRQ_EXTDST4_SHDLOAD);
+ irq_set_status_flags(dpu_crtc->safety_shdld_irq, IRQ_DISABLE_UNLAZY);
+ ret = devm_request_irq(dev, dpu_crtc->safety_shdld_irq,
+ dpu_safety_shdld_irq_handler, 0, "imx_drm",
+ dpu_crtc);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dev,
+ "safety shadow load irq request failed with %d.\n",
+ ret);
+ goto err_put_resources;
+ }
+ disable_irq(dpu_crtc->safety_shdld_irq);
+
+ dpu_crtc->content_shdld_irq = dpu_map_irq(dpu, stream_id ?
+ IRQ_EXTDST1_SHDLOAD : IRQ_EXTDST0_SHDLOAD);
+ irq_set_status_flags(dpu_crtc->content_shdld_irq, IRQ_DISABLE_UNLAZY);
+ ret = devm_request_irq(dev, dpu_crtc->content_shdld_irq,
+ dpu_content_shdld_irq_handler, 0, "imx_drm",
+ dpu_crtc);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dev,
+ "content shadow load irq request failed with %d.\n",
+ ret);
+ goto err_put_resources;
+ }
+ disable_irq(dpu_crtc->content_shdld_irq);
+
+ dpu_crtc->dec_shdld_irq = dpu_map_irq(dpu, stream_id ?
+ IRQ_DISENGCFG_SHDLOAD1 : IRQ_DISENGCFG_SHDLOAD0);
+ irq_set_status_flags(dpu_crtc->dec_shdld_irq, IRQ_DISABLE_UNLAZY);
+ ret = devm_request_irq(dev, dpu_crtc->dec_shdld_irq,
+ dpu_dec_shdld_irq_handler, 0, "imx_drm",
+ dpu_crtc);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dev,
+ "DEC shadow load irq request failed with %d.\n",
+ ret);
+ goto err_put_resources;
+ }
+ disable_irq(dpu_crtc->dec_shdld_irq);
+
+ dpu_crtc->crc_valid_irq = dpu_map_irq(dpu, stream_id ?
+ IRQ_SIG1_VALID : IRQ_SIG0_VALID);
+ irq_set_status_flags(dpu_crtc->crc_valid_irq, IRQ_DISABLE_UNLAZY);
+ ret = devm_request_threaded_irq(dev, dpu_crtc->crc_valid_irq, NULL,
+ dpu_crc_valid_irq_threaded_handler,
+ IRQF_ONESHOT, "imx_drm", dpu_crtc);
+ if (ret < 0) {
+ dev_err(dev,
+ "CRC valid irq request failed with %d.\n",
+ ret);
+ goto err_put_resources;
+ }
+ disable_irq(dpu_crtc->crc_valid_irq);
+
+ dpu_crtc->crc_shdld_irq = dpu_map_irq(dpu, stream_id ?
+ IRQ_SIG1_SHDLOAD : IRQ_SIG0_SHDLOAD);
+ irq_set_status_flags(dpu_crtc->crc_shdld_irq, IRQ_DISABLE_UNLAZY);
+ ret = devm_request_irq(dev, dpu_crtc->crc_shdld_irq,
+ dpu_crc_shdld_irq_handler, 0, "imx_drm",
+ dpu_crtc);
+ if (ret < 0) {
+ dev_err(dev,
+ "CRC shadow load irq request failed with %d.\n",
+ ret);
+ goto err_put_resources;
+ }
+ disable_irq(dpu_crtc->crc_shdld_irq);
+
+ return 0;
+
+err_put_resources:
+ dpu_crtc_put_resources(dpu_crtc);
+
+ return ret;
+}
+
+static int dpu_crtc_bind(struct device *dev, struct device *master, void *data)
+{
+ struct dpu_client_platformdata *pdata = dev->platform_data;
+ struct drm_device *drm = data;
+ struct dpu_crtc *dpu_crtc = dev_get_drvdata(dev);
+ int ret;
+
+ dpu_crtc->dev = dev;
+
+ drm->mode_config.max_width = 5120;
+ drm->mode_config.max_height = 4096;
+
+ ret = dpu_crtc_init(dpu_crtc, pdata, drm);
+ if (ret)
+ return ret;
+
+ if (!drm->mode_config.funcs)
+ drm->mode_config.funcs = &dpu_drm_mode_config_funcs;
+
+ return 0;
+}
+
+static void dpu_crtc_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct dpu_crtc *dpu_crtc = dev_get_drvdata(dev);
+
+ dpu_crtc_put_resources(dpu_crtc);
+}
+
+static const struct component_ops dpu_crtc_ops = {
+ .bind = dpu_crtc_bind,
+ .unbind = dpu_crtc_unbind,
+};
+
+static int dpu_crtc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct dpu_crtc *dpu_crtc;
+
+ if (!dev->platform_data)
+ return -EINVAL;
+
+ dpu_crtc = devm_kzalloc(dev, sizeof(*dpu_crtc), GFP_KERNEL);
+ if (!dpu_crtc)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, dpu_crtc);
+
+ return component_add(dev, &dpu_crtc_ops);
+}
+
+static int dpu_crtc_remove(struct platform_device *pdev)
+{
+ component_del(&pdev->dev, &dpu_crtc_ops);
+ return 0;
+}
+
+static struct platform_driver dpu_crtc_driver = {
+ .driver = {
+ .name = "imx-dpu-crtc",
+ },
+ .probe = dpu_crtc_probe,
+ .remove = dpu_crtc_remove,
+};
+module_platform_driver(dpu_crtc_driver);
+
+MODULE_AUTHOR("NXP Semiconductor");
+MODULE_DESCRIPTION("i.MX DPU CRTC");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imx-dpu-crtc");
diff --git a/drivers/gpu/drm/imx/dpu/dpu-crtc.h b/drivers/gpu/drm/imx/dpu/dpu-crtc.h
new file mode 100644
index 000000000000..423fe1339cf2
--- /dev/null
+++ b/drivers/gpu/drm/imx/dpu/dpu-crtc.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2017-2020 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef _DPU_CRTC_H_
+#define _DPU_CRTC_H_
+
+#include <drm/drm_vblank.h>
+#include <video/dpu.h>
+#include "dpu-plane.h"
+#include "../imx-drm.h"
+
+struct dpu_crtc {
+ struct device *dev;
+ struct drm_crtc base;
+ struct imx_drm_crtc *imx_crtc;
+ struct dpu_constframe *pa_cf;
+ struct dpu_constframe *sa_cf;
+ struct dpu_disengcfg *dec;
+ struct dpu_extdst *ed;
+ struct dpu_framegen *fg;
+ struct dpu_signature *sig;
+ struct dpu_tcon *tcon;
+ struct dpu_store *st;
+ struct dpu_constframe *aux_pa_cf;
+ struct dpu_constframe *aux_sa_cf;
+ struct dpu_disengcfg *aux_dec;
+ struct dpu_extdst *aux_ed;
+ struct dpu_framegen *aux_fg;
+ struct dpu_signature *aux_sig;
+ struct dpu_tcon *aux_tcon;
+ /* master */
+ struct dpu_constframe *m_pa_cf;
+ struct dpu_constframe *m_sa_cf;
+ struct dpu_disengcfg *m_dec;
+ struct dpu_extdst *m_ed;
+ struct dpu_framegen *m_fg;
+ struct dpu_tcon *m_tcon;
+ /* slave */
+ struct dpu_constframe *s_pa_cf;
+ struct dpu_constframe *s_sa_cf;
+ struct dpu_disengcfg *s_dec;
+ struct dpu_extdst *s_ed;
+ struct dpu_framegen *s_fg;
+ struct dpu_tcon *s_tcon;
+ struct dpu_plane **plane;
+ unsigned int hw_plane_num;
+ unsigned int stream_id;
+ unsigned int crtc_grp_id;
+ unsigned int syncmode_min_prate;
+ unsigned int singlemode_max_width;
+ unsigned int master_stream_id;
+ int vbl_irq;
+ int safety_shdld_irq;
+ int content_shdld_irq;
+ int dec_shdld_irq;
+ int crc_valid_irq;
+ int crc_shdld_irq;
+
+ bool aux_is_master;
+ bool use_dual_crc;
+ bool crc_is_enabled;
+
+ struct completion safety_shdld_done;
+ struct completion content_shdld_done;
+ struct completion dec_shdld_done;
+ struct completion crc_shdld_done;
+ struct completion aux_crc_done;
+
+ struct drm_pending_vblank_event *event;
+
+ u32 crc_red;
+ u32 crc_green;
+ u32 crc_blue;
+ u32 dual_crc_flag;
+};
+
+struct dpu_crc {
+ enum dpu_crc_source source;
+ struct drm_rect roi;
+};
+
+struct dpu_crtc_state {
+ struct imx_crtc_state imx_crtc_state;
+ struct dpu_plane_state **dpu_plane_states;
+ struct dpu_crc crc;
+ bool use_pc;
+};
+
+static inline struct dpu_crtc_state *to_dpu_crtc_state(struct imx_crtc_state *s)
+{
+ return container_of(s, struct dpu_crtc_state, imx_crtc_state);
+}
+
+static inline struct dpu_crtc *to_dpu_crtc(struct drm_crtc *crtc)
+{
+ return container_of(crtc, struct dpu_crtc, base);
+}
+
+struct dpu_plane_state **
+crtc_state_get_dpu_plane_states(struct drm_crtc_state *state);
+
+struct dpu_crtc *dpu_crtc_get_aux_dpu_crtc(struct dpu_crtc *dpu_crtc);
+
+#endif
diff --git a/drivers/gpu/drm/imx/dpu/dpu-kms.c b/drivers/gpu/drm/imx/dpu/dpu-kms.c
new file mode 100644
index 000000000000..1007460fd973
--- /dev/null
+++ b/drivers/gpu/drm/imx/dpu/dpu-kms.c
@@ -0,0 +1,759 @@
+/*
+ * Copyright 2017-2020 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <drm/drm_fourcc.h>
+#include <drm/drm_vblank.h>
+#include <drm/drm_print.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <linux/sort.h>
+#include <video/dpu.h>
+#include "dpu-crtc.h"
+#include "dpu-plane.h"
+#include "../imx-drm.h"
+
+static struct drm_plane_state **
+dpu_atomic_alloc_tmp_planes_per_crtc(struct drm_device *dev)
+{
+ int total_planes = dev->mode_config.num_total_plane;
+ struct drm_plane_state **states;
+
+ states = kmalloc_array(total_planes, sizeof(*states), GFP_KERNEL);
+ if (!states)
+ return ERR_PTR(-ENOMEM);
+
+ return states;
+}
+
+static int zpos_cmp(const void *a, const void *b)
+{
+ const struct drm_plane_state *sa = *(struct drm_plane_state **)a;
+ const struct drm_plane_state *sb = *(struct drm_plane_state **)b;
+
+ return sa->normalized_zpos - sb->normalized_zpos;
+}
+
+static int dpu_atomic_sort_planes_per_crtc(struct drm_crtc_state *crtc_state,
+ struct drm_plane_state **states)
+{
+ struct drm_atomic_state *state = crtc_state->state;
+ struct drm_device *dev = state->dev;
+ struct drm_plane *plane;
+ int n = 0;
+
+ drm_for_each_plane_mask(plane, dev, crtc_state->plane_mask) {
+ struct drm_plane_state *plane_state =
+ drm_atomic_get_plane_state(state, plane);
+ if (IS_ERR(plane_state))
+ return PTR_ERR(plane_state);
+ states[n++] = plane_state;
+ }
+
+ sort(states, n, sizeof(*states), zpos_cmp, NULL);
+
+ return n;
+}
+
+static void
+dpu_atomic_compute_plane_lrx_per_crtc(struct drm_crtc_state *crtc_state,
+ struct drm_plane_state **states, int n)
+{
+ struct dpu_plane_state *dpstate;
+ struct drm_plane_state *plane_state;
+ int i;
+ int half_hdisplay = crtc_state->adjusted_mode.hdisplay >> 1;
+ bool lo, ro, bo;
+
+ /* compute left/right_crtc_x if pixel combiner is needed */
+ for (i = 0; i < n; i++) {
+ plane_state = states[i];
+ dpstate = to_dpu_plane_state(plane_state);
+
+ lo = dpstate->left_src_w && !dpstate->right_src_w;
+ ro = !dpstate->left_src_w && dpstate->right_src_w;
+ bo = dpstate->left_src_w && dpstate->right_src_w;
+
+ if (lo || bo) {
+ dpstate->left_crtc_x = plane_state->crtc_x;
+ dpstate->right_crtc_x = 0;
+ } else if (ro) {
+ dpstate->left_crtc_x = 0;
+ dpstate->right_crtc_x =
+ plane_state->crtc_x - half_hdisplay;
+ }
+ }
+}
+
+static void
+dpu_atomic_set_top_plane_per_crtc(struct drm_plane_state **states, int n,
+ bool use_pc)
+{
+ struct dpu_plane_state *dpstate;
+ bool found_l_top = false, found_r_top = false;
+ int i;
+
+ for (i = n - 1; i >= 0; i--) {
+ dpstate = to_dpu_plane_state(states[i]);
+ if (use_pc) {
+ if (dpstate->left_src_w && !found_l_top) {
+ dpstate->is_left_top = true;
+ found_l_top = true;
+ } else {
+ dpstate->is_left_top = false;
+ }
+
+ if (dpstate->right_src_w && !found_r_top) {
+ dpstate->is_right_top = true;
+ found_r_top = true;
+ } else {
+ dpstate->is_right_top = false;
+ }
+ } else {
+ dpstate->is_top = (i == (n - 1)) ? true : false;
+ }
+ }
+}
+
+static inline bool dpu_atomic_source_pos_is_available(int pos, u32 src_a_mask)
+{
+ return !!((1 << pos) & src_a_mask);
+}
+
+static int dpu_atomic_get_source_pos(lb_sec_sel_t *current_source,
+ u32 src_a_mask)
+{
+ int pos = -1;
+
+ if (*current_source != LB_SEC_SEL__DISABLE) {
+ for (pos = 0; pos < ARRAY_SIZE(sources); pos++)
+ if (*current_source == sources[pos] &&
+ dpu_atomic_source_pos_is_available(pos, src_a_mask))
+ break;
+
+ if (pos == ARRAY_SIZE(sources))
+ *current_source = LB_SEC_SEL__DISABLE;
+ }
+
+ /* current_source not found, or already used */
+ if (pos == -1 || pos == ARRAY_SIZE(sources))
+ pos = ffs(src_a_mask) - 1;
+
+ return pos;
+}
+
+static int
+dpu_atomic_assign_plane_source_per_crtc(struct drm_plane_state **states,
+ int n, bool use_pc)
+{
+ struct dpu_plane_state *dpstate;
+ struct dpu_plane *dplane;
+ struct dpu_plane_grp *grp;
+ struct drm_framebuffer *fb;
+ struct dpu_fetchunit *fu;
+ struct dpu_fetchunit *fe;
+ struct dpu_hscaler *hs;
+ struct dpu_vscaler *vs;
+ lb_prim_sel_t stage;
+ lb_sec_sel_t current_source;
+ dpu_block_id_t blend;
+ unsigned int sid, src_sid;
+ unsigned int num_planes;
+ int bit;
+ int i, j, k = 0, m;
+ int total_asrc_num;
+ int s0_layer_cnt = 0, s1_layer_cnt = 0;
+ int s0_n = 0, s1_n = 0;
+ u32 src_a_mask, cap_mask, fe_mask, hs_mask, vs_mask;
+ bool need_fetcheco, need_hscaler, need_vscaler;
+ bool fmt_is_yuv;
+ bool alloc_aux_source;
+
+ if (use_pc) {
+ for (i = 0; i < n; i++) {
+ dpstate = to_dpu_plane_state(states[i]);
+
+ if (dpstate->left_src_w)
+ s0_n++;
+
+ if (dpstate->right_src_w)
+ s1_n++;
+ }
+ } else {
+ s0_n = n;
+ s1_n = n;
+ }
+
+ /* for active planes only */
+ for (i = 0; i < n; i++) {
+ dpstate = to_dpu_plane_state(states[i]);
+ dplane = to_dpu_plane(states[i]->plane);
+ fb = states[i]->fb;
+ num_planes = fb->format->num_planes;
+ fmt_is_yuv = drm_format_is_yuv(fb->format->format);
+ grp = dplane->grp;
+ alloc_aux_source = false;
+
+ if (use_pc)
+ sid = dpstate->left_src_w ? 0 : 1;
+ else
+ sid = dplane->stream_id;
+
+again:
+ if (alloc_aux_source)
+ sid ^= 1;
+
+ need_fetcheco = (num_planes > 1);
+ need_hscaler = (states[i]->src_w >> 16 != states[i]->crtc_w);
+ need_vscaler = (states[i]->src_h >> 16 != states[i]->crtc_h);
+
+ total_asrc_num = 0;
+ src_a_mask = grp->src_a_mask;
+ fe_mask = 0;
+ hs_mask = 0;
+ vs_mask = 0;
+
+ for_each_set_bit(bit, (unsigned long *)&src_a_mask, 32)
+ total_asrc_num++;
+
+ current_source = alloc_aux_source ?
+ dpstate->aux_source : dpstate->source;
+
+ /* assign source */
+ mutex_lock(&grp->mutex);
+ for (j = 0; j < total_asrc_num; j++) {
+ k = dpu_atomic_get_source_pos(&current_source, src_a_mask);
+ if (k < 0)
+ return -EINVAL;
+
+ fu = source_to_fu(&grp->res, sources[k]);
+ if (!fu)
+ return -EINVAL;
+
+ /* avoid on-the-fly/hot migration */
+ src_sid = fu->ops->get_stream_id(fu);
+ if (src_sid && src_sid != BIT(sid))
+ goto next;
+
+ if (fetchunit_is_fetchdecode(fu)) {
+ cap_mask = fetchdecode_get_vproc_mask(fu);
+
+ if (need_fetcheco) {
+ fe = fetchdecode_get_fetcheco(fu);
+
+ /* avoid on-the-fly/hot migration */
+ src_sid = fu->ops->get_stream_id(fe);
+ if (src_sid && src_sid != BIT(sid))
+ goto next;
+
+ /* fetch unit has the fetcheco cap? */
+ if (!dpu_vproc_has_fetcheco_cap(cap_mask))
+ goto next;
+
+ fe_mask =
+ dpu_vproc_get_fetcheco_cap(cap_mask);
+
+ /* fetcheco available? */
+ if (grp->src_use_vproc_mask & fe_mask)
+ goto next;
+ }
+
+ if (need_hscaler) {
+ hs = fetchdecode_get_hscaler(fu);
+
+ /* avoid on-the-fly/hot migration */
+ src_sid = hscaler_get_stream_id(hs);
+ if (src_sid && src_sid != BIT(sid))
+ goto next;
+
+ /* fetch unit has the hscale cap */
+ if (!dpu_vproc_has_hscale_cap(cap_mask))
+ goto next;
+
+ hs_mask =
+ dpu_vproc_get_hscale_cap(cap_mask);
+
+ /* hscaler available? */
+ if (grp->src_use_vproc_mask & hs_mask)
+ goto next;
+ }
+
+ if (need_vscaler) {
+ vs = fetchdecode_get_vscaler(fu);
+
+ /* avoid on-the-fly/hot migration */
+ src_sid = vscaler_get_stream_id(vs);
+ if (src_sid && src_sid != BIT(sid))
+ goto next;
+
+ /* fetch unit has the vscale cap? */
+ if (!dpu_vproc_has_vscale_cap(cap_mask))
+ goto next;
+
+ vs_mask =
+ dpu_vproc_get_vscale_cap(cap_mask);
+
+ /* vscaler available? */
+ if (grp->src_use_vproc_mask & vs_mask)
+ goto next;
+ }
+ } else {
+ if (fmt_is_yuv || need_fetcheco ||
+ need_hscaler || need_vscaler)
+ goto next;
+ }
+
+ grp->src_a_mask &= ~BIT(k);
+ grp->src_use_vproc_mask |= fe_mask | hs_mask | vs_mask;
+ break;
+next:
+ src_a_mask &= ~BIT(k);
+ fe_mask = 0;
+ hs_mask = 0;
+ vs_mask = 0;
+ }
+ mutex_unlock(&grp->mutex);
+
+ if (j == total_asrc_num)
+ return -EINVAL;
+
+ if (alloc_aux_source)
+ dpstate->aux_source = sources[k];
+ else
+ dpstate->source = sources[k];
+
+ /* assign stage and blend */
+ if (sid) {
+ m = grp->hw_plane_num - (s1_n - s1_layer_cnt);
+ stage = s1_layer_cnt ? stages[m - 1] : cf_stages[sid];
+ blend = blends[m];
+
+ s1_layer_cnt++;
+ } else {
+ stage = s0_layer_cnt ?
+ stages[s0_layer_cnt - 1] : cf_stages[sid];
+ blend = blends[s0_layer_cnt];
+
+ s0_layer_cnt++;
+ }
+
+ if (alloc_aux_source) {
+ dpstate->aux_stage = stage;
+ dpstate->aux_blend = blend;
+ } else {
+ dpstate->stage = stage;
+ dpstate->blend = blend;
+ }
+
+ if (dpstate->need_aux_source && !alloc_aux_source) {
+ alloc_aux_source = true;
+ goto again;
+ }
+ }
+
+ return 0;
+}
+
+static void
+dpu_atomic_mark_pipe_states_prone_to_put_per_crtc(struct drm_crtc *crtc,
+ u32 crtc_mask,
+ struct drm_atomic_state *state,
+ bool *puts)
+{
+ struct drm_plane *plane;
+ struct drm_plane_state *plane_state;
+ bool found_pstate = false;
+ int i;
+
+ if ((crtc_mask & drm_crtc_mask(crtc)) == 0) {
+ for_each_new_plane_in_state(state, plane, plane_state, i) {
+ if (plane->possible_crtcs & drm_crtc_mask(crtc)) {
+ found_pstate = true;
+ break;
+ }
+ }
+
+ if (!found_pstate)
+ puts[drm_crtc_index(crtc)] = true;
+ }
+}
+
+static void
+dpu_atomic_put_plane_state(struct drm_atomic_state *state,
+ struct drm_plane *plane)
+{
+ int index = drm_plane_index(plane);
+
+ plane->funcs->atomic_destroy_state(plane, state->planes[index].state);
+ state->planes[index].ptr = NULL;
+ state->planes[index].state = NULL;
+
+ drm_modeset_unlock(&plane->mutex);
+}
+
+static void
+dpu_atomic_put_crtc_state(struct drm_atomic_state *state,
+ struct drm_crtc *crtc)
+{
+ int index = drm_crtc_index(crtc);
+
+ crtc->funcs->atomic_destroy_state(crtc, state->crtcs[index].state);
+ state->crtcs[index].ptr = NULL;
+ state->crtcs[index].state = NULL;
+
+ drm_modeset_unlock(&crtc->mutex);
+}
+
+static void
+dpu_atomic_put_possible_states_per_crtc(struct drm_crtc_state *crtc_state)
+{
+ struct drm_atomic_state *state = crtc_state->state;
+ struct drm_crtc *crtc = crtc_state->crtc;
+ struct drm_crtc_state *old_crtc_state = crtc->state;
+ struct drm_plane *plane;
+ struct drm_plane_state *plane_state;
+ struct dpu_plane *dplane = to_dpu_plane(crtc->primary);
+ struct dpu_plane_state **old_dpstates;
+ struct dpu_plane_state *old_dpstate, *new_dpstate;
+ u32 active_mask = 0;
+ int i;
+
+ old_dpstates = crtc_state_get_dpu_plane_states(old_crtc_state);
+ if (WARN_ON(!old_dpstates))
+ return;
+
+ for (i = 0; i < dplane->grp->hw_plane_num; i++) {
+ old_dpstate = old_dpstates[i];
+ if (!old_dpstate)
+ continue;
+
+ active_mask |= BIT(i);
+
+ drm_atomic_crtc_state_for_each_plane(plane, crtc_state) {
+ if (drm_plane_index(plane) !=
+ drm_plane_index(old_dpstate->base.plane))
+ continue;
+
+ plane_state =
+ drm_atomic_get_existing_plane_state(state,
+ plane);
+ if (WARN_ON(!plane_state))
+ return;
+
+ new_dpstate = to_dpu_plane_state(plane_state);
+
+ active_mask &= ~BIT(i);
+
+ /*
+ * Should be enough to check the below real HW plane
+ * resources only.
+ * Things like vproc resources should be fine.
+ */
+ if (old_dpstate->stage != new_dpstate->stage ||
+ old_dpstate->source != new_dpstate->source ||
+ old_dpstate->blend != new_dpstate->blend ||
+ old_dpstate->aux_stage != new_dpstate->aux_stage ||
+ old_dpstate->aux_source != new_dpstate->aux_source ||
+ old_dpstate->aux_blend != new_dpstate->aux_blend)
+ return;
+ }
+ }
+
+ /* pure software check */
+ if (WARN_ON(active_mask))
+ return;
+
+ drm_atomic_crtc_state_for_each_plane(plane, crtc_state)
+ dpu_atomic_put_plane_state(state, plane);
+
+ dpu_atomic_put_crtc_state(state, crtc);
+}
+
+static int dpu_drm_atomic_check(struct drm_device *dev,
+ struct drm_atomic_state *state)
+{
+ struct drm_crtc *crtc;
+ struct drm_crtc_state *crtc_state;
+ struct drm_plane *plane;
+ struct dpu_plane *dpu_plane;
+ struct drm_plane_state *plane_state;
+ struct dpu_plane_state *dpstate;
+ struct drm_framebuffer *fb;
+ struct dpu_plane_grp *grp[MAX_DPU_PLANE_GRP];
+ int ret, i, grp_id;
+ int active_plane[MAX_DPU_PLANE_GRP];
+ int active_plane_fetcheco[MAX_DPU_PLANE_GRP];
+ int active_plane_hscale[MAX_DPU_PLANE_GRP];
+ int active_plane_vscale[MAX_DPU_PLANE_GRP];
+ int half_hdisplay = 0;
+ bool pipe_states_prone_to_put[MAX_CRTC];
+ bool use_pc[MAX_DPU_PLANE_GRP];
+ u32 crtc_mask_in_state = 0;
+
+ ret = drm_atomic_helper_check_modeset(dev, state);
+ if (ret) {
+ DRM_DEBUG_KMS("%s: failed to check modeset\n", __func__);
+ return ret;
+ }
+
+ for (i = 0; i < MAX_CRTC; i++)
+ pipe_states_prone_to_put[i] = false;
+
+ for (i = 0; i < MAX_DPU_PLANE_GRP; i++) {
+ active_plane[i] = 0;
+ active_plane_fetcheco[i] = 0;
+ active_plane_hscale[i] = 0;
+ active_plane_vscale[i] = 0;
+ use_pc[i] = false;
+ grp[i] = NULL;
+ }
+
+ for_each_new_crtc_in_state(state, crtc, crtc_state, i)
+ crtc_mask_in_state |= drm_crtc_mask(crtc);
+
+ drm_for_each_crtc(crtc, dev) {
+ struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc);
+ struct imx_crtc_state *imx_crtc_state;
+ struct dpu_crtc_state *dcstate;
+ bool need_left, need_right, need_aux_source, use_pc_per_crtc;
+
+ use_pc_per_crtc = false;
+
+ dpu_atomic_mark_pipe_states_prone_to_put_per_crtc(crtc,
+ crtc_mask_in_state, state,
+ pipe_states_prone_to_put);
+
+ crtc_state = drm_atomic_get_crtc_state(state, crtc);
+ if (IS_ERR(crtc_state))
+ return PTR_ERR(crtc_state);
+
+ imx_crtc_state = to_imx_crtc_state(crtc_state);
+ dcstate = to_dpu_crtc_state(imx_crtc_state);
+
+ if (crtc_state->enable) {
+ if (use_pc[dpu_crtc->crtc_grp_id]) {
+ DRM_DEBUG_KMS("other crtc needs pixel combiner\n");
+ return -EINVAL;
+ }
+
+ if (crtc_state->adjusted_mode.clock >
+ dpu_crtc->syncmode_min_prate ||
+ crtc_state->adjusted_mode.hdisplay >
+ dpu_crtc->singlemode_max_width)
+ use_pc_per_crtc = true;
+ }
+
+ if (use_pc_per_crtc) {
+ use_pc[dpu_crtc->crtc_grp_id] = true;
+ half_hdisplay = crtc_state->adjusted_mode.hdisplay >> 1;
+ }
+
+ dcstate->use_pc = use_pc_per_crtc;
+
+ drm_for_each_plane_mask(plane, dev, crtc_state->plane_mask) {
+ plane_state = drm_atomic_get_plane_state(state, plane);
+ if (IS_ERR(plane_state)) {
+ DRM_DEBUG_KMS("failed to get plane state\n");
+ return PTR_ERR(plane_state);
+ }
+
+ dpstate = to_dpu_plane_state(plane_state);
+ fb = plane_state->fb;
+ dpu_plane = to_dpu_plane(plane);
+ grp_id = dpu_plane->grp->id;
+ active_plane[grp_id]++;
+
+ need_left = false;
+ need_right = false;
+ need_aux_source = false;
+
+ if (use_pc_per_crtc) {
+ if (plane_state->crtc_x < half_hdisplay)
+ need_left = true;
+
+ if ((plane_state->crtc_w +
+ plane_state->crtc_x) > half_hdisplay)
+ need_right = true;
+
+ if (need_left && need_right) {
+ need_aux_source = true;
+ active_plane[grp_id]++;
+ }
+ }
+
+ if (need_left && need_right) {
+ dpstate->left_crtc_w = half_hdisplay;
+ dpstate->left_crtc_w -= plane_state->crtc_x;
+
+ dpstate->left_src_w = dpstate->left_crtc_w;
+ } else if (need_left) {
+ dpstate->left_crtc_w = plane_state->crtc_w;
+ dpstate->left_src_w = plane_state->src_w >> 16;
+ } else {
+ dpstate->left_crtc_w = 0;
+ dpstate->left_src_w = 0;
+ }
+
+ if (need_right && need_left) {
+ dpstate->right_crtc_w = plane_state->crtc_x +
+ plane_state->crtc_w;
+ dpstate->right_crtc_w -= half_hdisplay;
+
+ dpstate->right_src_w = dpstate->right_crtc_w;
+ } else if (need_right) {
+ dpstate->right_crtc_w = plane_state->crtc_w;
+ dpstate->right_src_w = plane_state->src_w >> 16;
+ } else {
+ dpstate->right_crtc_w = 0;
+ dpstate->right_src_w = 0;
+ }
+
+ if (fb->format->num_planes > 1) {
+ active_plane_fetcheco[grp_id]++;
+ if (need_aux_source)
+ active_plane_fetcheco[grp_id]++;
+ }
+
+ if (plane_state->src_w >> 16 != plane_state->crtc_w) {
+ if (use_pc_per_crtc)
+ return -EINVAL;
+
+ active_plane_hscale[grp_id]++;
+ }
+
+ if (plane_state->src_h >> 16 != plane_state->crtc_h) {
+ if (use_pc_per_crtc)
+ return -EINVAL;
+
+ active_plane_vscale[grp_id]++;
+ }
+
+ if (grp[grp_id] == NULL)
+ grp[grp_id] = dpu_plane->grp;
+
+ dpstate->need_aux_source = need_aux_source;
+ }
+ }
+
+ /* enough resources? */
+ for (i = 0; i < MAX_DPU_PLANE_GRP; i++) {
+ if (!grp[i])
+ continue;
+
+ if (active_plane[i] > grp[i]->hw_plane_num) {
+ DRM_DEBUG_KMS("no enough fetch units\n");
+ return -EINVAL;
+ }
+
+ if (active_plane_fetcheco[i] > grp[i]->hw_plane_fetcheco_num) {
+ DRM_DEBUG_KMS("no enough FetchEcos\n");
+ return -EINVAL;
+ }
+
+ if (active_plane_hscale[i] > grp[i]->hw_plane_hscaler_num) {
+ DRM_DEBUG_KMS("no enough Hscalers\n");
+ return -EINVAL;
+ }
+
+ if (active_plane_vscale[i] > grp[i]->hw_plane_vscaler_num) {
+ DRM_DEBUG_KMS("no enough Vscalers\n");
+ return -EINVAL;
+ }
+ }
+
+ /* initialize resource mask */
+ for (i = 0; i < MAX_DPU_PLANE_GRP; i++) {
+ if (!grp[i])
+ continue;
+
+ mutex_lock(&grp[i]->mutex);
+ grp[i]->src_a_mask = grp[i]->src_mask;
+ grp[i]->src_use_vproc_mask = 0;
+ mutex_unlock(&grp[i]->mutex);
+ }
+
+ ret = drm_atomic_normalize_zpos(dev, state);
+ if (ret)
+ return ret;
+
+ for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
+ struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc);
+ struct drm_plane_state **states;
+ int n;
+
+ states = dpu_atomic_alloc_tmp_planes_per_crtc(dev);
+ if (IS_ERR(states)) {
+ DRM_DEBUG_KMS(
+ "[CRTC:%d:%s] cannot alloc plane state ptrs\n",
+ crtc->base.id, crtc->name);
+ return PTR_ERR(states);
+ }
+
+ n = dpu_atomic_sort_planes_per_crtc(crtc_state, states);
+ if (n < 0) {
+ DRM_DEBUG_KMS("[CRTC:%d:%s] failed to sort planes\n",
+ crtc->base.id, crtc->name);
+ kfree(states);
+ return n;
+ }
+
+ /* no active planes? */
+ if (n == 0) {
+ kfree(states);
+ continue;
+ }
+
+ if (use_pc[dpu_crtc->crtc_grp_id])
+ dpu_atomic_compute_plane_lrx_per_crtc(crtc_state,
+ states, n);
+
+ dpu_atomic_set_top_plane_per_crtc(states, n,
+ use_pc[dpu_crtc->crtc_grp_id]);
+
+ ret = dpu_atomic_assign_plane_source_per_crtc(states, n,
+ use_pc[dpu_crtc->crtc_grp_id]);
+ if (ret) {
+ DRM_DEBUG_KMS("[CRTC:%d:%s] cannot assign plane rscs\n",
+ crtc->base.id, crtc->name);
+ kfree(states);
+ return ret;
+ }
+
+ kfree(states);
+ }
+
+ drm_for_each_crtc(crtc, dev) {
+ if (pipe_states_prone_to_put[drm_crtc_index(crtc)]) {
+ crtc_state = drm_atomic_get_crtc_state(state, crtc);
+ if (WARN_ON(IS_ERR(crtc_state)))
+ return PTR_ERR(crtc_state);
+
+ dpu_atomic_put_possible_states_per_crtc(crtc_state);
+ }
+ }
+
+ ret = drm_atomic_helper_check_planes(dev, state);
+ if (ret) {
+ DRM_DEBUG_KMS("%s: failed to check planes\n", __func__);
+ return ret;
+ }
+
+ return ret;
+}
+
+const struct drm_mode_config_funcs dpu_drm_mode_config_funcs = {
+ .fb_create = drm_gem_fb_create,
+ .atomic_check = dpu_drm_atomic_check,
+ .atomic_commit = drm_atomic_helper_commit,
+};
diff --git a/drivers/gpu/drm/imx/dpu/dpu-kms.h b/drivers/gpu/drm/imx/dpu/dpu-kms.h
new file mode 100644
index 000000000000..73723e500239
--- /dev/null
+++ b/drivers/gpu/drm/imx/dpu/dpu-kms.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2017-2019 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef _DPU_KMS_H_
+#define _DPU_KMS_H_
+
+extern const struct drm_mode_config_funcs dpu_drm_mode_config_funcs;
+
+#endif
diff --git a/drivers/gpu/drm/imx/dpu/dpu-plane.c b/drivers/gpu/drm/imx/dpu/dpu-plane.c
new file mode 100644
index 000000000000..d0db1edb43c6
--- /dev/null
+++ b/drivers/gpu/drm/imx/dpu/dpu-plane.c
@@ -0,0 +1,1024 @@
+/*
+ * Copyright 2017-2019,2022 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <drm/drm_vblank.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_blend.h>
+#include <drm/drm_color_mgmt.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_gem_atomic_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_plane_helper.h>
+#include <video/dpu.h>
+#include <video/imx8-prefetch.h>
+#include "dpu-plane.h"
+#include "../imx-drm.h"
+
+#define FRAC_16_16(mult, div) (((mult) << 16) / (div))
+
+static const uint32_t dpu_formats[] = {
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_ABGR8888,
+ DRM_FORMAT_XBGR8888,
+ DRM_FORMAT_RGBA8888,
+ DRM_FORMAT_RGBX8888,
+ DRM_FORMAT_BGRA8888,
+ DRM_FORMAT_BGRX8888,
+ DRM_FORMAT_RGB565,
+
+ DRM_FORMAT_YUYV,
+ DRM_FORMAT_UYVY,
+ DRM_FORMAT_NV12,
+ DRM_FORMAT_NV21,
+};
+
+static const uint64_t dpu_format_modifiers[] = {
+ DRM_FORMAT_MOD_VIVANTE_TILED,
+ DRM_FORMAT_MOD_VIVANTE_SUPER_TILED,
+ DRM_FORMAT_MOD_AMPHION_TILED,
+ DRM_FORMAT_MOD_LINEAR,
+ DRM_FORMAT_MOD_INVALID,
+};
+
+static unsigned int dpu_plane_get_default_zpos(enum drm_plane_type type)
+{
+ if (type == DRM_PLANE_TYPE_PRIMARY)
+ return 0;
+ else if (type == DRM_PLANE_TYPE_OVERLAY)
+ return 1;
+
+ return 0;
+}
+
+static void dpu_plane_destroy(struct drm_plane *plane)
+{
+ struct dpu_plane *dpu_plane = to_dpu_plane(plane);
+
+ drm_plane_cleanup(plane);
+ kfree(dpu_plane);
+}
+
+static void dpu_plane_reset(struct drm_plane *plane)
+{
+ struct dpu_plane_state *state;
+
+ if (plane->state) {
+ __drm_atomic_helper_plane_destroy_state(plane->state);
+ kfree(to_dpu_plane_state(plane->state));
+ plane->state = NULL;
+ }
+
+ state = kzalloc(sizeof(*state), GFP_KERNEL);
+ if (!state)
+ return;
+
+ __drm_atomic_helper_plane_reset(plane, &state->base);
+
+ plane->state->zpos = dpu_plane_get_default_zpos(plane->type);
+ plane->state->color_encoding = DRM_COLOR_YCBCR_BT601;
+ plane->state->color_range = DRM_COLOR_YCBCR_FULL_RANGE;
+}
+
+static struct drm_plane_state *
+dpu_drm_atomic_plane_duplicate_state(struct drm_plane *plane)
+{
+ struct dpu_plane_state *state, *copy;
+
+ if (WARN_ON(!plane->state))
+ return NULL;
+
+ copy = kmalloc(sizeof(*state), GFP_KERNEL);
+ if (!copy)
+ return NULL;
+
+ __drm_atomic_helper_plane_duplicate_state(plane, &copy->base);
+ state = to_dpu_plane_state(plane->state);
+ copy->stage = state->stage;
+ copy->source = state->source;
+ copy->blend = state->blend;
+ copy->aux_stage = state->aux_stage;
+ copy->aux_source = state->aux_source;
+ copy->aux_blend = state->aux_blend;
+ copy->is_top = state->is_top;
+ copy->use_prefetch = state->use_prefetch;
+ copy->use_aux_prefetch = state->use_aux_prefetch;
+ copy->need_aux_source = state->need_aux_source;
+ copy->left_src_w = state->left_src_w;
+ copy->left_crtc_w = state->left_crtc_w;
+ copy->left_crtc_x = state->left_crtc_x;
+ copy->right_src_w = state->right_src_w;
+ copy->right_crtc_w = state->right_crtc_w;
+ copy->right_crtc_x = state->right_crtc_x;
+ copy->is_left_top = state->is_left_top;
+ copy->is_right_top = state->is_right_top;
+
+ return &copy->base;
+}
+
+static bool dpu_drm_plane_format_mod_supported(struct drm_plane *plane,
+ uint32_t format,
+ uint64_t modifier)
+{
+ if (WARN_ON(modifier == DRM_FORMAT_MOD_INVALID))
+ return false;
+
+ switch (format) {
+ case DRM_FORMAT_YUYV:
+ case DRM_FORMAT_UYVY:
+ return modifier == DRM_FORMAT_MOD_LINEAR;
+ case DRM_FORMAT_ARGB8888:
+ case DRM_FORMAT_XRGB8888:
+ case DRM_FORMAT_ABGR8888:
+ case DRM_FORMAT_XBGR8888:
+ case DRM_FORMAT_RGBA8888:
+ case DRM_FORMAT_RGBX8888:
+ case DRM_FORMAT_BGRA8888:
+ case DRM_FORMAT_BGRX8888:
+ case DRM_FORMAT_RGB565:
+ return modifier == DRM_FORMAT_MOD_LINEAR ||
+ modifier == DRM_FORMAT_MOD_VIVANTE_TILED ||
+ modifier == DRM_FORMAT_MOD_VIVANTE_SUPER_TILED;
+ case DRM_FORMAT_NV12:
+ case DRM_FORMAT_NV21:
+ return modifier == DRM_FORMAT_MOD_LINEAR ||
+ modifier == DRM_FORMAT_MOD_AMPHION_TILED;
+ default:
+ return false;
+ }
+}
+
+static void dpu_drm_atomic_plane_destroy_state(struct drm_plane *plane,
+ struct drm_plane_state *state)
+{
+ __drm_atomic_helper_plane_destroy_state(state);
+ kfree(to_dpu_plane_state(state));
+}
+
+static const struct drm_plane_funcs dpu_plane_funcs = {
+ .update_plane = drm_atomic_helper_update_plane,
+ .disable_plane = drm_atomic_helper_disable_plane,
+ .destroy = dpu_plane_destroy,
+ .reset = dpu_plane_reset,
+ .atomic_duplicate_state = dpu_drm_atomic_plane_duplicate_state,
+ .atomic_destroy_state = dpu_drm_atomic_plane_destroy_state,
+ .format_mod_supported = dpu_drm_plane_format_mod_supported,
+};
+
+static inline dma_addr_t
+drm_plane_state_to_baseaddr(struct drm_plane_state *state, bool aux_source)
+{
+ struct drm_framebuffer *fb = state->fb;
+ struct drm_gem_cma_object *cma_obj;
+ struct dpu_plane_state *dpstate = to_dpu_plane_state(state);
+ unsigned int x = (state->src.x1 >> 16) +
+ (aux_source ? dpstate->left_src_w : 0);
+ unsigned int y = state->src.y1 >> 16;
+
+ cma_obj = drm_fb_cma_get_gem_obj(fb, 0);
+ BUG_ON(!cma_obj);
+
+ if (fb->modifier)
+ return cma_obj->paddr + fb->offsets[0];
+
+ if (fb->flags & DRM_MODE_FB_INTERLACED)
+ y /= 2;
+
+ return cma_obj->paddr + fb->offsets[0] + fb->pitches[0] * y +
+ fb->format->cpp[0] * x;
+}
+
+static inline dma_addr_t
+drm_plane_state_to_uvbaseaddr(struct drm_plane_state *state, bool aux_source)
+{
+ struct drm_framebuffer *fb = state->fb;
+ struct drm_gem_cma_object *cma_obj;
+ struct dpu_plane_state *dpstate = to_dpu_plane_state(state);
+ int x = (state->src.x1 >> 16) + (aux_source ? dpstate->left_src_w : 0);
+ int y = state->src.y1 >> 16;
+
+ cma_obj = drm_fb_cma_get_gem_obj(fb, 1);
+ BUG_ON(!cma_obj);
+
+ if (fb->modifier)
+ return cma_obj->paddr + fb->offsets[1];
+
+ x /= fb->format->hsub;
+ y /= fb->format->vsub;
+
+ if (fb->flags & DRM_MODE_FB_INTERLACED)
+ y /= 2;
+
+ return cma_obj->paddr + fb->offsets[1] + fb->pitches[1] * y +
+ fb->format->cpp[1] * x;
+}
+
+static inline bool dpu_plane_fb_format_is_yuv(u32 fmt)
+{
+ return fmt == DRM_FORMAT_YUYV || fmt == DRM_FORMAT_UYVY ||
+ fmt == DRM_FORMAT_NV12 || fmt == DRM_FORMAT_NV21;
+}
+
+static int dpu_plane_atomic_check(struct drm_plane *plane,
+ struct drm_atomic_state *state)
+{
+ struct dpu_plane *dplane = to_dpu_plane(plane);
+ struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state,
+ plane);
+ struct dpu_plane_state *dpstate = to_dpu_plane_state(new_plane_state);
+ struct dpu_plane_res *res = &dplane->grp->res;
+ struct drm_crtc_state *crtc_state;
+ struct drm_framebuffer *fb = new_plane_state->fb;
+ struct dpu_fetchunit *fu;
+ struct dprc *dprc;
+ dma_addr_t baseaddr, uv_baseaddr = 0;
+ u32 src_w, src_h, src_x, src_y;
+ unsigned int frame_width;
+ int min_scale, bpp, ret;
+ bool fb_is_interlaced;
+ bool check_aux_source = false;
+
+ /* ok to disable */
+ if (!fb) {
+ dpstate->stage = LB_PRIM_SEL__DISABLE;
+ dpstate->source = LB_SEC_SEL__DISABLE;
+ dpstate->blend = ID_NONE;
+ dpstate->aux_stage = LB_PRIM_SEL__DISABLE;
+ dpstate->aux_source = LB_SEC_SEL__DISABLE;
+ dpstate->aux_blend = ID_NONE;
+ dpstate->is_top = false;
+ dpstate->use_prefetch = false;
+ dpstate->use_aux_prefetch = false;
+ dpstate->need_aux_source = false;
+ dpstate->left_src_w = 0;
+ dpstate->left_crtc_w = 0;
+ dpstate->left_crtc_x = 0;
+ dpstate->right_src_w = 0;
+ dpstate->right_crtc_w = 0;
+ dpstate->right_crtc_x = 0;
+ dpstate->is_left_top = false;
+ dpstate->is_right_top = false;
+ return 0;
+ }
+
+ if (!new_plane_state->crtc) {
+ DRM_DEBUG_KMS("[PLANE:%d:%s] has no CRTC in plane state\n",
+ plane->base.id, plane->name);
+ return -EINVAL;
+ }
+
+ src_w = new_plane_state->src_w >> 16;
+ src_h = new_plane_state->src_h >> 16;
+ src_x = new_plane_state->src_x >> 16;
+ src_y = new_plane_state->src_y >> 16;
+
+ fb_is_interlaced = !!(fb->flags & DRM_MODE_FB_INTERLACED);
+
+ if (fb->modifier &&
+ fb->modifier != DRM_FORMAT_MOD_AMPHION_TILED &&
+ fb->modifier != DRM_FORMAT_MOD_VIVANTE_TILED &&
+ fb->modifier != DRM_FORMAT_MOD_VIVANTE_SUPER_TILED) {
+ DRM_DEBUG_KMS("[PLANE:%d:%s] unsupported fb modifier\n",
+ plane->base.id, plane->name);
+ return -EINVAL;
+ }
+
+ crtc_state = drm_atomic_get_existing_crtc_state(state,
+ new_plane_state->crtc);
+ if (WARN_ON(!crtc_state))
+ return -EINVAL;
+
+ min_scale = dplane->grp->has_vproc ?
+ FRAC_16_16(min(src_w, src_h), 8192) :
+ DRM_PLANE_HELPER_NO_SCALING;
+ ret = drm_atomic_helper_check_plane_state(new_plane_state, crtc_state,
+ min_scale,
+ DRM_PLANE_HELPER_NO_SCALING,
+ true, false);
+ if (ret) {
+ DRM_DEBUG_KMS("[PLANE:%d:%s] failed to check plane state\n",
+ plane->base.id, plane->name);
+ return ret;
+ }
+
+ /* no off screen */
+ if (new_plane_state->dst.x1 < 0 || new_plane_state->dst.y1 < 0 ||
+ (new_plane_state->dst.x2 > crtc_state->adjusted_mode.hdisplay) ||
+ (new_plane_state->dst.y2 > crtc_state->adjusted_mode.vdisplay)) {
+ DRM_DEBUG_KMS("[PLANE:%d:%s] no off screen\n",
+ plane->base.id, plane->name);
+ return -EINVAL;
+ }
+
+ /* pixel/line count and position parameters check */
+ if (fb->format->hsub == 2) {
+ if (dpstate->left_src_w || dpstate->right_src_w) {
+ if ((dpstate->left_src_w % 2) ||
+ (dpstate->right_src_w % 2) || (src_x % 2)) {
+ DRM_DEBUG_KMS("[PLANE:%d:%s] bad left/right uv width or xoffset\n",
+ plane->base.id, plane->name);
+ return -EINVAL;
+ }
+ } else {
+ if ((src_w % 2) || (src_x % 2)) {
+ DRM_DEBUG_KMS("[PLANE:%d:%s] bad uv width or xoffset\n",
+ plane->base.id, plane->name);
+ return -EINVAL;
+ }
+ }
+ }
+ if (fb->format->vsub == 2) {
+ if (src_h % (fb_is_interlaced ? 4 : 2)) {
+ DRM_DEBUG_KMS("[PLANE:%d:%s] bad uv height\n",
+ plane->base.id, plane->name);
+ return -EINVAL;
+ }
+ if (src_y % (fb_is_interlaced ? 4 : 2)) {
+ DRM_DEBUG_KMS("[PLANE:%d:%s] bad uv yoffset\n",
+ plane->base.id, plane->name);
+ return -EINVAL;
+ }
+ }
+
+ /* for tile formats, framebuffer has to be tile aligned */
+ switch (fb->modifier) {
+ case DRM_FORMAT_MOD_AMPHION_TILED:
+ if (fb->width % 8) {
+ DRM_DEBUG_KMS("[PLANE:%d:%s] bad fb width for AMPHION tile\n",
+ plane->base.id, plane->name);
+ return -EINVAL;
+ }
+ if (fb->height % 256) {
+ DRM_DEBUG_KMS("[PLANE:%d:%s] bad fb height for AMPHION tile\n",
+ plane->base.id, plane->name);
+ return -EINVAL;
+ }
+ break;
+ case DRM_FORMAT_MOD_VIVANTE_TILED:
+ if (fb->width % 4) {
+ DRM_DEBUG_KMS("[PLANE:%d:%s] bad fb width for VIVANTE tile\n",
+ plane->base.id, plane->name);
+ return -EINVAL;
+ }
+ if (fb->height % 4) {
+ DRM_DEBUG_KMS("[PLANE:%d:%s] bad fb height for VIVANTE tile\n",
+ plane->base.id, plane->name);
+ return -EINVAL;
+ }
+ break;
+ case DRM_FORMAT_MOD_VIVANTE_SUPER_TILED:
+ if (fb->width % 64) {
+ DRM_DEBUG_KMS("[PLANE:%d:%s] bad fb width for VIVANTE super tile\n",
+ plane->base.id, plane->name);
+ return -EINVAL;
+ }
+ if (fb->height % 64) {
+ DRM_DEBUG_KMS("[PLANE:%d:%s] bad fb height for VIVANTE super tile\n",
+ plane->base.id, plane->name);
+ return -EINVAL;
+ }
+ break;
+ default:
+ break;
+ }
+
+ /* do not support BT709 full range */
+ if (dpu_plane_fb_format_is_yuv(fb->format->format) &&
+ new_plane_state->color_encoding == DRM_COLOR_YCBCR_BT709 &&
+ new_plane_state->color_range == DRM_COLOR_YCBCR_FULL_RANGE)
+ return -EINVAL;
+
+again:
+ fu = source_to_fu(res,
+ check_aux_source ? dpstate->aux_source : dpstate->source);
+ if (!fu) {
+ DRM_DEBUG_KMS("[PLANE:%d:%s] cannot get fetch unit\n",
+ plane->base.id, plane->name);
+ return -EINVAL;
+ }
+
+ dprc = fu->dprc;
+
+ if (dpstate->need_aux_source)
+ frame_width = check_aux_source ?
+ dpstate->right_src_w : dpstate->left_src_w;
+ else
+ frame_width = src_w;
+
+ if (dprc &&
+ dprc_format_supported(dprc, fb->format->format, fb->modifier) &&
+ dprc_stride_supported(dprc, fb->pitches[0], fb->pitches[1],
+ frame_width, fb->format->format)) {
+ if (check_aux_source)
+ dpstate->use_aux_prefetch = true;
+ else
+ dpstate->use_prefetch = true;
+ } else {
+ if (check_aux_source)
+ dpstate->use_aux_prefetch = false;
+ else
+ dpstate->use_prefetch = false;
+ }
+
+ if (fb->modifier) {
+ if (check_aux_source && !dpstate->use_aux_prefetch) {
+ DRM_DEBUG_KMS("[PLANE:%d:%s] cannot do tile resolving wo prefetch\n",
+ plane->base.id, plane->name);
+ return -EINVAL;
+ } else if (!check_aux_source && !dpstate->use_prefetch) {
+ DRM_DEBUG_KMS("[PLANE:%d:%s] cannot do tile resolving wo prefetch\n",
+ plane->base.id, plane->name);
+ return -EINVAL;
+ }
+ }
+
+ /* base address alignment check */
+ baseaddr = drm_plane_state_to_baseaddr(new_plane_state,
+ check_aux_source);
+ switch (fb->format->format) {
+ case DRM_FORMAT_YUYV:
+ case DRM_FORMAT_UYVY:
+ bpp = 16;
+ break;
+ case DRM_FORMAT_NV12:
+ case DRM_FORMAT_NV21:
+ bpp = 8;
+ break;
+ default:
+ bpp = fb->format->cpp[0] * 8;
+ break;
+ }
+ switch (bpp) {
+ case 32:
+ if (baseaddr & 0x3) {
+ DRM_DEBUG_KMS("[PLANE:%d:%s] 32bpp fb bad baddr alignment\n",
+ plane->base.id, plane->name);
+ return -EINVAL;
+ }
+ break;
+ case 16:
+ if (fb->modifier) {
+ if (baseaddr & 0x1) {
+ DRM_DEBUG_KMS("[PLANE:%d:%s] 16bpp tile fb bad baddr alignment\n",
+ plane->base.id, plane->name);
+ return -EINVAL;
+ }
+ } else {
+ if (check_aux_source) {
+ if (baseaddr &
+ (dpstate->use_aux_prefetch ? 0x7 : 0x1)) {
+ DRM_DEBUG_KMS("[PLANE:%d:%s] 16bpp fb bad baddr alignment\n",
+ plane->base.id, plane->name);
+ return -EINVAL;
+ }
+ } else {
+ if (baseaddr &
+ (dpstate->use_prefetch ? 0x7 : 0x1)) {
+ DRM_DEBUG_KMS("[PLANE:%d:%s] 16bpp fb bad baddr alignment\n",
+ plane->base.id, plane->name);
+ return -EINVAL;
+ }
+ }
+ }
+ break;
+ }
+
+ if (fb->pitches[0] > 0x10000) {
+ DRM_DEBUG_KMS("[PLANE:%d:%s] fb pitch[0] is too big\n",
+ plane->base.id, plane->name);
+ return -EINVAL;
+ }
+
+ /* UV base address alignment check, assuming 16bpp */
+ if (fb->format->num_planes > 1) {
+ uv_baseaddr = drm_plane_state_to_uvbaseaddr(new_plane_state,
+ check_aux_source);
+ if (fb->modifier) {
+ if (uv_baseaddr & 0x1) {
+ DRM_DEBUG_KMS("[PLANE:%d:%s] bad uv baddr alignment for tile fb\n",
+ plane->base.id, plane->name);
+ return -EINVAL;
+ }
+ } else {
+ if (check_aux_source) {
+ if (uv_baseaddr &
+ (dpstate->use_aux_prefetch ? 0x7 : 0x1)) {
+ DRM_DEBUG_KMS("[PLANE:%d:%s] bad uv baddr alignment\n",
+ plane->base.id, plane->name);
+ return -EINVAL;
+ }
+ } else {
+ if (uv_baseaddr &
+ (dpstate->use_prefetch ? 0x7 : 0x1)) {
+ DRM_DEBUG_KMS("[PLANE:%d:%s] bad uv baddr alignment\n",
+ plane->base.id, plane->name);
+ return -EINVAL;
+ }
+ }
+ }
+
+ if (fb->pitches[1] > 0x10000) {
+ DRM_DEBUG_KMS("[PLANE:%d:%s] fb pitch[1] is too big\n",
+ plane->base.id, plane->name);
+ return -EINVAL;
+ }
+ }
+
+ if (!check_aux_source && dpstate->use_prefetch &&
+ !dprc_stride_double_check(dprc, frame_width, src_x,
+ fb->format->format,
+ fb->modifier,
+ baseaddr, uv_baseaddr)) {
+ if (fb->modifier) {
+ DRM_DEBUG_KMS("[PLANE:%d:%s] bad pitch\n",
+ plane->base.id, plane->name);
+ return -EINVAL;
+ }
+
+ if (bpp == 16 && (baseaddr & 0x1)) {
+ DRM_DEBUG_KMS("[PLANE:%d:%s] bad baddr alignment\n",
+ plane->base.id, plane->name);
+ return -EINVAL;
+ }
+
+ if (uv_baseaddr & 0x1) {
+ DRM_DEBUG_KMS("[PLANE:%d:%s] bad uv baddr alignment\n",
+ plane->base.id, plane->name);
+ return -EINVAL;
+ }
+
+ dpstate->use_prefetch = false;
+ } else if (check_aux_source && dpstate->use_aux_prefetch &&
+ !dprc_stride_double_check(dprc, frame_width, src_x,
+ fb->format->format,
+ fb->modifier,
+ baseaddr, uv_baseaddr)) {
+ if (fb->modifier) {
+ DRM_DEBUG_KMS("[PLANE:%d:%s] bad pitch\n",
+ plane->base.id, plane->name);
+ return -EINVAL;
+ }
+
+ if (bpp == 16 && (baseaddr & 0x1)) {
+ DRM_DEBUG_KMS("[PLANE:%d:%s] bad baddr alignment\n",
+ plane->base.id, plane->name);
+ return -EINVAL;
+ }
+
+ if (uv_baseaddr & 0x1) {
+ DRM_DEBUG_KMS("[PLANE:%d:%s] bad uv baddr alignment\n",
+ plane->base.id, plane->name);
+ return -EINVAL;
+ }
+
+ dpstate->use_aux_prefetch = false;
+ }
+
+ if (dpstate->need_aux_source && !check_aux_source) {
+ check_aux_source = true;
+ goto again;
+ }
+
+ return 0;
+}
+
+static void dpu_plane_atomic_update(struct drm_plane *plane,
+ struct drm_atomic_state *state)
+{
+ struct dpu_plane *dplane = to_dpu_plane(plane);
+ struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state,
+ plane);
+ struct dpu_plane_state *dpstate = to_dpu_plane_state(new_plane_state);
+ struct drm_framebuffer *fb = new_plane_state->fb;
+ struct dpu_plane_res *res = &dplane->grp->res;
+ struct dpu_fetchunit *fu;
+ struct dpu_fetchunit *fe = NULL;
+ struct dprc *dprc;
+ struct dpu_hscaler *hs = NULL;
+ struct dpu_vscaler *vs = NULL;
+ struct dpu_layerblend *lb;
+ struct dpu_extdst *ed;
+ struct dpu_framegen *fg;
+ dma_addr_t baseaddr, uv_baseaddr = 0;
+ dpu_block_id_t blend, fe_id, vs_id = ID_NONE, hs_id;
+ lb_sec_sel_t source;
+ lb_prim_sel_t stage;
+ unsigned int stream_id;
+ unsigned int src_w, src_h, src_x, src_y, dst_w, dst_h;
+ unsigned int crtc_x;
+ unsigned int mt_w = 0, mt_h = 0; /* w/h in a micro-tile */
+ int bpp, lb_id;
+ bool need_fetcheco, need_hscaler = false, need_vscaler = false;
+ bool prefetch_start, uv_prefetch_start;
+ bool crtc_use_pc = dpstate->left_src_w || dpstate->right_src_w;
+ bool update_aux_source = false;
+ bool use_prefetch;
+ bool need_modeset;
+ bool fb_is_interlaced;
+
+ /*
+ * Do nothing since the plane is disabled by
+ * crtc_func->atomic_begin/flush.
+ */
+ if (!fb)
+ return;
+
+ need_modeset =
+ drm_atomic_crtc_needs_modeset(new_plane_state->crtc->state);
+ fb_is_interlaced = !!(fb->flags & DRM_MODE_FB_INTERLACED);
+
+again:
+ need_fetcheco = false;
+ prefetch_start = false;
+ uv_prefetch_start = false;
+
+ source = update_aux_source ? dpstate->aux_source : dpstate->source;
+ blend = update_aux_source ? dpstate->aux_blend : dpstate->blend;
+ stage = update_aux_source ? dpstate->aux_stage : dpstate->stage;
+ use_prefetch = update_aux_source ?
+ dpstate->use_aux_prefetch : dpstate->use_prefetch;
+
+ if (crtc_use_pc) {
+ if (update_aux_source) {
+ stream_id = 1;
+ crtc_x = dpstate->right_crtc_x;
+ } else {
+ stream_id = dpstate->left_src_w ? 0 : 1;
+ crtc_x = dpstate->left_src_w ?
+ dpstate->left_crtc_x : dpstate->right_crtc_x;
+ }
+ } else {
+ stream_id = dplane->stream_id;
+ crtc_x = new_plane_state->crtc_x;
+ }
+
+ fg = res->fg[stream_id];
+
+ fu = source_to_fu(res, source);
+ if (!fu)
+ return;
+
+ dprc = fu->dprc;
+
+ lb_id = blend_to_id(blend);
+ if (lb_id < 0)
+ return;
+
+ lb = res->lb[lb_id];
+
+ if (crtc_use_pc) {
+ if (update_aux_source || !dpstate->left_src_w)
+ src_w = dpstate->right_src_w;
+ else
+ src_w = dpstate->left_src_w;
+ } else {
+ src_w = drm_rect_width(&new_plane_state->src) >> 16;
+ }
+ src_h = drm_rect_height(&new_plane_state->src) >> 16;
+ if (crtc_use_pc && update_aux_source) {
+ if (fb->modifier)
+ src_x = (new_plane_state->src_x >> 16) +
+ dpstate->left_src_w;
+ else
+ src_x = 0;
+ } else {
+ src_x = fb->modifier ? (new_plane_state->src_x >> 16) : 0;
+ }
+ src_y = fb->modifier ? (new_plane_state->src_y >> 16) : 0;
+ dst_w = drm_rect_width(&new_plane_state->dst);
+ dst_h = drm_rect_height(&new_plane_state->dst);
+
+ if (fetchunit_is_fetchdecode(fu)) {
+ if (fetchdecode_need_fetcheco(fu, fb->format->format)) {
+ need_fetcheco = true;
+ fe = fetchdecode_get_fetcheco(fu);
+ if (IS_ERR(fe))
+ return;
+ }
+
+ /* assume pixel combiner is unused */
+ if ((src_w != dst_w) && !crtc_use_pc) {
+ need_hscaler = true;
+ hs = fetchdecode_get_hscaler(fu);
+ if (IS_ERR(hs))
+ return;
+ }
+
+ if ((src_h != dst_h) || fb_is_interlaced) {
+ need_vscaler = true;
+ vs = fetchdecode_get_vscaler(fu);
+ if (IS_ERR(vs))
+ return;
+ }
+ }
+
+ switch (fb->format->format) {
+ case DRM_FORMAT_YUYV:
+ case DRM_FORMAT_UYVY:
+ bpp = 16;
+ break;
+ case DRM_FORMAT_NV12:
+ case DRM_FORMAT_NV21:
+ bpp = 8;
+ break;
+ default:
+ bpp = fb->format->cpp[0] * 8;
+ break;
+ }
+
+ switch (fb->modifier) {
+ case DRM_FORMAT_MOD_AMPHION_TILED:
+ mt_w = 8;
+ mt_h = 8;
+ break;
+ case DRM_FORMAT_MOD_VIVANTE_TILED:
+ case DRM_FORMAT_MOD_VIVANTE_SUPER_TILED:
+ mt_w = (bpp == 16) ? 8 : 4;
+ mt_h = 4;
+ break;
+ default:
+ break;
+ }
+
+ baseaddr = drm_plane_state_to_baseaddr(new_plane_state,
+ update_aux_source);
+ if (need_fetcheco)
+ uv_baseaddr = drm_plane_state_to_uvbaseaddr(new_plane_state,
+ update_aux_source);
+
+ if (use_prefetch &&
+ (fu->ops->get_stream_id(fu) == DPU_PLANE_SRC_DISABLED ||
+ need_modeset))
+ prefetch_start = true;
+
+ fu->ops->set_burstlength(fu, src_x, mt_w, bpp, baseaddr, use_prefetch);
+ fu->ops->set_src_bpp(fu, bpp);
+ fu->ops->set_src_stride(fu, src_w, src_w, mt_w, bpp, fb->pitches[0],
+ baseaddr, use_prefetch);
+ fu->ops->set_src_buf_dimensions(fu, src_w, src_h, 0, fb_is_interlaced);
+ fu->ops->set_pixel_blend_mode(fu, new_plane_state->pixel_blend_mode,
+ new_plane_state->alpha,
+ fb->format->format);
+ fu->ops->set_fmt(fu, fb->format->format,
+ new_plane_state->color_encoding,
+ new_plane_state->color_range, fb_is_interlaced);
+ fu->ops->enable_src_buf(fu);
+ fu->ops->set_framedimensions(fu, src_w, src_h, fb_is_interlaced);
+ fu->ops->set_baseaddress(fu, src_w, src_x, src_y, mt_w, mt_h, bpp,
+ baseaddr);
+ fu->ops->set_stream_id(fu, stream_id ?
+ DPU_PLANE_SRC_TO_DISP_STREAM1 :
+ DPU_PLANE_SRC_TO_DISP_STREAM0);
+
+ DRM_DEBUG_KMS("[PLANE:%d:%s] %s-0x%02x\n",
+ plane->base.id, plane->name, fu->name, fu->id);
+
+ if (need_fetcheco) {
+ fe_id = fetcheco_get_block_id(fe);
+ if (fe_id == ID_NONE)
+ return;
+
+ if (use_prefetch &&
+ (fe->ops->get_stream_id(fe) == DPU_PLANE_SRC_DISABLED ||
+ need_modeset))
+ uv_prefetch_start = true;
+
+ fetchdecode_pixengcfg_dynamic_src_sel(fu,
+ (fd_dynamic_src_sel_t)fe_id);
+ fe->ops->set_burstlength(fe, src_w, mt_w, bpp, uv_baseaddr,
+ use_prefetch);
+ fe->ops->set_src_bpp(fe, 16);
+ fe->ops->set_src_stride(fe, src_w, src_x, mt_w, bpp,
+ fb->pitches[1],
+ uv_baseaddr, use_prefetch);
+ fe->ops->set_fmt(fe, fb->format->format,
+ new_plane_state->color_encoding,
+ new_plane_state->color_range,
+ fb_is_interlaced);
+ fe->ops->set_src_buf_dimensions(fe, src_w, src_h,
+ fb->format->format,
+ fb_is_interlaced);
+ fe->ops->set_framedimensions(fe, src_w, src_h,
+ fb_is_interlaced);
+ fe->ops->set_baseaddress(fe, src_w, src_x, src_y / 2,
+ mt_w, mt_h, bpp, uv_baseaddr);
+ fe->ops->enable_src_buf(fe);
+ fe->ops->set_stream_id(fe, stream_id ?
+ DPU_PLANE_SRC_TO_DISP_STREAM1 :
+ DPU_PLANE_SRC_TO_DISP_STREAM0);
+
+ DRM_DEBUG_KMS("[PLANE:%d:%s] %s-0x%02x\n",
+ plane->base.id, plane->name, fe->name, fe_id);
+ } else {
+ if (fetchunit_is_fetchdecode(fu))
+ fetchdecode_pixengcfg_dynamic_src_sel(fu,
+ FD_SRC_DISABLE);
+ }
+
+ /* vscaler comes first */
+ if (need_vscaler) {
+ vs_id = vscaler_get_block_id(vs);
+ if (vs_id == ID_NONE)
+ return;
+
+ vscaler_pixengcfg_dynamic_src_sel(vs, (vs_src_sel_t)source);
+ vscaler_pixengcfg_clken(vs, CLKEN__AUTOMATIC);
+ vscaler_setup1(vs, src_h, new_plane_state->crtc_h,
+ fb_is_interlaced);
+ vscaler_setup2(vs, fb_is_interlaced);
+ vscaler_setup3(vs, fb_is_interlaced);
+ vscaler_output_size(vs, dst_h);
+ vscaler_field_mode(vs, fb_is_interlaced ?
+ SCALER_ALWAYS0 : SCALER_INPUT);
+ vscaler_filter_mode(vs, SCALER_LINEAR);
+ vscaler_scale_mode(vs, SCALER_UPSCALE);
+ vscaler_mode(vs, SCALER_ACTIVE);
+ vscaler_set_stream_id(vs, dplane->stream_id ?
+ DPU_PLANE_SRC_TO_DISP_STREAM1 :
+ DPU_PLANE_SRC_TO_DISP_STREAM0);
+
+ source = (lb_sec_sel_t)vs_id;
+
+ DRM_DEBUG_KMS("[PLANE:%d:%s] vscaler-0x%02x\n",
+ plane->base.id, plane->name, vs_id);
+ }
+
+ /* and then, hscaler */
+ if (need_hscaler) {
+ hs_id = hscaler_get_block_id(hs);
+ if (hs_id == ID_NONE)
+ return;
+
+ hscaler_pixengcfg_dynamic_src_sel(hs, need_vscaler ?
+ (hs_src_sel_t)vs_id :
+ (hs_src_sel_t)source);
+ hscaler_pixengcfg_clken(hs, CLKEN__AUTOMATIC);
+ hscaler_setup1(hs, src_w, dst_w);
+ hscaler_output_size(hs, dst_w);
+ hscaler_filter_mode(hs, SCALER_LINEAR);
+ hscaler_scale_mode(hs, SCALER_UPSCALE);
+ hscaler_mode(hs, SCALER_ACTIVE);
+ hscaler_set_stream_id(hs, dplane->stream_id ?
+ DPU_PLANE_SRC_TO_DISP_STREAM1 :
+ DPU_PLANE_SRC_TO_DISP_STREAM0);
+
+ source = (lb_sec_sel_t)hs_id;
+
+ DRM_DEBUG_KMS("[PLANE:%d:%s] hscaler-0x%02x\n",
+ plane->base.id, plane->name, hs_id);
+ }
+
+ if (use_prefetch) {
+ dprc_configure(dprc, stream_id,
+ src_w, src_h, src_x, src_y,
+ fb->pitches[0], fb->format->format,
+ fb->modifier, baseaddr, uv_baseaddr,
+ prefetch_start, uv_prefetch_start,
+ fb_is_interlaced);
+
+ dprc_enable(dprc);
+
+ dprc_reg_update(dprc);
+
+ if (prefetch_start || uv_prefetch_start) {
+ dprc_first_frame_handle(dprc);
+
+ if (!need_modeset &&
+ new_plane_state->normalized_zpos != 0)
+ framegen_wait_for_frame_counter_moving(fg);
+ }
+
+ if (update_aux_source)
+ DRM_DEBUG_KMS("[PLANE:%d:%s] use aux prefetch\n",
+ plane->base.id, plane->name);
+ else
+ DRM_DEBUG_KMS("[PLANE:%d:%s] use prefetch\n",
+ plane->base.id, plane->name);
+ } else if (dprc) {
+ dprc_disable(dprc);
+
+ if (update_aux_source)
+ DRM_DEBUG_KMS("[PLANE:%d:%s] bypass aux prefetch\n",
+ plane->base.id, plane->name);
+ else
+ DRM_DEBUG_KMS("[PLANE:%d:%s] bypass prefetch\n",
+ plane->base.id, plane->name);
+ }
+
+ layerblend_pixengcfg_dynamic_prim_sel(lb, stage);
+ layerblend_pixengcfg_dynamic_sec_sel(lb, source);
+ layerblend_control(lb, LB_BLEND);
+ layerblend_blendcontrol(lb, new_plane_state->normalized_zpos,
+ new_plane_state->pixel_blend_mode,
+ new_plane_state->alpha);
+ layerblend_pixengcfg_clken(lb, CLKEN__AUTOMATIC);
+ layerblend_position(lb, crtc_x, new_plane_state->crtc_y);
+
+ if (crtc_use_pc) {
+ if ((!stream_id && dpstate->is_left_top) ||
+ (stream_id && dpstate->is_right_top)) {
+ ed = res->ed[stream_id];
+ extdst_pixengcfg_src_sel(ed, (extdst_src_sel_t)blend);
+ }
+ } else {
+ if (dpstate->is_top) {
+ ed = res->ed[stream_id];
+ extdst_pixengcfg_src_sel(ed, (extdst_src_sel_t)blend);
+ }
+ }
+
+ if (update_aux_source)
+ DRM_DEBUG_KMS("[PLANE:%d:%s] *aux* source-0x%02x stage-0x%02x blend-0x%02x\n",
+ plane->base.id, plane->name,
+ source, dpstate->stage, dpstate->blend);
+ else
+ DRM_DEBUG_KMS("[PLANE:%d:%s] source-0x%02x stage-0x%02x blend-0x%02x\n",
+ plane->base.id, plane->name,
+ source, dpstate->stage, dpstate->blend);
+
+ if (dpstate->need_aux_source && !update_aux_source) {
+ update_aux_source = true;
+ goto again;
+ }
+}
+
+static const struct drm_plane_helper_funcs dpu_plane_helper_funcs = {
+ .prepare_fb = drm_gem_plane_helper_prepare_fb,
+ .atomic_check = dpu_plane_atomic_check,
+ .atomic_update = dpu_plane_atomic_update,
+};
+
+struct dpu_plane *dpu_plane_create(struct drm_device *drm,
+ unsigned int possible_crtcs,
+ unsigned int stream_id,
+ struct dpu_plane_grp *grp,
+ enum drm_plane_type type)
+{
+ struct dpu_plane *dpu_plane;
+ struct drm_plane *plane;
+ unsigned int zpos = dpu_plane_get_default_zpos(type);
+ int ret;
+
+ dpu_plane = kzalloc(sizeof(*dpu_plane), GFP_KERNEL);
+ if (!dpu_plane)
+ return ERR_PTR(-ENOMEM);
+
+ dpu_plane->stream_id = stream_id;
+ dpu_plane->grp = grp;
+
+ plane = &dpu_plane->base;
+
+ ret = drm_universal_plane_init(drm, plane, possible_crtcs,
+ &dpu_plane_funcs,
+ dpu_formats, ARRAY_SIZE(dpu_formats),
+ dpu_format_modifiers, type, NULL);
+ if (ret)
+ goto err;
+
+ drm_plane_helper_add(plane, &dpu_plane_helper_funcs);
+
+ ret = drm_plane_create_zpos_property(plane,
+ zpos, 0, grp->hw_plane_num - 1);
+ if (ret)
+ goto err;
+
+ ret = drm_plane_create_alpha_property(plane);
+ if (ret)
+ goto err;
+
+ ret = drm_plane_create_blend_mode_property(plane,
+ BIT(DRM_MODE_BLEND_PIXEL_NONE) |
+ BIT(DRM_MODE_BLEND_PREMULTI) |
+ BIT(DRM_MODE_BLEND_COVERAGE));
+ if (ret)
+ goto err;
+
+ ret = drm_plane_create_color_properties(plane,
+ BIT(DRM_COLOR_YCBCR_BT601) |
+ BIT(DRM_COLOR_YCBCR_BT709),
+ BIT(DRM_COLOR_YCBCR_LIMITED_RANGE) |
+ BIT(DRM_COLOR_YCBCR_FULL_RANGE),
+ DRM_COLOR_YCBCR_BT601,
+ DRM_COLOR_YCBCR_FULL_RANGE);
+ if (ret)
+ goto err;
+
+ return dpu_plane;
+
+err:
+ kfree(dpu_plane);
+ return ERR_PTR(ret);
+}
diff --git a/drivers/gpu/drm/imx/dpu/dpu-plane.h b/drivers/gpu/drm/imx/dpu/dpu-plane.h
new file mode 100644
index 000000000000..8d42ad0c9ff4
--- /dev/null
+++ b/drivers/gpu/drm/imx/dpu/dpu-plane.h
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2017-2019,2022 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef __DPU_PLANE_H__
+#define __DPU_PLANE_H__
+
+#include <video/dpu.h>
+#include "../imx-drm.h"
+
+#define MAX_DPU_PLANE_GRP (MAX_CRTC / 2)
+
+enum dpu_plane_src_type {
+ DPU_PLANE_SRC_FL,
+ DPU_PLANE_SRC_FW,
+ DPU_PLANE_SRC_FD,
+};
+
+struct dpu_plane {
+ struct drm_plane base;
+ struct dpu_plane_grp *grp;
+ struct list_head head;
+ unsigned int stream_id;
+};
+
+struct dpu_plane_state {
+ struct drm_plane_state base;
+ lb_prim_sel_t stage;
+ lb_sec_sel_t source;
+ dpu_block_id_t blend;
+ lb_prim_sel_t aux_stage;
+ lb_sec_sel_t aux_source;
+ dpu_block_id_t aux_blend;
+
+ bool is_top;
+ bool use_prefetch;
+ bool use_aux_prefetch;
+ bool need_aux_source;
+
+ /* used when pixel combiner is needed */
+ unsigned int left_src_w;
+ unsigned int left_crtc_w;
+ unsigned int left_crtc_x;
+ unsigned int right_src_w;
+ unsigned int right_crtc_w;
+ unsigned int right_crtc_x;
+
+ bool is_left_top;
+ bool is_right_top;
+};
+
+static const lb_prim_sel_t cf_stages[] = {LB_PRIM_SEL__CONSTFRAME0,
+ LB_PRIM_SEL__CONSTFRAME1};
+static const lb_prim_sel_t stages[] = {LB_PRIM_SEL__LAYERBLEND0,
+ LB_PRIM_SEL__LAYERBLEND1,
+ LB_PRIM_SEL__LAYERBLEND2,
+ LB_PRIM_SEL__LAYERBLEND3};
+/* TODO: Add source entries for subsidiary layers. */
+static const lb_sec_sel_t sources[] = {LB_SEC_SEL__FETCHLAYER0,
+ LB_SEC_SEL__FETCHWARP2,
+ LB_SEC_SEL__FETCHDECODE0,
+ LB_SEC_SEL__FETCHDECODE1};
+static const dpu_block_id_t blends[] = {ID_LAYERBLEND0, ID_LAYERBLEND1,
+ ID_LAYERBLEND2, ID_LAYERBLEND3};
+
+static inline struct dpu_plane *to_dpu_plane(struct drm_plane *plane)
+{
+ return container_of(plane, struct dpu_plane, base);
+}
+
+static inline struct dpu_plane_state *
+to_dpu_plane_state(struct drm_plane_state *plane_state)
+{
+ return container_of(plane_state, struct dpu_plane_state, base);
+}
+
+static inline int source_to_type(lb_sec_sel_t source)
+{
+ switch (source) {
+ case LB_SEC_SEL__FETCHLAYER0:
+ return DPU_PLANE_SRC_FL;
+ case LB_SEC_SEL__FETCHWARP2:
+ return DPU_PLANE_SRC_FW;
+ case LB_SEC_SEL__FETCHDECODE0:
+ case LB_SEC_SEL__FETCHDECODE1:
+ return DPU_PLANE_SRC_FD;
+ default:
+ break;
+ }
+
+ WARN_ON(1);
+ return -EINVAL;
+}
+
+static inline int source_to_id(lb_sec_sel_t source)
+{
+ int i, offset = 0;
+ int type = source_to_type(source);
+
+ for (i = 0; i < ARRAY_SIZE(sources); i++) {
+ if (source != sources[i])
+ continue;
+
+ /* FetchLayer */
+ if (type == DPU_PLANE_SRC_FL)
+ return i;
+
+ /* FetchWarp or FetchDecode */
+ while (offset < ARRAY_SIZE(sources)) {
+ if (source_to_type(sources[offset]) == type)
+ break;
+ offset++;
+ }
+ return i - offset;
+ }
+
+ WARN_ON(1);
+ return -EINVAL;
+}
+
+static inline struct dpu_fetchunit *
+source_to_fu(struct dpu_plane_res *res, lb_sec_sel_t source)
+{
+ int fu_type = source_to_type(source);
+ int fu_id = source_to_id(source);
+
+ if (fu_type < 0 || fu_id < 0)
+ return NULL;
+
+ switch (fu_type) {
+ case DPU_PLANE_SRC_FD:
+ if (fu_id >= ARRAY_SIZE(res->fd)) {
+ WARN_ON(1);
+ return NULL;
+ }
+
+ return res->fd[fu_id];
+ case DPU_PLANE_SRC_FL:
+ if (fu_id >= ARRAY_SIZE(res->fl)) {
+ WARN_ON(1);
+ return NULL;
+ }
+
+ return res->fl[fu_id];
+ case DPU_PLANE_SRC_FW:
+ if (fu_id >= ARRAY_SIZE(res->fw)) {
+ WARN_ON(1);
+ return NULL;
+ }
+
+ return res->fw[fu_id];
+ }
+
+ return NULL;
+}
+
+static inline struct dpu_fetchunit *
+dpstate_to_fu(struct dpu_plane_state *dpstate)
+{
+ struct drm_plane *plane = dpstate->base.plane;
+ struct dpu_plane *dplane = to_dpu_plane(plane);
+ struct dpu_plane_res *res = &dplane->grp->res;
+
+ return source_to_fu(res, dpstate->source);
+}
+
+static inline int blend_to_id(dpu_block_id_t blend)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(blends); i++) {
+ if (blend == blends[i])
+ return i;
+ }
+
+ WARN_ON(1);
+ return -EINVAL;
+}
+
+static inline bool drm_format_is_yuv(uint32_t format)
+{
+ switch (format) {
+ case DRM_FORMAT_YUYV:
+ case DRM_FORMAT_UYVY:
+ case DRM_FORMAT_NV12:
+ case DRM_FORMAT_NV21:
+ return true;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+struct dpu_plane *dpu_plane_create(struct drm_device *drm,
+ unsigned int possible_crtcs,
+ unsigned int stream_id,
+ struct dpu_plane_grp *grp,
+ enum drm_plane_type type);
+#endif
diff --git a/drivers/gpu/drm/imx/dw_hdmi-imx.c b/drivers/gpu/drm/imx/dw_hdmi-imx.c
index a2277a0d6d06..b08eae5278d0 100644
--- a/drivers/gpu/drm/imx/dw_hdmi-imx.c
+++ b/drivers/gpu/drm/imx/dw_hdmi-imx.c
@@ -9,7 +9,9 @@
#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h>
#include <linux/module.h>
#include <linux/platform_device.h>
+#include <linux/phy/phy.h>
#include <linux/regmap.h>
+#include <linux/reset.h>
#include <video/imx-ipu-v3.h>
@@ -22,6 +24,7 @@
#include <drm/drm_of.h>
#include <drm/drm_simple_kms_helper.h>
+#include "imx8mp-hdmi-pavi.h"
#include "imx-drm.h"
struct imx_hdmi;
@@ -31,11 +34,20 @@ struct imx_hdmi_encoder {
struct imx_hdmi *hdmi;
};
+/* GPR reg */
+struct imx_hdmi_chip_data {
+ int reg_offset;
+ u32 mask_bits;
+ u32 shift_bit;
+};
+
struct imx_hdmi {
struct device *dev;
struct drm_bridge *bridge;
struct dw_hdmi *hdmi;
struct regmap *regmap;
+ const struct imx_hdmi_chip_data *chip_data;
+ struct phy *phy;
};
static inline struct imx_hdmi *enc_to_imx_hdmi(struct drm_encoder *e)
@@ -43,6 +55,19 @@ static inline struct imx_hdmi *enc_to_imx_hdmi(struct drm_encoder *e)
return container_of(e, struct imx_hdmi_encoder, encoder)->hdmi;
}
+struct clk_bulk_data imx8mp_clocks[] = {
+ { .id = "pix_clk" },
+ { .id = "phy_int" },
+ { .id = "prep_clk" },
+ { .id = "skp_clk" },
+ { .id = "sfr_clk" },
+ { .id = "cec_clk" },
+ { .id = "apb_clk" },
+ { .id = "hpi_clk" },
+ { .id = "fdcc_ref" },
+ { .id = "pipe_clk" },
+};
+
static const struct dw_hdmi_mpll_config imx_mpll_cfg[] = {
{
45250000, {
@@ -101,7 +126,7 @@ static const struct dw_hdmi_curr_ctrl imx_cur_ctr[] = {
* PREEMP config 0.00
* TX/CK level 10
*/
-static const struct dw_hdmi_phy_config imx_phy_config[] = {
+static const struct dw_hdmi_phy_config imx6_phy_config[] = {
/*pixelclk symbol term vlev */
{ 216000000, 0x800d, 0x0005, 0x01ad},
{ ~0UL, 0x0000, 0x0000, 0x0000}
@@ -112,9 +137,11 @@ static void dw_hdmi_imx_encoder_enable(struct drm_encoder *encoder)
struct imx_hdmi *hdmi = enc_to_imx_hdmi(encoder);
int mux = drm_of_encoder_active_port_id(hdmi->dev->of_node, encoder);
- regmap_update_bits(hdmi->regmap, IOMUXC_GPR3,
- IMX6Q_GPR3_HDMI_MUX_CTL_MASK,
- mux << IMX6Q_GPR3_HDMI_MUX_CTL_SHIFT);
+ if (hdmi->chip_data->reg_offset < 0)
+ return;
+
+ regmap_update_bits(hdmi->regmap, hdmi->chip_data->reg_offset,
+ hdmi->chip_data->mask_bits, mux << hdmi->chip_data->shift_bit);
}
static int dw_hdmi_imx_atomic_check(struct drm_encoder *encoder,
@@ -124,6 +151,7 @@ static int dw_hdmi_imx_atomic_check(struct drm_encoder *encoder,
struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc_state);
imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB888_1X24;
+ imx_crtc_state->bus_flags = DRM_BUS_FLAG_DE_HIGH;
imx_crtc_state->di_hsync_pin = 2;
imx_crtc_state->di_vsync_pin = 3;
@@ -163,18 +191,169 @@ imx6dl_hdmi_mode_valid(struct dw_hdmi *hdmi, void *data,
return MODE_OK;
}
+static bool imx8mp_hdmi_check_clk_rate(int rate_khz)
+{
+ int rate = rate_khz * 1000;
+
+ /* Check hdmi phy pixel clock support rate */
+ if (rate != clk_round_rate(imx8mp_clocks[0].clk, rate))
+ return false;
+ return true;
+}
+
+static enum drm_mode_status
+imx8mp_hdmi_mode_valid(struct dw_hdmi *hdmi, void *data,
+ const struct drm_display_info *info,
+ const struct drm_display_mode *mode)
+{
+ if (mode->clock < 13500)
+ return MODE_CLOCK_LOW;
+ if (mode->clock > 297000)
+ return MODE_CLOCK_HIGH;
+
+ if (!imx8mp_hdmi_check_clk_rate(mode->clock))
+ return MODE_CLOCK_RANGE;
+
+ /* We don't support double-clocked and Interlaced modes */
+ if (mode->flags & DRM_MODE_FLAG_DBLCLK ||
+ mode->flags & DRM_MODE_FLAG_INTERLACE)
+ return MODE_BAD;
+
+ return MODE_OK;
+}
+
+struct imx_hdmi_chip_data imx6_chip_data = {
+ .reg_offset = IOMUXC_GPR3,
+ .mask_bits = IMX6Q_GPR3_HDMI_MUX_CTL_MASK,
+ .shift_bit = IMX6Q_GPR3_HDMI_MUX_CTL_SHIFT,
+};
+
static struct dw_hdmi_plat_data imx6q_hdmi_drv_data = {
.mpll_cfg = imx_mpll_cfg,
.cur_ctr = imx_cur_ctr,
- .phy_config = imx_phy_config,
+ .phy_config = imx6_phy_config,
.mode_valid = imx6q_hdmi_mode_valid,
+ .phy_data = &imx6_chip_data,
};
static struct dw_hdmi_plat_data imx6dl_hdmi_drv_data = {
.mpll_cfg = imx_mpll_cfg,
.cur_ctr = imx_cur_ctr,
- .phy_config = imx_phy_config,
+ .phy_config = imx6_phy_config,
.mode_valid = imx6dl_hdmi_mode_valid,
+ .phy_data = &imx6_chip_data,
+};
+
+static int imx8mp_hdmi_phy_init(struct dw_hdmi *dw_hdmi, void *data,
+ const struct drm_display_info *display,
+ const struct drm_display_mode *mode)
+{
+ struct imx_hdmi *hdmi = (struct imx_hdmi *)data;
+ int val;
+
+ dev_dbg(hdmi->dev, "%s\n", __func__);
+
+ dw_hdmi_phy_reset(dw_hdmi);
+
+ /* enable PVI */
+ imx8mp_hdmi_pavi_powerup();
+ imx8mp_hdmi_pvi_enable(mode);
+
+ regmap_read(hdmi->regmap, 0x200, &val);
+ /* HDMI PHY power off */
+ val |= 0x8;
+ regmap_write(hdmi->regmap, 0x200, val);
+ /* HDMI PHY power on */
+ val &= ~0x8;
+ /* Enable CEC */
+ val |= 0x2;
+ regmap_write(hdmi->regmap, 0x200, val);
+
+ if (!hdmi->phy)
+ return 0;
+
+ phy_power_on(hdmi->phy);
+
+ return 0;
+}
+
+static void imx8mp_hdmi_phy_disable(struct dw_hdmi *dw_hdmi, void *data)
+{
+ struct imx_hdmi *hdmi = (struct imx_hdmi *)data;
+ int val;
+
+ dev_dbg(hdmi->dev, "%s\n", __func__);
+ if (!hdmi->phy)
+ return;
+
+ /* disable PVI */
+ imx8mp_hdmi_pvi_disable();
+ imx8mp_hdmi_pavi_powerdown();
+
+ /* TODO */
+ regmap_read(hdmi->regmap, 0x200, &val);
+ /* Disable CEC */
+ val &= ~0x2;
+ /* Power down HDMI PHY
+ * TODO move PHY power off to hdmi phy driver
+ * val |= 0x8;
+ * regmap_write(hdmi->regmap, 0x200, val);
+ */
+}
+
+static int imx8mp_hdmimix_setup(struct imx_hdmi *hdmi)
+{
+ int ret;
+
+ if (NULL == imx8mp_hdmi_pavi_init()) {
+ dev_err(hdmi->dev, "No pavi info found\n");
+ return -EPROBE_DEFER;
+ }
+
+ ret = device_reset(hdmi->dev);
+ if (ret == -EPROBE_DEFER)
+ return ret;
+
+ ret = devm_clk_bulk_get(hdmi->dev, ARRAY_SIZE(imx8mp_clocks), imx8mp_clocks);
+ if (ret < 0) {
+ dev_err(hdmi->dev, "No hdmimix bulk clk got\n");
+ return -EPROBE_DEFER;
+ }
+
+ return clk_bulk_prepare_enable(ARRAY_SIZE(imx8mp_clocks), imx8mp_clocks);
+}
+
+void imx8mp_hdmi_enable_audio(struct dw_hdmi *dw_hdmi, void *data, int channel,
+ int width, int rate, int non_pcm)
+{
+ imx8mp_hdmi_pai_enable(channel, width, rate, non_pcm);
+}
+
+void imx8mp_hdmi_disable_audio(struct dw_hdmi *dw_hdmi, void *data)
+{
+ imx8mp_hdmi_pai_disable();
+}
+
+static const struct dw_hdmi_phy_ops imx8mp_hdmi_phy_ops = {
+ .init = imx8mp_hdmi_phy_init,
+ .disable = imx8mp_hdmi_phy_disable,
+ .read_hpd = dw_hdmi_phy_read_hpd,
+ .update_hpd = dw_hdmi_phy_update_hpd,
+ .setup_hpd = dw_hdmi_phy_setup_hpd,
+ .enable_audio = imx8mp_hdmi_enable_audio,
+ .disable_audio = imx8mp_hdmi_disable_audio,
+};
+
+struct imx_hdmi_chip_data imx8mp_chip_data = {
+ .reg_offset = -1,
+};
+
+static const struct dw_hdmi_plat_data imx8mp_hdmi_drv_data = {
+ .mode_valid = imx8mp_hdmi_mode_valid,
+ .phy_data = &imx8mp_chip_data,
+ .phy_ops = &imx8mp_hdmi_phy_ops,
+ .phy_name = "samsung_dw_hdmi_phy2",
+ .phy_force_vendor = true,
};
static const struct of_device_id dw_hdmi_imx_dt_ids[] = {
@@ -183,6 +362,9 @@ static const struct of_device_id dw_hdmi_imx_dt_ids[] = {
}, {
.compatible = "fsl,imx6dl-hdmi",
.data = &imx6dl_hdmi_drv_data
+ }, {
+ .compatible = "fsl,imx8mp-hdmi",
+ .data = &imx8mp_hdmi_drv_data
},
{},
};
@@ -221,6 +403,7 @@ static int dw_hdmi_imx_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
const struct of_device_id *match = of_match_node(dw_hdmi_imx_dt_ids, np);
+ struct dw_hdmi_plat_data *plat_data;
struct imx_hdmi *hdmi;
int ret;
@@ -237,7 +420,29 @@ static int dw_hdmi_imx_probe(struct platform_device *pdev)
return PTR_ERR(hdmi->regmap);
}
- hdmi->hdmi = dw_hdmi_probe(pdev, match->data);
+ hdmi->phy = devm_phy_optional_get(hdmi->dev, "hdmi");
+ if (IS_ERR(hdmi->phy)) {
+ ret = PTR_ERR(hdmi->phy);
+ if (ret != -EPROBE_DEFER)
+ dev_err(hdmi->dev, "failed to get phy\n");
+ return ret;
+ }
+
+ plat_data = devm_kmemdup(&pdev->dev, match->data,
+ sizeof(*plat_data), GFP_KERNEL);
+ if (!plat_data)
+ return -ENOMEM;
+
+ hdmi->chip_data = plat_data->phy_data;
+ plat_data->phy_data = hdmi;
+
+ if (of_device_is_compatible(pdev->dev.of_node, "fsl,imx8mp-hdmi")) {
+ ret = imx8mp_hdmimix_setup(hdmi);
+ if (ret < 0)
+ return ret;
+ }
+
+ hdmi->hdmi = dw_hdmi_probe(pdev, plat_data);
if (IS_ERR(hdmi->hdmi))
return PTR_ERR(hdmi->hdmi);
@@ -265,11 +470,25 @@ static int dw_hdmi_imx_remove(struct platform_device *pdev)
return 0;
}
+static int __maybe_unused dw_hdmi_imx_resume(struct device *dev)
+{
+ struct imx_hdmi *hdmi = dev_get_drvdata(dev);
+
+ dw_hdmi_resume(hdmi->hdmi);
+
+ return 0;
+}
+
+static const struct dev_pm_ops dw_hdmi_imx_pm = {
+ SET_SYSTEM_SLEEP_PM_OPS(NULL, dw_hdmi_imx_resume)
+};
+
static struct platform_driver dw_hdmi_imx_platform_driver = {
.probe = dw_hdmi_imx_probe,
.remove = dw_hdmi_imx_remove,
.driver = {
.name = "dwhdmi-imx",
+ .pm = &dw_hdmi_imx_pm,
.of_match_table = dw_hdmi_imx_dt_ids,
},
};
diff --git a/drivers/gpu/drm/imx/dw_mipi_dsi-imx.c b/drivers/gpu/drm/imx/dw_mipi_dsi-imx.c
new file mode 100644
index 000000000000..40de8b031d92
--- /dev/null
+++ b/drivers/gpu/drm/imx/dw_mipi_dsi-imx.c
@@ -0,0 +1,621 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2022 NXP
+ */
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/phy/phy.h>
+#include <linux/phy/phy-mipi-dphy.h>
+#include <linux/pm_runtime.h>
+
+#include <drm/bridge/dw_mipi_dsi.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_modeset_helper_vtables.h>
+#include <drm/drm_of.h>
+#include <drm/drm_print.h>
+#include <drm/drm_simple_kms_helper.h>
+
+#include "imx-drm.h"
+
+#define enc_to_dsi(enc) container_of(enc, struct dw_mipi_dsi_imx, encoder)
+
+struct dw_mipi_dsi_imx {
+ struct device *dev;
+ struct drm_encoder encoder;
+ void __iomem *base;
+
+ struct clk *byte_clk;
+
+ struct phy *phy;
+ union phy_configure_opts phy_cfg;
+
+ u32 lanes;
+ u32 format;
+
+ struct dw_mipi_dsi *dmd;
+ struct dw_mipi_dsi_plat_data pdata;
+};
+
+static enum drm_mode_status
+__dw_mipi_dsi_imx_mode_valid(void *priv_data,
+ const struct drm_display_mode *mode,
+ union phy_configure_opts *phy_cfg)
+{
+ struct dw_mipi_dsi_imx *dsi = priv_data;
+ struct device *dev = dsi->dev;
+ int bpp;
+ int ret;
+
+ bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
+ if (bpp < 0) {
+ DRM_DEV_DEBUG(dev, "failed to get bpp for pixel format %d\n",
+ dsi->format);
+ return MODE_ERROR;
+ }
+
+ ret = phy_mipi_dphy_get_default_config(mode->clock * MSEC_PER_SEC,
+ bpp, dsi->lanes,
+ &phy_cfg->mipi_dphy);
+ if (ret < 0) {
+ DRM_DEV_DEBUG(dev, "failed to get default phy cfg %d\n", ret);
+ return MODE_ERROR;
+ }
+
+ ret = phy_validate(dsi->phy, PHY_MODE_MIPI_DPHY, 0, phy_cfg);
+ if (ret < 0) {
+ DRM_DEV_DEBUG(dev, "failed to validate phy cfg %d\n", ret);
+ return MODE_ERROR;
+ }
+
+ return MODE_OK;
+}
+
+static enum drm_mode_status
+dw_mipi_dsi_imx_mode_valid(void *priv_data,
+ const struct drm_display_mode *mode)
+{
+ union phy_configure_opts phy_cfg;
+
+ return __dw_mipi_dsi_imx_mode_valid(priv_data, mode, &phy_cfg);
+}
+
+static int dw_mipi_dsi_imx_phy_init(void *priv_data)
+{
+ struct dw_mipi_dsi_imx *dsi = priv_data;
+ int ret;
+
+ ret = phy_set_mode(dsi->phy, PHY_MODE_MIPI_DPHY);
+ if (ret) {
+ DRM_DEV_ERROR(dsi->dev, "failed to set phy mode: %d\n", ret);
+ return ret;
+ }
+
+ ret = phy_init(dsi->phy);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dsi->dev, "failed to init phy: %d\n", ret);
+ return ret;
+ }
+
+ ret = phy_configure(dsi->phy, &dsi->phy_cfg);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dsi->dev, "failed to configure phy: %d\n", ret);
+ goto uninit_phy;
+ }
+
+ ret = phy_power_on(dsi->phy);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dsi->dev, "failed to power on phy: %d\n", ret);
+ goto uninit_phy;
+ }
+
+ return ret;
+
+uninit_phy:
+ phy_exit(dsi->phy);
+ return ret;
+}
+
+static void dw_mipi_dsi_imx_phy_power_off(void *priv_data)
+{
+ struct dw_mipi_dsi_imx *dsi = priv_data;
+ int ret;
+
+ ret = phy_power_off(dsi->phy);
+ if (ret < 0)
+ DRM_DEV_ERROR(dsi->dev, "failed to power off phy: %d\n", ret);
+
+ ret = phy_exit(dsi->phy);
+ if (ret < 0)
+ DRM_DEV_ERROR(dsi->dev, "failed to exit phy: %d\n", ret);
+}
+
+static int
+dw_mipi_dsi_get_lane_mbps(void *priv_data, const struct drm_display_mode *mode,
+ unsigned long mode_flags, u32 lanes, u32 format,
+ unsigned int *lane_mbps)
+{
+ struct dw_mipi_dsi_imx *dsi = priv_data;
+ struct device *dev = dsi->dev;
+ unsigned long mpclk = DIV_ROUND_UP(mode->clock, MSEC_PER_SEC);
+ int bpp;
+ int ret;
+
+ bpp = mipi_dsi_pixel_format_to_bpp(format);
+ if (bpp < 0) {
+ DRM_DEV_ERROR(dev, "failed to get bpp for pixel format %d\n",
+ format);
+ return bpp;
+ }
+
+ *lane_mbps = mpclk * (bpp / lanes);
+
+ ret = phy_mipi_dphy_get_default_config(mode->clock * MSEC_PER_SEC,
+ bpp, lanes,
+ &dsi->phy_cfg.mipi_dphy);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dev, "failed to get default phy cfg %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+struct hstt {
+ unsigned int maxfreq;
+ struct dw_mipi_dsi_dphy_timing timing;
+};
+
+#define HSTT(_maxfreq, _c_lp2hs, _c_hs2lp, _d_lp2hs, _d_hs2lp) \
+{ \
+ .maxfreq = (_maxfreq), \
+ .timing = { \
+ .clk_lp2hs = (_c_lp2hs), \
+ .clk_hs2lp = (_c_hs2lp), \
+ .data_lp2hs = (_d_lp2hs), \
+ .data_hs2lp = (_d_hs2lp), \
+ } \
+}
+
+/* Table A-4 High-Speed Transition Times */
+struct hstt hstt_table[] = {
+ HSTT(80, 21, 17, 15, 10),
+ HSTT(90, 23, 17, 16, 10),
+ HSTT(100, 22, 17, 16, 10),
+ HSTT(110, 25, 18, 17, 11),
+ HSTT(120, 26, 20, 18, 11),
+ HSTT(130, 27, 19, 19, 11),
+ HSTT(140, 27, 19, 19, 11),
+ HSTT(150, 28, 20, 20, 12),
+ HSTT(160, 30, 21, 22, 13),
+ HSTT(170, 30, 21, 23, 13),
+ HSTT(180, 31, 21, 23, 13),
+ HSTT(190, 32, 22, 24, 13),
+ HSTT(205, 35, 22, 25, 13),
+ HSTT(220, 37, 26, 27, 15),
+ HSTT(235, 38, 28, 27, 16),
+ HSTT(250, 41, 29, 30, 17),
+ HSTT(275, 43, 29, 32, 18),
+ HSTT(300, 45, 32, 35, 19),
+ HSTT(325, 48, 33, 36, 18),
+ HSTT(350, 51, 35, 40, 20),
+ HSTT(400, 59, 37, 44, 21),
+ HSTT(450, 65, 40, 49, 23),
+ HSTT(500, 71, 41, 54, 24),
+ HSTT(550, 77, 44, 57, 26),
+ HSTT(600, 82, 46, 64, 27),
+ HSTT(650, 87, 48, 67, 28),
+ HSTT(700, 94, 52, 71, 29),
+ HSTT(750, 99, 52, 75, 31),
+ HSTT(800, 105, 55, 82, 32),
+ HSTT(850, 110, 58, 85, 32),
+ HSTT(900, 115, 58, 88, 35),
+ HSTT(950, 120, 62, 93, 36),
+ HSTT(1000, 128, 63, 99, 38),
+ HSTT(1050, 132, 65, 102, 38),
+ HSTT(1100, 138, 67, 106, 39),
+ HSTT(1150, 146, 69, 112, 42),
+ HSTT(1200, 151, 71, 117, 43),
+ HSTT(1250, 153, 74, 120, 45),
+ HSTT(1300, 160, 73, 124, 46),
+ HSTT(1350, 165, 76, 130, 47),
+ HSTT(1400, 172, 78, 134, 49),
+ HSTT(1450, 177, 80, 138, 49),
+ HSTT(1500, 183, 81, 143, 52),
+ HSTT(1550, 191, 84, 147, 52),
+ HSTT(1600, 194, 85, 152, 52),
+ HSTT(1650, 201, 86, 155, 53),
+ HSTT(1700, 208, 88, 161, 53),
+ HSTT(1750, 212, 89, 165, 53),
+ HSTT(1800, 220, 90, 171, 54),
+ HSTT(1850, 223, 92, 175, 54),
+ HSTT(1900, 231, 91, 180, 55),
+ HSTT(1950, 236, 95, 185, 56),
+ HSTT(2000, 243, 97, 190, 56),
+ HSTT(2050, 248, 99, 194, 58),
+ HSTT(2100, 252, 100, 199, 59),
+ HSTT(2150, 259, 102, 204, 61),
+ HSTT(2200, 266, 105, 210, 62),
+ HSTT(2250, 269, 109, 213, 63),
+ HSTT(2300, 272, 109, 217, 65),
+ HSTT(2350, 281, 112, 225, 66),
+ HSTT(2400, 283, 115, 226, 66),
+ HSTT(2450, 282, 115, 226, 67),
+ HSTT(2500, 281, 118, 227, 67),
+};
+
+static int
+dw_mipi_dsi_phy_get_timing(void *priv_data, unsigned int lane_mbps,
+ struct dw_mipi_dsi_dphy_timing *timing)
+{
+ struct dw_mipi_dsi_imx *dsi = priv_data;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(hstt_table); i++)
+ if (lane_mbps <= hstt_table[i].maxfreq)
+ break;
+
+ if (i == ARRAY_SIZE(hstt_table))
+ i--;
+
+ *timing = hstt_table[i].timing;
+
+ DRM_DEV_DEBUG(dsi->dev, "get phy timing for %u <= %u (lane_mbps)\n",
+ lane_mbps, hstt_table[i].maxfreq);
+
+ return 0;
+}
+
+static const struct dw_mipi_dsi_phy_ops dw_mipi_dsi_imx_phy_ops = {
+ .init = dw_mipi_dsi_imx_phy_init,
+ .power_off = dw_mipi_dsi_imx_phy_power_off,
+ .get_lane_mbps = dw_mipi_dsi_get_lane_mbps,
+ .get_timing = dw_mipi_dsi_phy_get_timing,
+};
+
+static int dw_mipi_dsi_imx_host_attach(void *priv_data,
+ struct mipi_dsi_device *device)
+{
+ struct dw_mipi_dsi_imx *dsi = priv_data;
+
+ dsi->lanes = device->lanes;
+ dsi->format = device->format;
+
+ return 0;
+}
+
+static const struct dw_mipi_dsi_host_ops dw_mipi_dsi_imx_host_ops = {
+ .attach = dw_mipi_dsi_imx_host_attach,
+};
+
+static bool
+dw_mipi_dsi_imx_hcomponents_need_fixup(struct dw_mipi_dsi_imx *dsi,
+ int bpp,
+ const struct drm_display_mode *mode)
+{
+ int htotal = mode->htotal;
+ int hsa = mode->hsync_end - mode->hsync_start;
+ int hbp = mode->htotal - mode->hsync_end;
+ int divisor = dsi->lanes * 8;
+
+ /*
+ * It appears that (hcomponent * bpp) / (8 * lanes)
+ * should be no remainder.
+ */
+ return !!((htotal * bpp) % divisor) ||
+ !!((hsa * bpp) % divisor) ||
+ !!((hbp * bpp) % divisor);
+}
+
+static int dw_mipi_dsi_imx_fixup_hcomponent(struct dw_mipi_dsi_imx *dsi,
+ int bpp, int component)
+{
+ int divisor, i;
+
+ divisor = dsi->lanes * 8;
+
+ for (i = 0; i < divisor; i++) {
+ if ((bpp * (component + i)) % divisor == 0) {
+ component += i;
+ break;
+ }
+ }
+
+ return component;
+}
+
+static void
+dw_mipi_dsi_imx_fixup_hcomponents(struct dw_mipi_dsi_imx *dsi,
+ int bpp,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adj)
+{
+ struct device *dev = dsi->dev;
+ int hfp = mode->hsync_start - mode->hdisplay;
+ int hsa = mode->hsync_end - mode->hsync_start;
+ int hbp = mode->htotal - mode->hsync_end;
+
+ hfp = dw_mipi_dsi_imx_fixup_hcomponent(dsi, bpp, hfp);
+ adj->hsync_start = adj->hdisplay + hfp;
+
+ hsa = dw_mipi_dsi_imx_fixup_hcomponent(dsi, bpp, hsa);
+ adj->hsync_end = adj->hsync_start + hsa;
+
+ hbp = dw_mipi_dsi_imx_fixup_hcomponent(dsi, bpp, hbp);
+ adj->htotal = adj->hsync_end + hbp;
+
+ DRM_DEV_DEBUG(dev, "fixing up mode:\n");
+ DRM_DEV_DEBUG(dev, "\tModeline " DRM_MODE_FMT "\n", DRM_MODE_ARG(mode));
+ DRM_DEV_DEBUG(dev, "adjusted mode:\n");
+ DRM_DEV_DEBUG(dev, "\tModeline " DRM_MODE_FMT "\n", DRM_MODE_ARG(adj));
+}
+
+static int
+dw_mipi_dsi_encoder_atomic_check(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct imx_crtc_state *s = to_imx_crtc_state(crtc_state);
+ struct dw_mipi_dsi_imx *dsi = enc_to_dsi(encoder);
+ struct device *dev = dsi->dev;
+
+ switch (dsi->format) {
+ case MIPI_DSI_FMT_RGB888:
+ s->bus_format = MEDIA_BUS_FMT_RGB888_1X24;
+ break;
+ case MIPI_DSI_FMT_RGB666:
+ s->bus_format = MEDIA_BUS_FMT_RGB666_1X18;
+ break;
+ case MIPI_DSI_FMT_RGB565:
+ s->bus_format = MEDIA_BUS_FMT_RGB565_1X16;
+ break;
+ default:
+ DRM_DEV_ERROR(dev, "unsupported DSI format: 0x%x\n",
+ dsi->format);
+ return -EINVAL;
+ }
+
+ s->bus_flags = DRM_BUS_FLAG_DE_HIGH;
+
+ /* Force mode_changed to true, when CRTC state is changed to active. */
+ if (crtc_state->active_changed && crtc_state->active)
+ crtc_state->mode_changed = true;
+
+ if (crtc_state->active) {
+ const struct drm_display_mode *mode = &crtc_state->mode;
+ struct drm_display_mode *adj = &crtc_state->adjusted_mode;
+ union phy_configure_opts phy_cfg;
+ struct phy_configure_opts_mipi_dphy *cfg = &phy_cfg.mipi_dphy;
+ enum drm_mode_status mode_status;
+ int bpp;
+
+ mode_status = __dw_mipi_dsi_imx_mode_valid(dsi, mode, &phy_cfg);
+ if (mode_status != MODE_OK)
+ return -EINVAL;
+
+ bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
+ if (bpp < 0)
+ return -EINVAL;
+
+ adj->clock = cfg->hs_clk_rate * dsi->lanes / bpp / MSEC_PER_SEC;
+ if (adj->clock != mode->clock)
+ DRM_DEV_DEBUG(dev, "adjust mode clock %dKHz->%dKHz\n",
+ mode->clock, adj->clock);
+
+ if (dw_mipi_dsi_imx_hcomponents_need_fixup(dsi, bpp, mode))
+ dw_mipi_dsi_imx_fixup_hcomponents(dsi, bpp, mode, adj);
+ }
+
+ return 0;
+}
+
+static void
+dw_mipi_dsi_encoder_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ struct dw_mipi_dsi_imx *dsi = enc_to_dsi(encoder);
+
+ pm_runtime_get_sync(dsi->dev);
+}
+
+static void dw_mipi_dsi_encoder_disable(struct drm_encoder *encoder)
+{
+ struct dw_mipi_dsi_imx *dsi = enc_to_dsi(encoder);
+
+ pm_runtime_put(dsi->dev);
+}
+
+static const struct drm_encoder_helper_funcs
+dw_mipi_dsi_encoder_helper_funcs = {
+ .atomic_check = dw_mipi_dsi_encoder_atomic_check,
+ .mode_set = dw_mipi_dsi_encoder_mode_set,
+ .disable = dw_mipi_dsi_encoder_disable,
+};
+
+static int imx_dsi_drm_create_encoder(struct dw_mipi_dsi_imx *dsi,
+ struct drm_device *drm_dev)
+{
+ struct device *dev = dsi->dev;
+ struct drm_encoder *encoder = &dsi->encoder;
+ int ret;
+
+ encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev,
+ dev->of_node);
+
+ ret = drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_DSI);
+ if (ret) {
+ DRM_DEV_ERROR(dev,
+ "failed to initialize encoder with drm: %d\n",
+ ret);
+ return ret;
+ }
+
+ drm_encoder_helper_add(encoder, &dw_mipi_dsi_encoder_helper_funcs);
+
+ return 0;
+}
+
+static int
+dw_mipi_dsi_imx_bind(struct device *dev, struct device *master, void *data)
+{
+ struct dw_mipi_dsi_imx *dsi = dev_get_drvdata(dev);
+ struct drm_device *drm_dev = data;
+ struct drm_bridge *bridge;
+ int ret;
+
+ ret = imx_dsi_drm_create_encoder(dsi, drm_dev);
+ if (ret) {
+ DRM_DEV_ERROR(dev, "failed to create drm encoder\n");
+ return ret;
+ }
+
+ bridge = of_drm_find_bridge(dev->of_node);
+ if (!bridge) {
+ DRM_DEV_ERROR(dev, "failed to find drm bridge\n");
+ return -ENODEV;
+ }
+
+ ret = drm_bridge_attach(&dsi->encoder, bridge, NULL, 0);
+ if (ret) {
+ DRM_DEV_ERROR(dev, "failed to attach bridge: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct component_ops dw_mipi_dsi_imx_ops = {
+ .bind = dw_mipi_dsi_imx_bind,
+};
+
+static int dw_mipi_dsi_imx_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct dw_mipi_dsi_imx *dsi;
+ struct resource *res;
+ int ret;
+
+ dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL);
+ if (!dsi)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ dsi->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(dsi->base)) {
+ DRM_DEV_ERROR(dev, "failed to get dsi registers\n");
+ return PTR_ERR(dsi->base);
+ }
+
+ dsi->byte_clk = devm_clk_get(dev, "byte");
+ if (IS_ERR(dsi->byte_clk)) {
+ ret = PTR_ERR(dsi->byte_clk);
+ if (ret != -EPROBE_DEFER)
+ DRM_DEV_ERROR(dev, "failed to get byte clk: %d\n", ret);
+ return ret;
+ }
+
+ dsi->phy = devm_phy_get(dev, "dphy");
+ if (IS_ERR(dsi->phy)) {
+ ret = PTR_ERR(dsi->phy);
+ if (ret != -EPROBE_DEFER)
+ DRM_DEV_ERROR(dev, "failed to get phy: %d\n", ret);
+ return ret;
+ }
+
+ dsi->dev = dev;
+ dsi->pdata.base = dsi->base;
+ dsi->pdata.max_data_lanes = 4;
+ dsi->pdata.mode_valid = dw_mipi_dsi_imx_mode_valid;
+ dsi->pdata.phy_ops = &dw_mipi_dsi_imx_phy_ops;
+ dsi->pdata.host_ops = &dw_mipi_dsi_imx_host_ops;
+ dsi->pdata.priv_data = dsi;
+ platform_set_drvdata(pdev, dsi);
+
+ dsi->dmd = dw_mipi_dsi_probe(pdev, &dsi->pdata);
+ if (IS_ERR(dsi->dmd)) {
+ ret = PTR_ERR(dsi->dmd);
+ if (ret != -EPROBE_DEFER)
+ DRM_DEV_ERROR(dev,
+ "failed to probe dw_mipi_dsi: %d\n", ret);
+ return ret;
+ }
+
+ ret = component_add(dev, &dw_mipi_dsi_imx_ops);
+ if (ret) {
+ DRM_DEV_ERROR(dev, "failed to register component: %d\n", ret);
+ dw_mipi_dsi_remove(dsi->dmd);
+ }
+
+ return ret;
+}
+
+static int dw_mipi_dsi_imx_remove(struct platform_device *pdev)
+{
+ struct dw_mipi_dsi_imx *dsi = platform_get_drvdata(pdev);
+
+ component_del(dsi->dev, &dw_mipi_dsi_imx_ops);
+
+ dw_mipi_dsi_remove(dsi->dmd);
+
+ clk_disable_unprepare(dsi->byte_clk);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int dw_mipi_dsi_imx_runtime_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct dw_mipi_dsi_imx *dsi = platform_get_drvdata(pdev);
+
+ clk_disable_unprepare(dsi->byte_clk);
+
+ return 0;
+}
+
+static int dw_mipi_dsi_imx_runtime_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct dw_mipi_dsi_imx *dsi = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = clk_prepare_enable(dsi->byte_clk);
+ if (ret)
+ DRM_DEV_ERROR(dev, "failed to enable byte_clk: %d\n", ret);
+
+ return ret;
+}
+
+static const struct dev_pm_ops dw_mipi_dsi_imx_pm_ops = {
+ SET_RUNTIME_PM_OPS(dw_mipi_dsi_imx_runtime_suspend,
+ dw_mipi_dsi_imx_runtime_resume, NULL)
+};
+#endif
+
+static const struct of_device_id dw_mipi_dsi_imx_dt_ids[] = {
+ { .compatible = "fsl,imx93-mipi-dsi", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, dw_mipi_dsi_imx_dt_ids);
+
+struct platform_driver dw_mipi_dsi_imx_driver = {
+ .probe = dw_mipi_dsi_imx_probe,
+ .remove = dw_mipi_dsi_imx_remove,
+ .driver = {
+ .of_match_table = dw_mipi_dsi_imx_dt_ids,
+ .name = "dw-mipi-dsi-imx",
+ .pm = &dw_mipi_dsi_imx_pm_ops,
+ },
+};
+
+module_platform_driver(dw_mipi_dsi_imx_driver);
+
+MODULE_DESCRIPTION("Freescale i.MX93 Synopsys DesignWare MIPI DSI Host Controller driver");
+MODULE_AUTHOR("NXP Semiconductor");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:dw-mipi-dsi-imx");
diff --git a/drivers/gpu/drm/imx/imx-drm-core.c b/drivers/gpu/drm/imx/imx-drm-core.c
index cb685fe2039b..25c9ab12f59c 100644
--- a/drivers/gpu/drm/imx/imx-drm-core.c
+++ b/drivers/gpu/drm/imx/imx-drm-core.c
@@ -12,6 +12,8 @@
#include <linux/platform_device.h>
#include <video/imx-ipu-v3.h>
+#include <video/imx-lcdif.h>
+#include <video/imx-lcdifv3.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
@@ -25,11 +27,10 @@
#include <drm/drm_plane_helper.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_vblank.h>
+#include <video/dpu.h>
+#include "dpu/dpu-blit.h"
#include "imx-drm.h"
-#include "ipuv3-plane.h"
-
-#define MAX_CRTC 4
static int legacyfb_depth = 16;
module_param(legacyfb_depth, int, 0444);
@@ -43,81 +44,6 @@ void imx_drm_connector_destroy(struct drm_connector *connector)
}
EXPORT_SYMBOL_GPL(imx_drm_connector_destroy);
-static int imx_drm_atomic_check(struct drm_device *dev,
- struct drm_atomic_state *state)
-{
- int ret;
-
- ret = drm_atomic_helper_check(dev, state);
- if (ret)
- return ret;
-
- /*
- * Check modeset again in case crtc_state->mode_changed is
- * updated in plane's ->atomic_check callback.
- */
- ret = drm_atomic_helper_check_modeset(dev, state);
- if (ret)
- return ret;
-
- /* Assign PRG/PRE channels and check if all constrains are satisfied. */
- ret = ipu_planes_assign_pre(dev, state);
- if (ret)
- return ret;
-
- return ret;
-}
-
-static const struct drm_mode_config_funcs imx_drm_mode_config_funcs = {
- .fb_create = drm_gem_fb_create,
- .atomic_check = imx_drm_atomic_check,
- .atomic_commit = drm_atomic_helper_commit,
-};
-
-static void imx_drm_atomic_commit_tail(struct drm_atomic_state *state)
-{
- struct drm_device *dev = state->dev;
- struct drm_plane *plane;
- struct drm_plane_state *old_plane_state, *new_plane_state;
- bool plane_disabling = false;
- int i;
-
- drm_atomic_helper_commit_modeset_disables(dev, state);
-
- drm_atomic_helper_commit_planes(dev, state,
- DRM_PLANE_COMMIT_ACTIVE_ONLY |
- DRM_PLANE_COMMIT_NO_DISABLE_AFTER_MODESET);
-
- drm_atomic_helper_commit_modeset_enables(dev, state);
-
- for_each_oldnew_plane_in_state(state, plane, old_plane_state, new_plane_state, i) {
- if (drm_atomic_plane_disabling(old_plane_state, new_plane_state))
- plane_disabling = true;
- }
-
- /*
- * The flip done wait is only strictly required by imx-drm if a deferred
- * plane disable is in-flight. As the core requires blocking commits
- * to wait for the flip it is done here unconditionally. This keeps the
- * workitem around a bit longer than required for the majority of
- * non-blocking commits, but we accept that for the sake of simplicity.
- */
- drm_atomic_helper_wait_for_flip_done(dev, state);
-
- if (plane_disabling) {
- for_each_old_plane_in_state(state, plane, old_plane_state, i)
- ipu_plane_disable_deferred(plane);
-
- }
-
- drm_atomic_helper_commit_hw_done(state);
-}
-
-static const struct drm_mode_config_helper_funcs imx_drm_mode_config_helpers = {
- .atomic_commit_tail = imx_drm_atomic_commit_tail,
-};
-
-
int imx_drm_encoder_parse_of(struct drm_device *drm,
struct drm_encoder *encoder, struct device_node *np)
{
@@ -145,9 +71,9 @@ static const struct drm_ioctl_desc imx_drm_ioctls[] = {
/* none so far */
};
-static int imx_drm_dumb_create(struct drm_file *file_priv,
- struct drm_device *drm,
- struct drm_mode_create_dumb *args)
+static int imx_drm_ipu_dumb_create(struct drm_file *file_priv,
+ struct drm_device *drm,
+ struct drm_mode_create_dumb *args)
{
u32 width = args->width;
int ret;
@@ -162,9 +88,9 @@ static int imx_drm_dumb_create(struct drm_file *file_priv,
return ret;
}
-static const struct drm_driver imx_drm_driver = {
+static struct drm_driver imx_drm_driver = {
.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
- DRM_GEM_CMA_DRIVER_OPS_WITH_DUMB_CREATE(imx_drm_dumb_create),
+ DRM_GEM_CMA_DRIVER_OPS,
.ioctls = imx_drm_ioctls,
.num_ioctls = ARRAY_SIZE(imx_drm_ioctls),
.fops = &imx_drm_driver_fops,
@@ -176,6 +102,35 @@ static const struct drm_driver imx_drm_driver = {
.patchlevel = 0,
};
+static const struct drm_driver imx_drm_ipu_driver = {
+ .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
+ DRM_GEM_CMA_DRIVER_OPS_WITH_DUMB_CREATE(imx_drm_ipu_dumb_create),
+ .ioctls = imx_drm_ioctls,
+ .num_ioctls = ARRAY_SIZE(imx_drm_ioctls),
+ .fops = &imx_drm_driver_fops,
+ .name = "imx-drm",
+ .desc = "i.MX DRM graphics",
+ .date = "20120507",
+ .major = 1,
+ .minor = 0,
+ .patchlevel = 0,
+};
+
+static const struct drm_driver imx_drm_dpu_driver = {
+ .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC |
+ DRIVER_RENDER,
+ DRM_GEM_CMA_DRIVER_OPS,
+ .ioctls = imx_drm_dpu_ioctls,
+ .num_ioctls = ARRAY_SIZE(imx_drm_dpu_ioctls),
+ .fops = &imx_drm_driver_fops,
+ .name = "imx-drm",
+ .desc = "i.MX DRM graphics",
+ .date = "20120507",
+ .major = 1,
+ .minor = 0,
+ .patchlevel = 0,
+};
+
static int compare_of(struct device *dev, void *data)
{
struct device_node *np = data;
@@ -185,6 +140,30 @@ static int compare_of(struct device *dev, void *data)
struct ipu_client_platformdata *pdata = dev->platform_data;
return pdata->of_node == np;
+ } else if (strcmp(dev->driver->name, "imx-dpu-crtc") == 0) {
+ struct dpu_client_platformdata *pdata = dev->platform_data;
+
+ return pdata->of_node == np;
+ } else if (strcmp(dev->driver->name, "imx-lcdif-crtc") == 0 ||
+ strcmp(dev->driver->name, "imx-lcdifv3-crtc") == 0) {
+ struct lcdif_client_platformdata *pdata = dev->platform_data;
+#if IS_ENABLED(CONFIG_DRM_FBDEV_EMULATION)
+ /* set legacyfb_depth to be 32 for lcdif, since
+ * default format of the connectors attached to
+ * lcdif is usually RGB888
+ */
+ if (pdata->of_node == np)
+ legacyfb_depth = 32;
+#endif
+
+ return pdata->of_node == np;
+ }
+
+ /* This is a special case for dpu bliteng. */
+ if (strcmp(dev->driver->name, "imx-drm-dpu-bliteng") == 0) {
+ struct dpu_client_platformdata *pdata = dev->platform_data;
+
+ return pdata->of_node == np;
}
/* Special case for LDB, one device for two channels */
@@ -196,12 +175,122 @@ static int compare_of(struct device *dev, void *data)
return dev->of_node == np;
}
+static const char *const imx_drm_ipu_comp_parents[] = {
+ "fsl,imx51-ipu",
+ "fsl,imx53-ipu",
+ "fsl,imx6q-ipu",
+ "fsl,imx6qp-ipu",
+};
+
+static const char *const imx_drm_dpu_comp_parents[] = {
+ "fsl,imx8qm-dpu",
+ "fsl,imx8qxp-dpu",
+};
+
+static bool imx_drm_parent_is_compatible(struct device *dev,
+ const char *const comp_parents[],
+ int comp_parents_size)
+{
+ struct device_node *port, *parent;
+ bool ret = false;
+ int i;
+
+ port = of_parse_phandle(dev->of_node, "ports", 0);
+ if (!port)
+ return ret;
+
+ parent = of_get_parent(port);
+
+ for (i = 0; i < comp_parents_size; i++) {
+ if (of_device_is_compatible(parent, comp_parents[i])) {
+ ret = true;
+ break;
+ }
+ }
+
+ of_node_put(parent);
+
+ of_node_put(port);
+
+ return ret;
+}
+
+static inline bool has_ipu(struct device *dev)
+{
+ return imx_drm_parent_is_compatible(dev, imx_drm_ipu_comp_parents,
+ ARRAY_SIZE(imx_drm_ipu_comp_parents));
+}
+
+static inline bool has_dpu(struct device *dev)
+{
+ return imx_drm_parent_is_compatible(dev, imx_drm_dpu_comp_parents,
+ ARRAY_SIZE(imx_drm_dpu_comp_parents));
+}
+
+static void add_dpu_bliteng_components(struct device *dev,
+ struct component_match **matchptr)
+{
+ /*
+ * As there may be two dpu bliteng device,
+ * so need add something in compare data to distinguish.
+ * Use its parent dpu's of_node as the data here.
+ */
+ struct device_node *port, *parent;
+ /* assume max dpu number is 8 */
+ struct device_node *dpu[8];
+ int num_dpu = 0;
+ int i, j;
+ bool found = false;
+
+ for (i = 0; ; i++) {
+ port = of_parse_phandle(dev->of_node, "ports", i);
+ if (!port)
+ break;
+
+ parent = of_get_parent(port);
+
+ for (j = 0; j < num_dpu; j++) {
+ if (dpu[j] == parent) {
+ found = true;
+ break;
+ }
+ }
+
+ if (found) {
+ found = false;
+ } else {
+ if (num_dpu >= ARRAY_SIZE(dpu)) {
+ dev_err(dev, "The number of found dpu is greater than max [%ld].\n",
+ ARRAY_SIZE(dpu));
+ of_node_put(parent);
+ of_node_put(port);
+ break;
+ }
+
+ dpu[num_dpu] = parent;
+ num_dpu++;
+
+ component_match_add(dev, matchptr, compare_of, parent);
+ }
+
+ of_node_put(parent);
+ of_node_put(port);
+ }
+}
+
static int imx_drm_bind(struct device *dev)
{
struct drm_device *drm;
int ret;
- drm = drm_dev_alloc(&imx_drm_driver, dev);
+ if (has_ipu(dev)) {
+ drm = drm_dev_alloc(&imx_drm_ipu_driver, dev);
+ } else if (has_dpu(dev)) {
+ drm = drm_dev_alloc(&imx_drm_dpu_driver, dev);
+ } else {
+ drm = drm_dev_alloc(&imx_drm_driver, dev);
+ }
+
if (IS_ERR(drm))
return PTR_ERR(drm);
@@ -214,8 +303,6 @@ static int imx_drm_bind(struct device *dev)
drm->mode_config.min_height = 1;
drm->mode_config.max_width = 4096;
drm->mode_config.max_height = 4096;
- drm->mode_config.funcs = &imx_drm_mode_config_funcs;
- drm->mode_config.helper_private = &imx_drm_mode_config_helpers;
drm->mode_config.normalize_zpos = true;
ret = drmm_mode_config_init(drm);
@@ -226,8 +313,6 @@ static int imx_drm_bind(struct device *dev)
if (ret)
goto err_kms;
- dev_set_drvdata(dev, drm);
-
/* Now try and bind all our sub-components */
ret = component_bind_all(dev, drm);
if (ret)
@@ -253,6 +338,8 @@ static int imx_drm_bind(struct device *dev)
drm_fbdev_generic_setup(drm, legacyfb_depth);
+ dev_set_drvdata(dev, drm);
+
return 0;
err_poll_fini:
@@ -286,7 +373,14 @@ static const struct component_master_ops imx_drm_ops = {
static int imx_drm_platform_probe(struct platform_device *pdev)
{
- int ret = drm_of_component_probe(&pdev->dev, compare_of, &imx_drm_ops);
+ struct component_match *match = NULL;
+ int ret;
+
+ if (has_dpu(&pdev->dev))
+ add_dpu_bliteng_components(&pdev->dev, &match);
+
+ ret = drm_of_component_probe_with_match(&pdev->dev, match, compare_of,
+ &imx_drm_ops);
if (!ret)
ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32));
@@ -333,23 +427,7 @@ static struct platform_driver imx_drm_pdrv = {
.of_match_table = imx_drm_dt_ids,
},
};
-
-static struct platform_driver * const drivers[] = {
- &imx_drm_pdrv,
- &ipu_drm_driver,
-};
-
-static int __init imx_drm_init(void)
-{
- return platform_register_drivers(drivers, ARRAY_SIZE(drivers));
-}
-module_init(imx_drm_init);
-
-static void __exit imx_drm_exit(void)
-{
- platform_unregister_drivers(drivers, ARRAY_SIZE(drivers));
-}
-module_exit(imx_drm_exit);
+module_platform_driver(imx_drm_pdrv);
MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
MODULE_DESCRIPTION("i.MX drm driver core");
diff --git a/drivers/gpu/drm/imx/imx-drm.h b/drivers/gpu/drm/imx/imx-drm.h
index c3e1a3f14d30..2ab936f6faa0 100644
--- a/drivers/gpu/drm/imx/imx-drm.h
+++ b/drivers/gpu/drm/imx/imx-drm.h
@@ -2,6 +2,8 @@
#ifndef _IMX_DRM_H_
#define _IMX_DRM_H_
+#define MAX_CRTC 4
+
struct device_node;
struct drm_crtc;
struct drm_connector;
@@ -28,8 +30,6 @@ int imx_drm_init_drm(struct platform_device *pdev,
int preferred_bpp);
int imx_drm_exit_drm(void);
-extern struct platform_driver ipu_drm_driver;
-
void imx_drm_mode_config_init(struct drm_device *drm);
struct drm_gem_cma_object *imx_drm_fb_get_obj(struct drm_framebuffer *fb);
@@ -39,7 +39,4 @@ int imx_drm_encoder_parse_of(struct drm_device *drm,
void imx_drm_connector_destroy(struct drm_connector *connector);
-int ipu_planes_assign_pre(struct drm_device *dev,
- struct drm_atomic_state *state);
-
#endif /* _IMX_DRM_H_ */
diff --git a/drivers/gpu/drm/imx/imx8mp-hdmi-pavi.c b/drivers/gpu/drm/imx/imx8mp-hdmi-pavi.c
new file mode 100644
index 000000000000..d93694d80334
--- /dev/null
+++ b/drivers/gpu/drm/imx/imx8mp-hdmi-pavi.c
@@ -0,0 +1,220 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+/*
+ * Copyright 2020 NXP
+ *
+ * Programe Video/Audio Interface between LCDIF and HDMI Ctrl in HDMIMIX
+ *
+ */
+
+#include <linux/io.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <drm/drm_fourcc.h>
+
+#include "imx8mp-hdmi-pavi.h"
+
+#define DRIVER_NAME "imx-hdmi-pavi"
+
+#define HTX_PVI_CTRL 0x0
+#define HTX_PVI_IRQ_MASK 0x04
+#define HTX_TMG_GEN_DISP_LRC 0x10
+#define HTX_TMG_GEN_DE_ULC 0x14
+#define HTX_TMG_GEN_DE_LRC 0x18
+#define HTX_TMG_GEN_HSYNC 0x1c
+#define HTX_TMG_GEN_VSYNC 0x20
+#define HTX_TMG_GEN_IRQ0 0x24
+#define HTX_TMG_GEN_IRQ1 0x28
+#define HTX_TMG_GEN_IRQ2 0x2c
+#define HTX_TMG_GEN_IRQ3 0x30
+#define HTX_TMG_GEN_CFG 0x40
+
+#define HTX_PAI_CTRL 0x800
+#define HTX_PAI_CTRL_EXT 0x804
+#define HTX_PAI_FIELD_CTRL 0x808
+
+#define HTX_PAI_CTRL_ENABLE 1
+
+
+static struct imx8mp_hdmi_pavi *gpavi;
+
+/* PAI APIs */
+void imx8mp_hdmi_pai_enable(int channel, int width, int rate, int non_pcm)
+{
+ /* PAI set */
+ writel((0x3030000 | ((channel-1) << 8)),
+ gpavi->base + HTX_PAI_CTRL_EXT);
+
+ /* hbr */
+ if (non_pcm && width == 32 && channel == 8 && rate == 192000)
+ writel(0x004e77df, gpavi->base + HTX_PAI_FIELD_CTRL);
+ else if (width == 32)
+ writel(0x1c8c675b, gpavi->base + HTX_PAI_FIELD_CTRL);
+ else
+ writel(0x1c0c675b, gpavi->base + HTX_PAI_FIELD_CTRL);
+
+ /* PAI start running */
+ writel(HTX_PAI_CTRL_ENABLE, gpavi->base + HTX_PAI_CTRL);
+}
+EXPORT_SYMBOL(imx8mp_hdmi_pai_enable);
+
+void imx8mp_hdmi_pai_disable(void)
+{
+ /* stop PAI */
+ writel(0, gpavi->base + HTX_PAI_CTRL);
+}
+EXPORT_SYMBOL(imx8mp_hdmi_pai_disable);
+
+/* PVI APIs */
+void imx8mp_hdmi_pvi_enable(const struct drm_display_mode *mode)
+{
+ writel(0x00000003, gpavi->base + HTX_PVI_IRQ_MASK);
+ writel(0x08970464, gpavi->base + HTX_TMG_GEN_DISP_LRC);
+ writel(0x00bf0029, gpavi->base + HTX_TMG_GEN_DE_ULC);
+ writel(0x083f0460, gpavi->base + HTX_TMG_GEN_DE_LRC);
+ writel(0x0897002b, gpavi->base + HTX_TMG_GEN_HSYNC);
+ writel(0x04640004, gpavi->base + HTX_TMG_GEN_VSYNC);
+ writel(0x000100ff, gpavi->base + HTX_TMG_GEN_IRQ0);
+ writel(0x000100f0, gpavi->base + HTX_TMG_GEN_IRQ1);
+ writel(0x00010315, gpavi->base + HTX_TMG_GEN_IRQ2);
+ writel(0x00010207, gpavi->base + HTX_TMG_GEN_IRQ3);
+ writel(0x84640000, gpavi->base + HTX_TMG_GEN_CFG);
+
+ /* DE/VSYN/HSYNC pol */
+ if ((mode->flags & DRM_MODE_FLAG_PVSYNC) &&
+ (mode->flags & DRM_MODE_FLAG_PHSYNC)) {
+ writel(0x00377004, gpavi->base + HTX_PVI_CTRL);
+ writel(0x00377005, gpavi->base + HTX_PVI_CTRL);
+ } else {
+ writel(0x00311004, gpavi->base + HTX_PVI_CTRL);
+ writel(0x00311005, gpavi->base + HTX_PVI_CTRL);
+ }
+}
+EXPORT_SYMBOL(imx8mp_hdmi_pvi_enable);
+
+void imx8mp_hdmi_pvi_disable(void)
+{
+ /* Stop PVI */
+ writel(0x0, gpavi->base + HTX_PVI_CTRL);
+}
+EXPORT_SYMBOL(imx8mp_hdmi_pvi_disable);
+
+void imx8mp_hdmi_pavi_powerup(void)
+{
+ clk_prepare_enable(gpavi->clk_pvi);
+ clk_prepare_enable(gpavi->clk_pai);
+
+ /* deassert pai reset */
+ if (!gpavi->reset_pai)
+ reset_control_deassert(gpavi->reset_pai);
+
+ /* deassert pvi reset */
+ if (!gpavi->reset_pvi)
+ reset_control_deassert(gpavi->reset_pvi);
+}
+EXPORT_SYMBOL(imx8mp_hdmi_pavi_powerup);
+
+void imx8mp_hdmi_pavi_powerdown(void)
+{
+ /* set pvi reset */
+ if (!gpavi->reset_pvi)
+ reset_control_assert(gpavi->reset_pvi);
+
+ /* set pai reset */
+ if (!gpavi->reset_pai)
+ reset_control_assert(gpavi->reset_pai);
+
+ clk_disable_unprepare(gpavi->clk_pai);
+ clk_disable_unprepare(gpavi->clk_pvi);
+}
+EXPORT_SYMBOL(imx8mp_hdmi_pavi_powerdown);
+
+struct imx8mp_hdmi_pavi *imx8mp_hdmi_pavi_init(void)
+{
+ return gpavi;
+}
+EXPORT_SYMBOL(imx8mp_hdmi_pavi_init);
+
+static int imx8mp_hdmi_pavi_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct imx8mp_hdmi_pavi *pavi;
+ struct resource *res;
+
+ dev_dbg(dev, "%s: probe begin\n", __func__);
+
+ pavi = devm_kzalloc(dev, sizeof(*pavi), GFP_KERNEL);
+ if (!pavi) {
+ dev_err(dev, "Can't allocate 'imx8mp pavi' structure\n");
+ return -ENOMEM;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENODEV;
+
+ pavi->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(pavi->base))
+ return PTR_ERR(pavi->base);
+
+ pavi->clk_pvi = devm_clk_get(dev, "pvi_clk");
+ if (IS_ERR(pavi->clk_pvi)) {
+ dev_err(dev, "No pvi clock get\n");
+ return -EPROBE_DEFER;
+ }
+
+ pavi->clk_pai = devm_clk_get(dev, "pai_clk");
+ if (IS_ERR(pavi->clk_pai)) {
+ dev_err(dev, "No pai clock get\n");
+ return -EPROBE_DEFER;
+ }
+
+ pavi->reset_pai = devm_reset_control_get(dev, "pai_rst");
+ if (IS_ERR(pavi->reset_pai)) {
+ dev_err(pavi->dev, "No PAI reset\n");
+ return -EPROBE_DEFER;
+ }
+
+ pavi->reset_pvi = devm_reset_control_get(dev, "pvi_rst");
+ if (IS_ERR(pavi->reset_pvi)) {
+ dev_err(pavi->dev, "No PVI reset\n");
+ return -EPROBE_DEFER;
+ }
+
+ platform_set_drvdata(pdev, pavi);
+
+ gpavi = pavi;
+
+ dev_dbg(dev, "%s: probe success\n", __func__);
+ return 0;
+}
+
+static int imx8mp_hdmi_pavi_remove(struct platform_device *pdev)
+{
+ gpavi = NULL;
+ return 0;
+}
+
+static const struct of_device_id imx8mp_hdmi_pavi_dt_ids[] = {
+ { .compatible = "fsl,imx8mp-hdmi-pavi", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx8mp_hdmi_pavi_dt_ids);
+
+struct platform_driver imx8mp_hdmi_pavi_driver = {
+ .probe = imx8mp_hdmi_pavi_probe,
+ .remove = imx8mp_hdmi_pavi_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = imx8mp_hdmi_pavi_dt_ids,
+ },
+};
+
+module_platform_driver(imx8mp_hdmi_pavi_driver);
+
+MODULE_DESCRIPTION("NXP i.MX8MP HDMI PAI/PVI Mix driver");
+MODULE_AUTHOR("Sandor Yu <Sandor.yu@nxp.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/imx/imx8mp-hdmi-pavi.h b/drivers/gpu/drm/imx/imx8mp-hdmi-pavi.h
new file mode 100644
index 000000000000..ef90bf46cb97
--- /dev/null
+++ b/drivers/gpu/drm/imx/imx8mp-hdmi-pavi.h
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+/*
+ * Copyright 2020 NXP
+ *
+ * PAI/PVI Head file
+ *
+ */
+#ifndef _IMX8MP_HDMI_AV_CTL_H_
+#define _IMX8MP_HDMI_AV_CTL_H_
+
+#include <linux/clk.h>
+#include <linux/reset.h>
+#include <drm/drm_modes.h>
+
+struct imx8mp_hdmi_pavi {
+ struct device *dev;
+
+ void __iomem *base;
+ atomic_t rpm_suspended;
+
+ struct clk *clk_pai;
+ struct clk *clk_pvi;
+ struct reset_control *reset_pai;
+ struct reset_control *reset_pvi;
+};
+
+void imx8mp_hdmi_pai_enable(int channel, int width, int rate, int non_pcm);
+void imx8mp_hdmi_pai_disable(void);
+
+void imx8mp_hdmi_pvi_enable(const struct drm_display_mode *mode);
+void imx8mp_hdmi_pvi_disable(void);
+
+void imx8mp_hdmi_pavi_powerup(void);
+void imx8mp_hdmi_pavi_powerdown(void);
+
+struct imx8mp_hdmi_pavi *imx8mp_hdmi_pavi_init(void);
+
+#endif /* _IMX8MP_HDMI_PAVI_H_ */
diff --git a/drivers/gpu/drm/imx/imx8mp-ldb.c b/drivers/gpu/drm/imx/imx8mp-ldb.c
new file mode 100644
index 000000000000..be969f453587
--- /dev/null
+++ b/drivers/gpu/drm/imx/imx8mp-ldb.c
@@ -0,0 +1,466 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2020,2022 NXP
+ */
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/phy/phy.h>
+
+#include <drm/bridge/fsl_imx_ldb.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+
+#include "imx-drm.h"
+
+#define DRIVER_NAME "imx8mp-ldb"
+
+#define LDB_CH0_MODE_EN_TO_DI0 (1 << 0)
+#define LDB_CH0_MODE_EN_TO_DI1 (3 << 0)
+#define LDB_CH0_MODE_EN_MASK (3 << 0)
+#define LDB_CH1_MODE_EN_TO_DI0 (1 << 2)
+#define LDB_CH1_MODE_EN_TO_DI1 (3 << 2)
+#define LDB_CH1_MODE_EN_MASK (3 << 2)
+#define LDB_REG_CH0_FIFO_RESET (1 << 11)
+#define LDB_REG_CH1_FIFO_RESET (1 << 12)
+#define LDB_REG_ASYNC_FIFO_EN (1 << 24)
+#define LDB_FIFO_THRESHOLD (4 << 25)
+
+struct imx8mp_ldb;
+
+struct imx8mp_ldb_channel {
+ struct ldb_channel base;
+ struct imx8mp_ldb *imx8mp_ldb;
+
+ struct drm_connector connector;
+ struct drm_encoder encoder;
+
+ struct phy *phy;
+ bool phy_is_on;
+
+ u32 bus_flags;
+};
+
+static inline struct imx8mp_ldb_channel *
+con_to_imx8mp_ldb_ch(struct drm_connector *c)
+{
+ return container_of(c, struct imx8mp_ldb_channel, connector);
+}
+
+static inline struct imx8mp_ldb_channel *
+enc_to_imx8mp_ldb_ch(struct drm_encoder *e)
+{
+ return container_of(e, struct imx8mp_ldb_channel, encoder);
+}
+
+struct imx8mp_ldb {
+ struct ldb base;
+ struct imx8mp_ldb_channel channel[LDB_CH_NUM];
+ struct clk *clk_root;
+};
+
+static struct drm_encoder *imx8mp_ldb_connector_best_encoder(
+ struct drm_connector *connector)
+{
+ struct imx8mp_ldb_channel *imx8mp_ldb_ch =
+ con_to_imx8mp_ldb_ch(connector);
+
+ return &imx8mp_ldb_ch->encoder;
+}
+
+static void imx8mp_ldb_encoder_enable(struct drm_encoder *encoder)
+{
+ struct imx8mp_ldb_channel *imx8mp_ldb_ch =
+ enc_to_imx8mp_ldb_ch(encoder);
+ struct imx8mp_ldb *imx8mp_ldb = imx8mp_ldb_ch->imx8mp_ldb;
+ struct ldb *ldb = &imx8mp_ldb->base;
+
+ clk_prepare_enable(imx8mp_ldb->clk_root);
+
+ if (imx8mp_ldb_ch == &imx8mp_ldb->channel[0] || ldb->dual) {
+ ldb->ldb_ctrl &= ~LDB_CH0_MODE_EN_MASK;
+ ldb->ldb_ctrl |= LDB_CH0_MODE_EN_TO_DI0;
+ }
+ if (imx8mp_ldb_ch == &imx8mp_ldb->channel[1] || ldb->dual) {
+ ldb->ldb_ctrl &= ~LDB_CH1_MODE_EN_MASK;
+ ldb->ldb_ctrl |= ldb->dual ?
+ LDB_CH1_MODE_EN_TO_DI0 : LDB_CH1_MODE_EN_TO_DI1;
+ }
+
+ if (ldb->dual) {
+ phy_power_on(imx8mp_ldb->channel[0].phy);
+ phy_power_on(imx8mp_ldb->channel[1].phy);
+
+ imx8mp_ldb->channel[0].phy_is_on = true;
+ imx8mp_ldb->channel[1].phy_is_on = true;
+ } else {
+ phy_power_on(imx8mp_ldb_ch->phy);
+
+ imx8mp_ldb_ch->phy_is_on = true;
+ }
+}
+
+static void
+imx8mp_ldb_encoder_atomic_mode_set(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *connector_state)
+{
+ struct imx8mp_ldb_channel *imx8mp_ldb_ch =
+ enc_to_imx8mp_ldb_ch(encoder);
+ struct imx8mp_ldb *imx8mp_ldb = imx8mp_ldb_ch->imx8mp_ldb;
+ struct ldb_channel *ldb_ch = &imx8mp_ldb_ch->base;
+ struct ldb *ldb = &imx8mp_ldb->base;
+ struct drm_display_mode *mode = &crtc_state->adjusted_mode;
+ unsigned long serial_clk;
+
+ if (mode->clock > 160000) {
+ dev_warn(ldb->dev,
+ "%s: mode exceeds 160 MHz pixel clock\n", __func__);
+ }
+ if (mode->clock > 80000 && !ldb->dual) {
+ dev_warn(ldb->dev,
+ "%s: mode exceeds 80 MHz pixel clock\n", __func__);
+ }
+
+ serial_clk = mode->clock * (ldb->dual ? 3500UL : 7000UL);
+ clk_set_rate(imx8mp_ldb->clk_root, serial_clk);
+
+ if (!ldb_ch->bus_format) {
+ struct drm_connector *connector = connector_state->connector;
+ struct drm_display_info *di = &connector->display_info;
+
+ if (di->num_bus_formats)
+ ldb_ch->bus_format = di->bus_formats[0];
+ }
+}
+
+static void imx8mp_ldb_encoder_disable(struct drm_encoder *encoder)
+{
+ struct imx8mp_ldb_channel *imx8mp_ldb_ch =
+ enc_to_imx8mp_ldb_ch(encoder);
+ struct imx8mp_ldb *imx8mp_ldb = imx8mp_ldb_ch->imx8mp_ldb;
+ struct ldb *ldb = &imx8mp_ldb->base;
+
+ if (ldb->dual) {
+ phy_power_off(imx8mp_ldb->channel[0].phy);
+ phy_power_off(imx8mp_ldb->channel[1].phy);
+
+ imx8mp_ldb->channel[0].phy_is_on = false;
+ imx8mp_ldb->channel[1].phy_is_on = false;
+ } else {
+ phy_power_off(imx8mp_ldb_ch->phy);
+
+ imx8mp_ldb_ch->phy_is_on = false;
+ }
+
+ clk_disable_unprepare(imx8mp_ldb->clk_root);
+}
+
+static int
+imx8mp_ldb_encoder_atomic_check(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc_state);
+ struct imx8mp_ldb_channel *imx8mp_ldb_ch =
+ enc_to_imx8mp_ldb_ch(encoder);
+ struct ldb_channel *ldb_ch = &imx8mp_ldb_ch->base;
+ struct imx8mp_ldb *imx8mp_ldb = imx8mp_ldb_ch->imx8mp_ldb;
+ struct ldb *ldb = &imx8mp_ldb->base;
+ struct drm_display_info *di = &conn_state->connector->display_info;
+ struct drm_display_mode *mode = &crtc_state->adjusted_mode;
+ u32 bus_format = ldb_ch->bus_format;
+
+ /* Bus format description in DT overrides connector display info. */
+ if (!bus_format && di->num_bus_formats) {
+ bus_format = di->bus_formats[0];
+ imx_crtc_state->bus_flags = di->bus_flags;
+ } else {
+ bus_format = ldb_ch->bus_format;
+ imx_crtc_state->bus_flags = imx8mp_ldb_ch->bus_flags;
+ }
+ switch (bus_format) {
+ case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
+ imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB666_1X18;
+ break;
+ case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
+ case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
+ imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB888_1X24;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /*
+ * Due to limited video PLL frequency points on i.MX8mp,
+ * we do mode fixup here in case any mode is unsupported.
+ */
+ if (ldb->dual)
+ mode->clock = mode->clock > 100000 ? 148500 : 74250;
+ else
+ mode->clock = 74250;
+
+ return 0;
+}
+
+static enum drm_mode_status
+imx8mp_ldb_encoder_mode_valid(struct drm_encoder *encoder,
+ const struct drm_display_mode *mode)
+{
+ struct imx8mp_ldb_channel *imx8mp_ldb_ch =
+ enc_to_imx8mp_ldb_ch(encoder);
+ struct ldb_channel *ldb_ch = &imx8mp_ldb_ch->base;
+ struct imx8mp_ldb *imx8mp_ldb = imx8mp_ldb_ch->imx8mp_ldb;
+ struct ldb *ldb = &imx8mp_ldb->base;
+
+ /* it should be okay with a panel */
+ if (ldb_ch->panel)
+ return MODE_OK;
+
+ /*
+ * Due to limited video PLL frequency points on i.MX8mp,
+ * we do mode valid check here.
+ */
+ if (ldb->dual && mode->clock != 74250 && mode->clock != 148500)
+ return MODE_NOCLOCK;
+
+ if (!ldb->dual && mode->clock != 74250)
+ return MODE_NOCLOCK;
+
+ return MODE_OK;
+}
+
+static const struct drm_connector_funcs imx8mp_ldb_connector_funcs = {
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = imx_drm_connector_destroy,
+ .reset = drm_atomic_helper_connector_reset,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static const struct drm_connector_helper_funcs
+imx8mp_ldb_connector_helper_funcs = {
+ .best_encoder = imx8mp_ldb_connector_best_encoder,
+};
+
+static const struct drm_encoder_helper_funcs imx8mp_ldb_encoder_helper_funcs = {
+ .atomic_mode_set = imx8mp_ldb_encoder_atomic_mode_set,
+ .enable = imx8mp_ldb_encoder_enable,
+ .disable = imx8mp_ldb_encoder_disable,
+ .atomic_check = imx8mp_ldb_encoder_atomic_check,
+ .mode_valid = imx8mp_ldb_encoder_mode_valid,
+};
+
+static const struct of_device_id imx8mp_ldb_dt_ids[] = {
+ { .compatible = "fsl,imx8mp-ldb", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, imx8mp_ldb_dt_ids);
+
+static int
+imx8mp_ldb_bind(struct device *dev, struct device *master, void *data)
+{
+ struct drm_device *drm = data;
+ struct device_node *np = dev->of_node;
+ struct device_node *child;
+ struct imx8mp_ldb *imx8mp_ldb = dev_get_drvdata(dev);
+ struct ldb *ldb;
+ struct ldb_channel *ldb_ch;
+ struct drm_encoder *encoder[LDB_CH_NUM];
+ int ret;
+ int i;
+
+ ldb = &imx8mp_ldb->base;
+ ldb->dev = dev;
+ ldb->ctrl_reg = 0x5c,
+ ldb->output_port = 1;
+
+ for (i = 0; i < LDB_CH_NUM; i++) {
+ imx8mp_ldb->channel[i].imx8mp_ldb = imx8mp_ldb;
+ ldb->channel[i] = &imx8mp_ldb->channel[i].base;
+ }
+
+ imx8mp_ldb->clk_root = devm_clk_get(dev, "ldb");
+ if (IS_ERR(imx8mp_ldb->clk_root))
+ return PTR_ERR(imx8mp_ldb->clk_root);
+
+ for_each_child_of_node(np, child) {
+ ret = of_property_read_u32(child, "reg", &i);
+ if (ret || i < 0 || i > 1)
+ return -EINVAL;
+
+ if (!of_device_is_available(child))
+ continue;
+
+ encoder[i] = &imx8mp_ldb->channel[i].encoder;
+
+ drm_encoder_helper_add(encoder[i],
+ &imx8mp_ldb_encoder_helper_funcs);
+ drm_simple_encoder_init(drm, encoder[i], DRM_MODE_ENCODER_LVDS);
+ }
+
+ pm_runtime_enable(dev);
+
+ ret = ldb_bind(ldb, encoder);
+ if (ret)
+ goto disable_pm_runtime;
+
+ for_each_child_of_node(np, child) {
+ struct imx8mp_ldb_channel *imx8mp_ldb_ch;
+ bool auxiliary_ch = false;
+
+ ret = of_property_read_u32(child, "reg", &i);
+ if (ret || i < 0 || i > 1) {
+ ret = -EINVAL;
+ goto free_child;
+ }
+
+ if (ldb->dual && i > 0) {
+ auxiliary_ch = true;
+ imx8mp_ldb_ch = &imx8mp_ldb->channel[i];
+ goto get_phy;
+ }
+
+ if (!of_device_is_available(child))
+ continue;
+
+ imx8mp_ldb_ch = &imx8mp_ldb->channel[i];
+get_phy:
+ imx8mp_ldb_ch->phy = devm_of_phy_get(dev, child, "ldb_phy");
+ if (IS_ERR(imx8mp_ldb_ch->phy)) {
+ ret = PTR_ERR(imx8mp_ldb_ch->phy);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "can't get channel%d phy: %d\n",
+ i, ret);
+ goto free_child;
+ }
+
+ ret = phy_init(imx8mp_ldb_ch->phy);
+ if (ret < 0) {
+ dev_err(dev, "failed to initialize channel%d phy: %d\n",
+ i, ret);
+ goto free_child;
+ }
+
+ if (auxiliary_ch)
+ continue;
+ }
+
+ for (i = 0; i < LDB_CH_NUM; i++) {
+ ldb_ch = &imx8mp_ldb->channel[i].base;
+
+ if (!ldb_ch->is_valid)
+ continue;
+
+ ret = imx_drm_encoder_parse_of(drm, encoder[i], ldb_ch->child);
+ if (ret)
+ goto disable_pm_runtime;
+ }
+
+ return 0;
+
+free_child:
+ of_node_put(child);
+disable_pm_runtime:
+ pm_runtime_disable(dev);
+
+ return ret;
+}
+
+static void imx8mp_ldb_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct imx8mp_ldb *imx8mp_ldb = dev_get_drvdata(dev);
+ int i;
+
+ for (i = 0; i < LDB_CH_NUM; i++) {
+ struct imx8mp_ldb_channel *imx8mp_ldb_ch =
+ &imx8mp_ldb->channel[i];
+
+ if (imx8mp_ldb_ch->phy_is_on)
+ phy_power_off(imx8mp_ldb_ch->phy);
+
+ phy_exit(imx8mp_ldb_ch->phy);
+ }
+
+ pm_runtime_disable(dev);
+}
+
+static const struct component_ops imx8mp_ldb_ops = {
+ .bind = imx8mp_ldb_bind,
+ .unbind = imx8mp_ldb_unbind,
+};
+
+static int imx8mp_ldb_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct imx8mp_ldb *imx8mp_ldb;
+
+ imx8mp_ldb = devm_kzalloc(dev, sizeof(*imx8mp_ldb), GFP_KERNEL);
+ if (!imx8mp_ldb)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, imx8mp_ldb);
+
+ return component_add(dev, &imx8mp_ldb_ops);
+}
+
+static int imx8mp_ldb_remove(struct platform_device *pdev)
+{
+ component_del(&pdev->dev, &imx8mp_ldb_ops);
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int imx8mp_ldb_suspend(struct device *dev)
+{
+ struct imx8mp_ldb *imx8mp_ldb = dev_get_drvdata(dev);
+ int i;
+
+ if (imx8mp_ldb == NULL)
+ return 0;
+
+ for (i = 0; i < LDB_CH_NUM; i++)
+ phy_exit(imx8mp_ldb->channel[i].phy);
+
+ return 0;
+}
+
+static int imx8mp_ldb_resume(struct device *dev)
+{
+ struct imx8mp_ldb *imx8mp_ldb = dev_get_drvdata(dev);
+ int i;
+
+ if (imx8mp_ldb == NULL)
+ return 0;
+
+ for (i = 0; i < LDB_CH_NUM; i++)
+ phy_init(imx8mp_ldb->channel[i].phy);
+
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops imx8mp_ldb_pm_ops = {
+ SET_LATE_SYSTEM_SLEEP_PM_OPS(imx8mp_ldb_suspend, imx8mp_ldb_resume)
+};
+
+static struct platform_driver imx8mp_ldb_driver = {
+ .probe = imx8mp_ldb_probe,
+ .remove = imx8mp_ldb_remove,
+ .driver = {
+ .of_match_table = imx8mp_ldb_dt_ids,
+ .name = DRIVER_NAME,
+ .pm = &imx8mp_ldb_pm_ops,
+ },
+};
+
+module_platform_driver(imx8mp_ldb_driver);
+
+MODULE_DESCRIPTION("i.MX8MP LVDS driver");
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRIVER_NAME);
diff --git a/drivers/gpu/drm/imx/imx8qm-ldb.c b/drivers/gpu/drm/imx/imx8qm-ldb.c
new file mode 100644
index 000000000000..383c80014fdf
--- /dev/null
+++ b/drivers/gpu/drm/imx/imx8qm-ldb.c
@@ -0,0 +1,571 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2020,2022 NXP
+ */
+
+#include <dt-bindings/firmware/imx/rsrc.h>
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/firmware/imx/sci.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/phy/phy.h>
+#include <linux/phy/phy-mixel-lvds.h>
+
+#include <drm/bridge/fsl_imx_ldb.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+
+#include "imx-drm.h"
+
+#define DRIVER_NAME "imx8qm-ldb"
+
+#define LDB_CH0_MODE_EN_TO_DI0 (1 << 0)
+#define LDB_CH0_MODE_EN_TO_DI1 (3 << 0)
+#define LDB_CH0_MODE_EN_MASK (3 << 0)
+#define LDB_CH1_MODE_EN_TO_DI0 (1 << 2)
+#define LDB_CH1_MODE_EN_TO_DI1 (3 << 2)
+#define LDB_CH1_MODE_EN_MASK (3 << 2)
+#define LDB_BIT_MAP_CH0_JEIDA (1 << 6)
+#define LDB_BIT_MAP_CH1_JEIDA (1 << 8)
+#define LDB_CH0_10BIT_EN (1 << 22)
+#define LDB_CH1_10BIT_EN (1 << 23)
+#define LDB_CH0_DATA_WIDTH_24BIT (1 << 24)
+#define LDB_CH1_DATA_WIDTH_24BIT (1 << 26)
+#define LDB_CH0_DATA_WIDTH_30BIT (2 << 24)
+#define LDB_CH1_DATA_WIDTH_30BIT (2 << 26)
+
+struct imx8qm_ldb;
+
+struct imx8qm_ldb_channel {
+ struct ldb_channel base;
+ struct imx8qm_ldb *imx8qm_ldb;
+
+ struct drm_connector connector;
+ struct drm_encoder encoder;
+
+ struct phy *phy;
+ bool phy_is_on;
+
+ u32 bus_flags;
+};
+
+static inline struct imx8qm_ldb_channel *
+con_to_imx8qm_ldb_ch(struct drm_connector *c)
+{
+ return container_of(c, struct imx8qm_ldb_channel, connector);
+}
+
+static inline struct imx8qm_ldb_channel *
+enc_to_imx8qm_ldb_ch(struct drm_encoder *e)
+{
+ return container_of(e, struct imx8qm_ldb_channel, encoder);
+}
+
+struct imx8qm_ldb {
+ struct ldb base;
+ struct imx8qm_ldb_channel channel[LDB_CH_NUM];
+ struct clk *clk_pixel;
+ struct clk *clk_bypass;
+ struct imx_sc_ipc *handle;
+
+ int id;
+};
+
+static void
+imx8qm_ldb_ch_set_bus_format(struct imx8qm_ldb_channel *imx8qm_ldb_ch,
+ u32 bus_format)
+{
+ struct imx8qm_ldb *imx8qm_ldb = imx8qm_ldb_ch->imx8qm_ldb;
+ struct ldb *ldb = &imx8qm_ldb->base;
+ struct ldb_channel *ldb_ch = &imx8qm_ldb_ch->base;
+
+ switch (bus_format) {
+ case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
+ break;
+ case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
+ if (ldb_ch->chno == 0 || ldb->dual)
+ ldb->ldb_ctrl |= LDB_CH0_DATA_WIDTH_24BIT;
+ if (ldb_ch->chno == 1 || ldb->dual)
+ ldb->ldb_ctrl |= LDB_CH1_DATA_WIDTH_24BIT;
+ break;
+ case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
+ if (ldb_ch->chno == 0 || ldb->dual)
+ ldb->ldb_ctrl |= LDB_CH0_DATA_WIDTH_24BIT;
+ if (ldb_ch->chno == 1 || ldb->dual)
+ ldb->ldb_ctrl |= LDB_CH1_DATA_WIDTH_24BIT;
+ break;
+ case MEDIA_BUS_FMT_RGB101010_1X7X5_SPWG:
+ if (ldb_ch->chno == 0 || ldb->dual)
+ ldb->ldb_ctrl |= LDB_CH0_10BIT_EN |
+ LDB_CH0_DATA_WIDTH_30BIT;
+ if (ldb_ch->chno == 1 || ldb->dual)
+ ldb->ldb_ctrl |= LDB_CH1_10BIT_EN |
+ LDB_CH1_DATA_WIDTH_30BIT;
+ break;
+ case MEDIA_BUS_FMT_RGB101010_1X7X5_JEIDA:
+ if (ldb_ch->chno == 0 || ldb->dual)
+ ldb->ldb_ctrl |= LDB_CH0_10BIT_EN |
+ LDB_CH0_DATA_WIDTH_30BIT |
+ LDB_BIT_MAP_CH0_JEIDA;
+ if (ldb_ch->chno == 1 || ldb->dual)
+ ldb->ldb_ctrl |= LDB_CH1_10BIT_EN |
+ LDB_CH1_DATA_WIDTH_30BIT |
+ LDB_BIT_MAP_CH1_JEIDA;
+ break;
+ }
+}
+
+static struct drm_encoder *imx8qm_ldb_connector_best_encoder(
+ struct drm_connector *connector)
+{
+ struct imx8qm_ldb_channel *imx8qm_ldb_ch =
+ con_to_imx8qm_ldb_ch(connector);
+
+ return &imx8qm_ldb_ch->encoder;
+}
+
+static void imx8qm_ldb_pxlink_set_mst_valid(struct imx8qm_ldb *imx8qm_ldb,
+ int dc_id, bool enable)
+{
+ u32 rsc = dc_id ? IMX_SC_R_DC_1 : IMX_SC_R_DC_0;
+
+ imx_sc_misc_set_control(imx8qm_ldb->handle,
+ rsc, IMX_SC_C_PXL_LINK_MST2_VLD, enable);
+}
+
+static void imx8qm_ldb_pxlink_set_sync_ctrl(struct imx8qm_ldb *imx8qm_ldb,
+ int dc_id, bool enable)
+{
+ u32 rsc = dc_id ? IMX_SC_R_DC_1 : IMX_SC_R_DC_0;
+
+ imx_sc_misc_set_control(imx8qm_ldb->handle,
+ rsc, IMX_SC_C_SYNC_CTRL1, enable);
+}
+
+static void imx8qm_ldb_encoder_enable(struct drm_encoder *encoder)
+{
+ struct imx8qm_ldb_channel *imx8qm_ldb_ch =
+ enc_to_imx8qm_ldb_ch(encoder);
+ struct imx8qm_ldb *imx8qm_ldb = imx8qm_ldb_ch->imx8qm_ldb;
+ struct ldb *ldb = &imx8qm_ldb->base;
+
+ clk_prepare_enable(imx8qm_ldb->clk_pixel);
+ clk_prepare_enable(imx8qm_ldb->clk_bypass);
+
+ if (imx8qm_ldb_ch == &imx8qm_ldb->channel[0] || ldb->dual) {
+ ldb->ldb_ctrl &= ~LDB_CH0_MODE_EN_MASK;
+ ldb->ldb_ctrl |= LDB_CH0_MODE_EN_TO_DI0;
+ }
+ if (imx8qm_ldb_ch == &imx8qm_ldb->channel[1] || ldb->dual) {
+ ldb->ldb_ctrl &= ~LDB_CH1_MODE_EN_MASK;
+ ldb->ldb_ctrl |= ldb->dual ?
+ LDB_CH1_MODE_EN_TO_DI0 : LDB_CH1_MODE_EN_TO_DI1;
+ }
+
+ if (ldb->dual) {
+ phy_power_on(imx8qm_ldb->channel[0].phy);
+ phy_power_on(imx8qm_ldb->channel[1].phy);
+
+ imx8qm_ldb->channel[0].phy_is_on = true;
+ imx8qm_ldb->channel[1].phy_is_on = true;
+ } else {
+ phy_power_on(imx8qm_ldb_ch->phy);
+
+ imx8qm_ldb_ch->phy_is_on = true;
+ }
+
+ imx8qm_ldb_pxlink_set_mst_valid(imx8qm_ldb, imx8qm_ldb->id, true);
+ imx8qm_ldb_pxlink_set_sync_ctrl(imx8qm_ldb, imx8qm_ldb->id, true);
+}
+
+static void
+imx8qm_ldb_encoder_atomic_mode_set(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *connector_state)
+{
+ struct imx8qm_ldb_channel *imx8qm_ldb_ch =
+ enc_to_imx8qm_ldb_ch(encoder);
+ struct imx8qm_ldb *imx8qm_ldb = imx8qm_ldb_ch->imx8qm_ldb;
+ struct ldb_channel *ldb_ch = &imx8qm_ldb_ch->base;
+ struct ldb *ldb = &imx8qm_ldb->base;
+ struct drm_display_mode *mode = &crtc_state->adjusted_mode;
+ unsigned long di_clk = mode->clock * 1000;
+
+ if (mode->clock > 300000) {
+ dev_warn(ldb->dev,
+ "%s: mode exceeds 300 MHz pixel clock\n", __func__);
+ }
+ if (mode->clock > 150000 && !ldb->dual) {
+ dev_warn(ldb->dev,
+ "%s: mode exceeds 150 MHz pixel clock\n", __func__);
+ }
+
+ clk_set_rate(imx8qm_ldb->clk_bypass, di_clk);
+ clk_set_rate(imx8qm_ldb->clk_pixel, di_clk);
+
+ if (ldb->dual) {
+ mixel_phy_lvds_set_phy_speed(imx8qm_ldb->channel[0].phy,
+ di_clk / 2);
+ mixel_phy_lvds_set_phy_speed(imx8qm_ldb->channel[1].phy,
+ di_clk / 2);
+ } else {
+ mixel_phy_lvds_set_phy_speed(imx8qm_ldb_ch->phy, di_clk);
+ }
+
+ if (ldb->dual) {
+ /* VSYNC */
+ if (mode->flags & DRM_MODE_FLAG_NVSYNC) {
+ mixel_phy_lvds_set_vsync_pol(imx8qm_ldb->channel[0].phy,
+ false);
+ mixel_phy_lvds_set_vsync_pol(imx8qm_ldb->channel[1].phy,
+ false);
+ } else if (mode->flags & DRM_MODE_FLAG_PVSYNC) {
+ mixel_phy_lvds_set_vsync_pol(imx8qm_ldb->channel[0].phy,
+ true);
+ mixel_phy_lvds_set_vsync_pol(imx8qm_ldb->channel[1].phy,
+ true);
+ }
+ /* HSYNC */
+ if (mode->flags & DRM_MODE_FLAG_NHSYNC) {
+ mixel_phy_lvds_set_hsync_pol(imx8qm_ldb->channel[0].phy,
+ false);
+ mixel_phy_lvds_set_hsync_pol(imx8qm_ldb->channel[1].phy,
+ false);
+ } else if (mode->flags & DRM_MODE_FLAG_PHSYNC) {
+ mixel_phy_lvds_set_hsync_pol(imx8qm_ldb->channel[0].phy,
+ true);
+ mixel_phy_lvds_set_hsync_pol(imx8qm_ldb->channel[1].phy,
+ true);
+ }
+ } else {
+ /* VSYNC */
+ if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+ mixel_phy_lvds_set_vsync_pol(imx8qm_ldb_ch->phy, false);
+ else if (mode->flags & DRM_MODE_FLAG_PVSYNC)
+ mixel_phy_lvds_set_vsync_pol(imx8qm_ldb_ch->phy, true);
+ /* HSYNC */
+ if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+ mixel_phy_lvds_set_hsync_pol(imx8qm_ldb_ch->phy, false);
+ else if (mode->flags & DRM_MODE_FLAG_PHSYNC)
+ mixel_phy_lvds_set_hsync_pol(imx8qm_ldb_ch->phy, true);
+ }
+
+ if (!ldb_ch->bus_format) {
+ struct drm_connector *connector = connector_state->connector;
+ struct drm_display_info *di = &connector->display_info;
+
+ if (di->num_bus_formats)
+ ldb_ch->bus_format = di->bus_formats[0];
+ }
+ imx8qm_ldb_ch_set_bus_format(imx8qm_ldb_ch, ldb_ch->bus_format);
+}
+
+static void imx8qm_ldb_encoder_disable(struct drm_encoder *encoder)
+{
+ struct imx8qm_ldb_channel *imx8qm_ldb_ch =
+ enc_to_imx8qm_ldb_ch(encoder);
+ struct imx8qm_ldb *imx8qm_ldb = imx8qm_ldb_ch->imx8qm_ldb;
+ struct ldb *ldb = &imx8qm_ldb->base;
+
+ imx8qm_ldb_pxlink_set_mst_valid(imx8qm_ldb, imx8qm_ldb->id, false);
+ imx8qm_ldb_pxlink_set_sync_ctrl(imx8qm_ldb, imx8qm_ldb->id, false);
+
+ if (ldb->dual) {
+ phy_power_off(imx8qm_ldb->channel[0].phy);
+ phy_power_off(imx8qm_ldb->channel[1].phy);
+
+ imx8qm_ldb->channel[0].phy_is_on = false;
+ imx8qm_ldb->channel[1].phy_is_on = false;
+ } else {
+ phy_power_off(imx8qm_ldb_ch->phy);
+
+ imx8qm_ldb_ch->phy_is_on = false;
+ }
+
+ clk_disable_unprepare(imx8qm_ldb->clk_bypass);
+ clk_disable_unprepare(imx8qm_ldb->clk_pixel);
+}
+
+static int
+imx8qm_ldb_encoder_atomic_check(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc_state);
+ struct imx8qm_ldb_channel *imx8qm_ldb_ch =
+ enc_to_imx8qm_ldb_ch(encoder);
+ struct ldb_channel *ldb_ch = &imx8qm_ldb_ch->base;
+ struct drm_display_info *di = &conn_state->connector->display_info;
+ u32 bus_format = ldb_ch->bus_format;
+
+ /* Bus format description in DT overrides connector display info. */
+ if (!bus_format && di->num_bus_formats) {
+ bus_format = di->bus_formats[0];
+ imx_crtc_state->bus_flags = di->bus_flags;
+ } else {
+ bus_format = ldb_ch->bus_format;
+ imx_crtc_state->bus_flags = imx8qm_ldb_ch->bus_flags;
+ }
+ switch (bus_format) {
+ case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
+ imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB666_1X30_PADLO;
+ break;
+ case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
+ case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
+ imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB888_1X30_PADLO;
+ break;
+ case MEDIA_BUS_FMT_RGB101010_1X7X5_SPWG:
+ case MEDIA_BUS_FMT_RGB101010_1X7X5_JEIDA:
+ imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB101010_1X30;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct drm_connector_funcs imx8qm_ldb_connector_funcs = {
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = imx_drm_connector_destroy,
+ .reset = drm_atomic_helper_connector_reset,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static const struct drm_connector_helper_funcs
+imx8qm_ldb_connector_helper_funcs = {
+ .best_encoder = imx8qm_ldb_connector_best_encoder,
+};
+
+static const struct drm_encoder_helper_funcs imx8qm_ldb_encoder_helper_funcs = {
+ .atomic_mode_set = imx8qm_ldb_encoder_atomic_mode_set,
+ .enable = imx8qm_ldb_encoder_enable,
+ .disable = imx8qm_ldb_encoder_disable,
+ .atomic_check = imx8qm_ldb_encoder_atomic_check,
+};
+
+static const struct of_device_id imx8qm_ldb_dt_ids[] = {
+ { .compatible = "fsl,imx8qm-ldb", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, imx8qm_ldb_dt_ids);
+
+static int
+imx8qm_ldb_bind(struct device *dev, struct device *master, void *data)
+{
+ struct drm_device *drm = data;
+ struct device_node *np = dev->of_node;
+ struct device_node *child;
+ struct imx8qm_ldb *imx8qm_ldb = dev_get_drvdata(dev);
+ struct ldb *ldb;
+ struct ldb_channel *ldb_ch;
+ struct drm_encoder *encoder[LDB_CH_NUM];
+ int ret;
+ int i;
+
+ ldb = &imx8qm_ldb->base;
+ ldb->dev = dev;
+ ldb->ctrl_reg = 0xe0;
+ ldb->output_port = 1;
+
+ for (i = 0; i < LDB_CH_NUM; i++) {
+ imx8qm_ldb->channel[i].imx8qm_ldb = imx8qm_ldb;
+ ldb->channel[i] = &imx8qm_ldb->channel[i].base;
+ }
+
+ ret = imx_scu_get_handle(&imx8qm_ldb->handle);
+ if (ret) {
+ dev_err(dev, "failed to get scu ipc handle %d\n", ret);
+ return ret;
+ }
+
+ imx8qm_ldb->id = of_alias_get_id(np, "ldb");
+
+ imx8qm_ldb->clk_pixel = devm_clk_get(dev, "pixel");
+ if (IS_ERR(imx8qm_ldb->clk_pixel))
+ return PTR_ERR(imx8qm_ldb->clk_pixel);
+
+ imx8qm_ldb->clk_bypass = devm_clk_get(dev, "bypass");
+ if (IS_ERR(imx8qm_ldb->clk_bypass))
+ return PTR_ERR(imx8qm_ldb->clk_bypass);
+
+ for_each_child_of_node(np, child) {
+ ret = of_property_read_u32(child, "reg", &i);
+ if (ret || i < 0 || i > 1)
+ return -EINVAL;
+
+ if (!of_device_is_available(child))
+ continue;
+
+ encoder[i] = &imx8qm_ldb->channel[i].encoder;
+
+ drm_encoder_helper_add(encoder[i],
+ &imx8qm_ldb_encoder_helper_funcs);
+ drm_simple_encoder_init(drm, encoder[i], DRM_MODE_ENCODER_LVDS);
+ }
+
+ pm_runtime_enable(dev);
+
+ ret = ldb_bind(ldb, encoder);
+ if (ret)
+ goto disable_pm_runtime;
+
+ for_each_child_of_node(np, child) {
+ struct imx8qm_ldb_channel *imx8qm_ldb_ch;
+ bool auxiliary_ch = false;
+
+ ret = of_property_read_u32(child, "reg", &i);
+ if (ret || i < 0 || i > 1) {
+ ret = -EINVAL;
+ goto free_child;
+ }
+
+ if (ldb->dual && i > 0) {
+ auxiliary_ch = true;
+ imx8qm_ldb_ch = &imx8qm_ldb->channel[i];
+ goto get_phy;
+ }
+
+ if (!of_device_is_available(child))
+ continue;
+
+ imx8qm_ldb_ch = &imx8qm_ldb->channel[i];
+get_phy:
+ imx8qm_ldb_ch->phy = devm_of_phy_get(dev, child, "ldb_phy");
+ if (IS_ERR(imx8qm_ldb_ch->phy)) {
+ ret = PTR_ERR(imx8qm_ldb_ch->phy);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "can't get channel%d phy: %d\n",
+ i, ret);
+ goto free_child;
+ }
+
+ ret = phy_init(imx8qm_ldb_ch->phy);
+ if (ret < 0) {
+ dev_err(dev, "failed to initialize channel%d phy: %d\n",
+ i, ret);
+ goto free_child;
+ }
+
+ if (auxiliary_ch)
+ continue;
+ }
+
+ for (i = 0; i < LDB_CH_NUM; i++) {
+ ldb_ch = &imx8qm_ldb->channel[i].base;
+
+ if (!ldb_ch->is_valid)
+ continue;
+
+ ret = imx_drm_encoder_parse_of(drm, encoder[i], ldb_ch->child);
+ if (ret)
+ goto disable_pm_runtime;
+ }
+
+ return 0;
+
+free_child:
+ of_node_put(child);
+disable_pm_runtime:
+ pm_runtime_disable(dev);
+
+ return ret;
+}
+
+static void imx8qm_ldb_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct imx8qm_ldb *imx8qm_ldb = dev_get_drvdata(dev);
+ int i;
+
+ for (i = 0; i < LDB_CH_NUM; i++) {
+ struct imx8qm_ldb_channel *imx8qm_ldb_ch =
+ &imx8qm_ldb->channel[i];
+
+ if (imx8qm_ldb_ch->phy_is_on)
+ phy_power_off(imx8qm_ldb_ch->phy);
+
+ phy_exit(imx8qm_ldb_ch->phy);
+ }
+
+ pm_runtime_disable(dev);
+}
+
+static const struct component_ops imx8qm_ldb_ops = {
+ .bind = imx8qm_ldb_bind,
+ .unbind = imx8qm_ldb_unbind,
+};
+
+static int imx8qm_ldb_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct imx8qm_ldb *imx8qm_ldb;
+
+ imx8qm_ldb = devm_kzalloc(dev, sizeof(*imx8qm_ldb), GFP_KERNEL);
+ if (!imx8qm_ldb)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, imx8qm_ldb);
+
+ return component_add(dev, &imx8qm_ldb_ops);
+}
+
+static int imx8qm_ldb_remove(struct platform_device *pdev)
+{
+ component_del(&pdev->dev, &imx8qm_ldb_ops);
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int imx8qm_ldb_suspend(struct device *dev)
+{
+ struct imx8qm_ldb *imx8qm_ldb = dev_get_drvdata(dev);
+ int i;
+
+ if (imx8qm_ldb == NULL)
+ return 0;
+
+ for (i = 0; i < LDB_CH_NUM; i++)
+ phy_exit(imx8qm_ldb->channel[i].phy);
+
+ return 0;
+}
+
+static int imx8qm_ldb_resume(struct device *dev)
+{
+ struct imx8qm_ldb *imx8qm_ldb = dev_get_drvdata(dev);
+ int i;
+
+ if (imx8qm_ldb == NULL)
+ return 0;
+
+ for (i = 0; i < LDB_CH_NUM; i++)
+ phy_init(imx8qm_ldb->channel[i].phy);
+
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops imx8qm_ldb_pm_ops = {
+ SET_LATE_SYSTEM_SLEEP_PM_OPS(imx8qm_ldb_suspend, imx8qm_ldb_resume)
+};
+
+static struct platform_driver imx8qm_ldb_driver = {
+ .probe = imx8qm_ldb_probe,
+ .remove = imx8qm_ldb_remove,
+ .driver = {
+ .of_match_table = imx8qm_ldb_dt_ids,
+ .name = DRIVER_NAME,
+ .pm = &imx8qm_ldb_pm_ops,
+ },
+};
+
+module_platform_driver(imx8qm_ldb_driver);
+
+MODULE_DESCRIPTION("i.MX8QM LVDS driver");
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRIVER_NAME);
diff --git a/drivers/gpu/drm/imx/imx8qxp-ldb.c b/drivers/gpu/drm/imx/imx8qxp-ldb.c
new file mode 100644
index 000000000000..b48c4dbb923d
--- /dev/null
+++ b/drivers/gpu/drm/imx/imx8qxp-ldb.c
@@ -0,0 +1,882 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2020,2022 NXP
+ */
+
+#include <dt-bindings/firmware/imx/rsrc.h>
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/firmware/imx/sci.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/phy/phy.h>
+#include <linux/phy/phy-mixel-lvds-combo.h>
+#include <linux/pm_domain.h>
+#include <linux/regmap.h>
+
+#include <drm/bridge/fsl_imx_ldb.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+
+#include "imx-drm.h"
+
+#define DRIVER_NAME "imx8qxp-ldb"
+
+#define LDB_CH0_MODE_EN_TO_DI0 (1 << 0)
+#define LDB_CH0_MODE_EN_TO_DI1 (3 << 0)
+#define LDB_CH0_MODE_EN_MASK (3 << 0)
+#define LDB_CH1_MODE_EN_TO_DI0 (1 << 2)
+#define LDB_CH1_MODE_EN_TO_DI1 (3 << 2)
+#define LDB_CH1_MODE_EN_MASK (3 << 2)
+#define LDB_BIT_MAP_CH0_JEIDA (1 << 6)
+#define LDB_BIT_MAP_CH1_JEIDA (1 << 8)
+#define LDB_DI0_VS_POL_ACT_LOW (1 << 9)
+#define LDB_DI1_VS_POL_ACT_LOW (1 << 10)
+#define LDB_CH0_10BIT_EN (1 << 22)
+#define LDB_CH1_10BIT_EN (1 << 23)
+#define LDB_CH0_DATA_WIDTH_24BIT (1 << 24)
+#define LDB_CH1_DATA_WIDTH_24BIT (1 << 26)
+#define LDB_CH0_DATA_WIDTH_30BIT (2 << 24)
+#define LDB_CH1_DATA_WIDTH_30BIT (2 << 26)
+#define LDB_CH_SEL (1 << 28)
+
+struct imx8qxp_ldb;
+
+struct imx8qxp_ldb_channel {
+ struct ldb_channel base;
+ struct imx8qxp_ldb *imx8qxp_ldb;
+
+ struct drm_connector connector;
+ struct drm_encoder encoder;
+
+ struct phy *phy;
+ struct phy *aux_phy;
+ bool phy_is_on;
+
+ u32 bus_flags;
+};
+
+static inline struct imx8qxp_ldb_channel *
+con_to_imx8qxp_ldb_ch(struct drm_connector *c)
+{
+ return container_of(c, struct imx8qxp_ldb_channel, connector);
+}
+
+static inline struct imx8qxp_ldb_channel *
+enc_to_imx8qxp_ldb_ch(struct drm_encoder *e)
+{
+ return container_of(e, struct imx8qxp_ldb_channel, encoder);
+}
+
+struct imx8qxp_ldb {
+ struct ldb base;
+ struct regmap *aux_regmap;
+ struct imx8qxp_ldb_channel channel[LDB_CH_NUM];
+ struct clk *clk_pixel;
+ struct clk *clk_bypass;
+ struct clk *clk_aux_pixel;
+ struct clk *clk_aux_bypass;
+ struct imx_sc_ipc *handle;
+
+ struct device *pd_main_dev;
+ struct device *pd_aux_dev;
+ struct device_link *pd_main_link;
+ struct device_link *pd_aux_link;
+
+ int id;
+};
+
+static void
+imx8qxp_ldb_ch_set_bus_format(struct imx8qxp_ldb_channel *imx8qxp_ldb_ch,
+ u32 bus_format)
+{
+ struct imx8qxp_ldb *imx8qxp_ldb = imx8qxp_ldb_ch->imx8qxp_ldb;
+ struct ldb *ldb = &imx8qxp_ldb->base;
+ struct ldb_channel *ldb_ch = &imx8qxp_ldb_ch->base;
+
+ switch (bus_format) {
+ case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
+ break;
+ case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
+ if (ldb_ch->chno == 0 || ldb->dual)
+ ldb->ldb_ctrl |= LDB_CH0_DATA_WIDTH_24BIT;
+ if (ldb_ch->chno == 1 || ldb->dual)
+ ldb->ldb_ctrl |= LDB_CH1_DATA_WIDTH_24BIT;
+ break;
+ case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
+ if (ldb_ch->chno == 0 || ldb->dual)
+ ldb->ldb_ctrl |= LDB_CH0_DATA_WIDTH_24BIT;
+ if (ldb_ch->chno == 1 || ldb->dual)
+ ldb->ldb_ctrl |= LDB_CH1_DATA_WIDTH_24BIT;
+ break;
+ case MEDIA_BUS_FMT_RGB101010_1X7X5_SPWG:
+ if (ldb_ch->chno == 0 || ldb->dual)
+ ldb->ldb_ctrl |= LDB_CH0_10BIT_EN |
+ LDB_CH0_DATA_WIDTH_30BIT;
+ if (ldb_ch->chno == 1 || ldb->dual)
+ ldb->ldb_ctrl |= LDB_CH1_10BIT_EN |
+ LDB_CH1_DATA_WIDTH_30BIT;
+ break;
+ case MEDIA_BUS_FMT_RGB101010_1X7X5_JEIDA:
+ if (ldb_ch->chno == 0 || ldb->dual)
+ ldb->ldb_ctrl |= LDB_CH0_10BIT_EN |
+ LDB_CH0_DATA_WIDTH_30BIT |
+ LDB_BIT_MAP_CH0_JEIDA;
+ if (ldb_ch->chno == 1 || ldb->dual)
+ ldb->ldb_ctrl |= LDB_CH1_10BIT_EN |
+ LDB_CH1_DATA_WIDTH_30BIT |
+ LDB_BIT_MAP_CH1_JEIDA;
+ break;
+ }
+}
+
+static struct drm_encoder *imx8qxp_ldb_connector_best_encoder(
+ struct drm_connector *connector)
+{
+ struct imx8qxp_ldb_channel *imx8qxp_ldb_ch =
+ con_to_imx8qxp_ldb_ch(connector);
+
+ return &imx8qxp_ldb_ch->encoder;
+}
+
+static void imx8qxp_ldb_pxlink_enable(struct imx8qxp_ldb *imx8qxp_ldb,
+ int stream_id, bool enable)
+{
+ u8 ctrl = stream_id ?
+ IMX_SC_C_PXL_LINK_MST2_ENB : IMX_SC_C_PXL_LINK_MST1_ENB;
+
+ imx_sc_misc_set_control(imx8qxp_ldb->handle,
+ IMX_SC_R_DC_0, ctrl, enable);
+}
+
+static void imx8qxp_ldb_pxlink_set_mst_valid(struct imx8qxp_ldb *imx8qxp_ldb,
+ int stream_id, bool enable)
+{
+ u8 ctrl = stream_id ?
+ IMX_SC_C_PXL_LINK_MST2_VLD : IMX_SC_C_PXL_LINK_MST1_VLD;
+
+ imx_sc_misc_set_control(imx8qxp_ldb->handle,
+ IMX_SC_R_DC_0, ctrl, enable);
+}
+
+static void imx8qxp_ldb_pxlink_set_sync_ctrl(struct imx8qxp_ldb *imx8qxp_ldb,
+ int stream_id, bool enable)
+{
+ u8 ctrl = stream_id ? IMX_SC_C_SYNC_CTRL1 : IMX_SC_C_SYNC_CTRL0;
+
+ imx_sc_misc_set_control(imx8qxp_ldb->handle,
+ IMX_SC_R_DC_0, ctrl, enable);
+}
+
+static void imx8qxp_ldb_encoder_enable(struct drm_encoder *encoder)
+{
+ struct imx8qxp_ldb_channel *imx8qxp_ldb_ch =
+ enc_to_imx8qxp_ldb_ch(encoder);
+ struct imx8qxp_ldb *imx8qxp_ldb = imx8qxp_ldb_ch->imx8qxp_ldb;
+ struct ldb *ldb = &imx8qxp_ldb->base;
+
+ clk_prepare_enable(imx8qxp_ldb->clk_pixel);
+ clk_prepare_enable(imx8qxp_ldb->clk_bypass);
+
+ if (ldb->dual) {
+ clk_prepare_enable(imx8qxp_ldb->clk_aux_pixel);
+ clk_prepare_enable(imx8qxp_ldb->clk_aux_bypass);
+ }
+
+ /*
+ * LDB frontend doesn't know if the auxiliary LDB is used or not.
+ * Enable pixel link after dual or single LDB clocks are enabled
+ * so that the dual LDBs are synchronized.
+ */
+ imx8qxp_ldb_pxlink_enable(imx8qxp_ldb, imx8qxp_ldb->id, true);
+
+ if (imx8qxp_ldb_ch == &imx8qxp_ldb->channel[0] || ldb->dual) {
+ ldb->ldb_ctrl &= ~LDB_CH0_MODE_EN_MASK;
+ ldb->ldb_ctrl |= LDB_CH0_MODE_EN_TO_DI0;
+ }
+ if (imx8qxp_ldb_ch == &imx8qxp_ldb->channel[1] || ldb->dual) {
+ ldb->ldb_ctrl &= ~LDB_CH1_MODE_EN_MASK;
+ ldb->ldb_ctrl |= ldb->dual ?
+ LDB_CH1_MODE_EN_TO_DI0 : LDB_CH1_MODE_EN_TO_DI1;
+ }
+
+ pm_runtime_get_sync(ldb->dev);
+
+ regmap_write(ldb->regmap, ldb->ctrl_reg, ldb->ldb_ctrl);
+ if (ldb->dual)
+ regmap_write(imx8qxp_ldb->aux_regmap, ldb->ctrl_reg,
+ ldb->ldb_ctrl | LDB_CH_SEL);
+
+ if (ldb->dual) {
+ phy_power_on(imx8qxp_ldb->channel[0].phy);
+ phy_power_on(imx8qxp_ldb->channel[0].aux_phy);
+
+ imx8qxp_ldb->channel[0].phy_is_on = true;
+ } else {
+ phy_power_on(imx8qxp_ldb_ch->phy);
+
+ imx8qxp_ldb_ch->phy_is_on = true;
+ }
+
+ imx8qxp_ldb_pxlink_set_mst_valid(imx8qxp_ldb, imx8qxp_ldb->id, true);
+ imx8qxp_ldb_pxlink_set_sync_ctrl(imx8qxp_ldb, imx8qxp_ldb->id, true);
+}
+
+static void
+imx8qxp_ldb_encoder_atomic_mode_set(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *connector_state)
+{
+ struct imx8qxp_ldb_channel *imx8qxp_ldb_ch =
+ enc_to_imx8qxp_ldb_ch(encoder);
+ struct imx8qxp_ldb *imx8qxp_ldb = imx8qxp_ldb_ch->imx8qxp_ldb;
+ struct ldb_channel *ldb_ch = &imx8qxp_ldb_ch->base;
+ struct ldb *ldb = &imx8qxp_ldb->base;
+ struct drm_display_mode *mode = &crtc_state->adjusted_mode;
+ unsigned long di_clk = mode->clock * 1000;
+
+ if (mode->clock > 300000) {
+ dev_warn(ldb->dev,
+ "%s: mode exceeds 300 MHz pixel clock\n", __func__);
+ }
+ if (mode->clock > 150000 && !ldb->dual) {
+ dev_warn(ldb->dev,
+ "%s: mode exceeds 150 MHz pixel clock\n", __func__);
+ }
+
+ clk_set_rate(imx8qxp_ldb->clk_bypass, di_clk);
+ clk_set_rate(imx8qxp_ldb->clk_pixel, di_clk);
+
+ if (ldb->dual) {
+ clk_set_rate(imx8qxp_ldb->clk_aux_bypass, di_clk);
+ clk_set_rate(imx8qxp_ldb->clk_aux_pixel, di_clk);
+ }
+
+ if (ldb->dual) {
+ mixel_phy_combo_lvds_set_phy_speed(imx8qxp_ldb->channel[0].phy,
+ di_clk / 2);
+ mixel_phy_combo_lvds_set_phy_speed(imx8qxp_ldb->channel[0].aux_phy,
+ di_clk / 2);
+ } else {
+ mixel_phy_combo_lvds_set_phy_speed(imx8qxp_ldb_ch->phy, di_clk);
+ }
+
+ if (imx8qxp_ldb_ch == &imx8qxp_ldb->channel[0])
+ ldb->ldb_ctrl &= ~LDB_CH_SEL;
+ if (imx8qxp_ldb_ch == &imx8qxp_ldb->channel[1])
+ ldb->ldb_ctrl |= LDB_CH_SEL;
+
+ /* FIXME - assumes straight connections DI0 --> CH0, DI1 --> CH1 */
+ if (imx8qxp_ldb_ch == &imx8qxp_ldb->channel[0] || ldb->dual) {
+ if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+ ldb->ldb_ctrl |= LDB_DI0_VS_POL_ACT_LOW;
+ else if (mode->flags & DRM_MODE_FLAG_PVSYNC)
+ ldb->ldb_ctrl &= ~LDB_DI0_VS_POL_ACT_LOW;
+ }
+ if (imx8qxp_ldb_ch == &imx8qxp_ldb->channel[1] || ldb->dual) {
+ if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+ ldb->ldb_ctrl |= LDB_DI1_VS_POL_ACT_LOW;
+ else if (mode->flags & DRM_MODE_FLAG_PVSYNC)
+ ldb->ldb_ctrl &= ~LDB_DI1_VS_POL_ACT_LOW;
+ }
+
+ pm_runtime_get_sync(ldb->dev);
+
+ /* settle vsync polarity and channel selection down early */
+ if (ldb->dual) {
+ regmap_write(ldb->regmap, ldb->ctrl_reg, ldb->ldb_ctrl);
+ regmap_write(imx8qxp_ldb->aux_regmap, ldb->ctrl_reg,
+ ldb->ldb_ctrl | LDB_CH_SEL);
+ }
+
+ pm_runtime_put(ldb->dev);
+
+ if (ldb->dual) {
+ /* VSYNC */
+ if (mode->flags & DRM_MODE_FLAG_NVSYNC) {
+ mixel_phy_combo_lvds_set_vsync_pol(
+ imx8qxp_ldb->channel[0].phy, false);
+ mixel_phy_combo_lvds_set_vsync_pol(
+ imx8qxp_ldb->channel[0].aux_phy, false);
+ } else {
+ mixel_phy_combo_lvds_set_vsync_pol(
+ imx8qxp_ldb->channel[0].phy, true);
+ mixel_phy_combo_lvds_set_vsync_pol(
+ imx8qxp_ldb->channel[0].aux_phy, true);
+ }
+ /* HSYNC */
+ if (mode->flags & DRM_MODE_FLAG_NHSYNC) {
+ mixel_phy_combo_lvds_set_hsync_pol(
+ imx8qxp_ldb->channel[0].phy, false);
+ mixel_phy_combo_lvds_set_hsync_pol(
+ imx8qxp_ldb->channel[0].aux_phy, false);
+ } else {
+ mixel_phy_combo_lvds_set_hsync_pol(
+ imx8qxp_ldb->channel[0].phy, true);
+ mixel_phy_combo_lvds_set_hsync_pol(
+ imx8qxp_ldb->channel[0].aux_phy, true);
+ }
+ } else {
+ /* VSYNC */
+ if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+ mixel_phy_combo_lvds_set_vsync_pol(imx8qxp_ldb_ch->phy,
+ false);
+ else if (mode->flags & DRM_MODE_FLAG_PVSYNC)
+ mixel_phy_combo_lvds_set_vsync_pol(imx8qxp_ldb_ch->phy,
+ true);
+ /* HSYNC */
+ if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+ mixel_phy_combo_lvds_set_hsync_pol(imx8qxp_ldb_ch->phy,
+ false);
+ else if (mode->flags & DRM_MODE_FLAG_PHSYNC)
+ mixel_phy_combo_lvds_set_hsync_pol(imx8qxp_ldb_ch->phy,
+ true);
+ }
+
+ if (!ldb_ch->bus_format) {
+ struct drm_connector *connector = connector_state->connector;
+ struct drm_display_info *di = &connector->display_info;
+
+ if (di->num_bus_formats)
+ ldb_ch->bus_format = di->bus_formats[0];
+ }
+ imx8qxp_ldb_ch_set_bus_format(imx8qxp_ldb_ch, ldb_ch->bus_format);
+}
+
+static void imx8qxp_ldb_encoder_disable(struct drm_encoder *encoder)
+{
+ struct imx8qxp_ldb_channel *imx8qxp_ldb_ch =
+ enc_to_imx8qxp_ldb_ch(encoder);
+ struct imx8qxp_ldb *imx8qxp_ldb = imx8qxp_ldb_ch->imx8qxp_ldb;
+ struct ldb *ldb = &imx8qxp_ldb->base;
+
+ imx8qxp_ldb_pxlink_set_mst_valid(imx8qxp_ldb, imx8qxp_ldb->id, false);
+ imx8qxp_ldb_pxlink_set_sync_ctrl(imx8qxp_ldb, imx8qxp_ldb->id, false);
+
+ if (ldb->dual) {
+ phy_power_off(imx8qxp_ldb->channel[0].phy);
+ phy_power_off(imx8qxp_ldb->channel[0].aux_phy);
+
+ imx8qxp_ldb->channel[0].phy_is_on = false;
+ } else {
+ phy_power_off(imx8qxp_ldb_ch->phy);
+
+ imx8qxp_ldb_ch->phy_is_on = false;
+ }
+
+ if (ldb->dual)
+ regmap_write(imx8qxp_ldb->aux_regmap,
+ ldb->ctrl_reg, ldb->ldb_ctrl);
+
+ pm_runtime_put(ldb->dev);
+
+ clk_disable_unprepare(imx8qxp_ldb->clk_bypass);
+ clk_disable_unprepare(imx8qxp_ldb->clk_pixel);
+
+ if (ldb->dual) {
+ clk_disable_unprepare(imx8qxp_ldb->clk_aux_bypass);
+ clk_disable_unprepare(imx8qxp_ldb->clk_aux_pixel);
+ }
+
+ imx8qxp_ldb_pxlink_enable(imx8qxp_ldb, imx8qxp_ldb->id, false);
+}
+
+static int
+imx8qxp_ldb_encoder_atomic_check(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc_state);
+ struct imx8qxp_ldb_channel *imx8qxp_ldb_ch =
+ enc_to_imx8qxp_ldb_ch(encoder);
+ struct ldb_channel *ldb_ch = &imx8qxp_ldb_ch->base;
+ struct drm_display_info *di = &conn_state->connector->display_info;
+ u32 bus_format = ldb_ch->bus_format;
+
+ /* Bus format description in DT overrides connector display info. */
+ if (!bus_format && di->num_bus_formats) {
+ bus_format = di->bus_formats[0];
+ imx_crtc_state->bus_flags = di->bus_flags;
+ } else {
+ bus_format = ldb_ch->bus_format;
+ imx_crtc_state->bus_flags = imx8qxp_ldb_ch->bus_flags;
+ }
+ switch (bus_format) {
+ case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
+ imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB666_1X30_PADLO;
+ break;
+ case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
+ case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
+ imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB888_1X30_PADLO;
+ break;
+ case MEDIA_BUS_FMT_RGB101010_1X7X5_SPWG:
+ case MEDIA_BUS_FMT_RGB101010_1X7X5_JEIDA:
+ imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB101010_1X30;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct drm_connector_funcs imx8qxp_ldb_connector_funcs = {
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = imx_drm_connector_destroy,
+ .reset = drm_atomic_helper_connector_reset,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static const struct drm_connector_helper_funcs
+imx8qxp_ldb_connector_helper_funcs = {
+ .best_encoder = imx8qxp_ldb_connector_best_encoder,
+};
+
+static const struct drm_encoder_helper_funcs
+imx8qxp_ldb_encoder_helper_funcs = {
+ .atomic_mode_set = imx8qxp_ldb_encoder_atomic_mode_set,
+ .enable = imx8qxp_ldb_encoder_enable,
+ .disable = imx8qxp_ldb_encoder_disable,
+ .atomic_check = imx8qxp_ldb_encoder_atomic_check,
+};
+
+static const struct of_device_id imx8qxp_ldb_dt_ids[] = {
+ { .compatible = "fsl,imx8qxp-ldb", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, imx8qxp_ldb_dt_ids);
+
+static void imx8qxp_ldb_detach_pm_domains(struct imx8qxp_ldb *imx8qxp_ldb)
+{
+ if (imx8qxp_ldb->pd_aux_link && !IS_ERR(imx8qxp_ldb->pd_aux_link))
+ device_link_del(imx8qxp_ldb->pd_aux_link);
+ if (imx8qxp_ldb->pd_aux_dev && !IS_ERR(imx8qxp_ldb->pd_aux_dev))
+ dev_pm_domain_detach(imx8qxp_ldb->pd_aux_dev, true);
+
+ if (imx8qxp_ldb->pd_main_link && !IS_ERR(imx8qxp_ldb->pd_main_link))
+ device_link_del(imx8qxp_ldb->pd_main_link);
+ if (imx8qxp_ldb->pd_main_dev && !IS_ERR(imx8qxp_ldb->pd_main_dev))
+ dev_pm_domain_detach(imx8qxp_ldb->pd_main_dev, true);
+
+ imx8qxp_ldb->pd_aux_dev = NULL;
+ imx8qxp_ldb->pd_aux_link = NULL;
+ imx8qxp_ldb->pd_main_dev = NULL;
+ imx8qxp_ldb->pd_main_link = NULL;
+}
+
+static int
+imx8qxp_ldb_attach_pm_domains(struct imx8qxp_ldb *imx8qxp_ldb, bool dual)
+{
+ struct ldb *ldb = &imx8qxp_ldb->base;
+ struct device *dev = ldb->dev;
+ u32 flags = DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME | DL_FLAG_RPM_ACTIVE;
+ int ret = 0;
+
+ imx8qxp_ldb->pd_main_dev = dev_pm_domain_attach_by_name(dev, "main");
+ if (IS_ERR(imx8qxp_ldb->pd_main_dev)) {
+ ret = PTR_ERR(imx8qxp_ldb->pd_main_dev);
+ dev_err(dev, "Failed to attach main pd dev: %d\n", ret);
+ goto fail;
+ }
+ imx8qxp_ldb->pd_main_link = device_link_add(dev,
+ imx8qxp_ldb->pd_main_dev, flags);
+ if (IS_ERR(imx8qxp_ldb->pd_main_link)) {
+ ret = PTR_ERR(imx8qxp_ldb->pd_main_link);
+ dev_err(dev, "Failed to add device link to main pd dev: %d\n",
+ ret);
+ goto fail;
+ }
+
+ if (!dual)
+ goto out;
+
+ imx8qxp_ldb->pd_aux_dev = dev_pm_domain_attach_by_name(dev, "aux");
+ if (IS_ERR(imx8qxp_ldb->pd_aux_dev)) {
+ ret = PTR_ERR(imx8qxp_ldb->pd_aux_dev);
+ dev_err(dev, "Failed to attach aux pd dev: %d\n", ret);
+ goto fail;
+ }
+ imx8qxp_ldb->pd_aux_link = device_link_add(dev,
+ imx8qxp_ldb->pd_aux_dev, flags);
+ if (IS_ERR(imx8qxp_ldb->pd_aux_link)) {
+ ret = PTR_ERR(imx8qxp_ldb->pd_aux_link);
+ dev_err(dev, "Failed to add device link to aux pd dev: %d\n",
+ ret);
+ goto fail;
+ }
+
+out:
+ return ret;
+fail:
+ imx8qxp_ldb_detach_pm_domains(imx8qxp_ldb);
+ return ret;
+}
+
+static int imx8qxp_ldb_init_sc_misc(int ldb_id, bool dual)
+{
+ struct imx_sc_ipc *handle;
+ u32 rsc;
+ bool is_aux = false;
+ int ret = 0;
+
+ imx_scu_get_handle(&handle);
+
+again:
+ rsc = ldb_id ? IMX_SC_R_MIPI_1 : IMX_SC_R_MIPI_0;
+
+ ret |= imx_sc_misc_set_control(handle,
+ rsc, IMX_SC_C_MODE, 1);
+ ret |= imx_sc_misc_set_control(handle,
+ rsc, IMX_SC_C_DUAL_MODE, is_aux);
+ ret |= imx_sc_misc_set_control(handle,
+ rsc, IMX_SC_C_PXL_LINK_SEL, is_aux);
+
+ if (dual && !is_aux) {
+ ldb_id ^= 1;
+ is_aux = true;
+ goto again;
+ }
+
+ return ret;
+}
+
+static struct phy *imx8qxp_ldb_get_aux_phy(struct device_node *auxldb_np)
+{
+ struct device_node *child;
+ struct phy *phy = NULL;
+ int ret, i;
+
+ for_each_child_of_node(auxldb_np, child) {
+ ret = of_property_read_u32(child, "reg", &i);
+ if (ret || i < 0) {
+ of_node_put(child);
+ return ERR_PTR(-ENODEV);
+ }
+
+ if (i != 1)
+ continue;
+
+ phy = of_phy_get(child, "ldb_phy");
+ }
+
+ of_node_put(child);
+
+ return phy;
+}
+
+static int
+imx8qxp_ldb_bind(struct device *dev, struct device *master, void *data)
+{
+ struct drm_device *drm = data;
+ struct device_node *np = dev->of_node;
+ struct device_node *auxldb_np = NULL, *child;
+ struct imx8qxp_ldb *imx8qxp_ldb = dev_get_drvdata(dev);
+ struct ldb *ldb;
+ struct ldb_channel *ldb_ch;
+ struct drm_encoder *encoder[LDB_CH_NUM];
+ bool dual;
+ int ret;
+ int i;
+
+ ldb = &imx8qxp_ldb->base;
+ ldb->dev = dev;
+ ldb->ctrl_reg = 0xe0;
+ ldb->output_port = 1;
+
+ for (i = 0; i < LDB_CH_NUM; i++) {
+ imx8qxp_ldb->channel[i].imx8qxp_ldb = imx8qxp_ldb;
+ ldb->channel[i] = &imx8qxp_ldb->channel[i].base;
+ }
+
+ ret = imx_scu_get_handle(&imx8qxp_ldb->handle);
+ if (ret) {
+ dev_err(dev, "failed to get scu ipc handle %d\n", ret);
+ return ret;
+ }
+
+ imx8qxp_ldb->id = of_alias_get_id(np, "ldb");
+
+ for_each_child_of_node(np, child) {
+ ret = of_property_read_u32(child, "reg", &i);
+ if (ret || i < 0 || i > 1)
+ return -EINVAL;
+
+ if (!of_device_is_available(child))
+ continue;
+
+ encoder[i] = &imx8qxp_ldb->channel[i].encoder;
+
+ drm_encoder_helper_add(encoder[i],
+ &imx8qxp_ldb_encoder_helper_funcs);
+ drm_simple_encoder_init(drm, encoder[i], DRM_MODE_ENCODER_LVDS);
+ }
+
+ dual = of_property_read_bool(np, "fsl,dual-channel");
+
+ ret = imx8qxp_ldb_attach_pm_domains(imx8qxp_ldb, dual);
+ if (ret) {
+ dev_err(dev, "failed to attach pm domains %d\n", ret);
+ return ret;
+ }
+
+ pm_runtime_enable(dev);
+
+ ret = ldb_bind(ldb, encoder);
+ if (ret)
+ goto disable_pm_runtime;
+
+ ret = imx8qxp_ldb_init_sc_misc(imx8qxp_ldb->id, ldb->dual);
+ if (ret) {
+ dev_err(dev, "failed to initialize sc misc %d\n", ret);
+ goto disable_pm_runtime;
+ }
+
+ imx8qxp_ldb->clk_pixel = devm_clk_get(dev, "pixel");
+ if (IS_ERR(imx8qxp_ldb->clk_pixel)) {
+ ret = PTR_ERR(imx8qxp_ldb->clk_pixel);
+ goto disable_pm_runtime;
+ }
+
+ imx8qxp_ldb->clk_bypass = devm_clk_get(dev, "bypass");
+ if (IS_ERR(imx8qxp_ldb->clk_bypass)) {
+ ret = PTR_ERR(imx8qxp_ldb->clk_bypass);
+ goto disable_pm_runtime;
+ }
+
+ if (ldb->dual) {
+ imx8qxp_ldb->clk_aux_pixel = devm_clk_get(dev, "aux_pixel");
+ if (IS_ERR(imx8qxp_ldb->clk_aux_pixel)) {
+ ret = PTR_ERR(imx8qxp_ldb->clk_aux_pixel);
+ goto disable_pm_runtime;
+ }
+
+ imx8qxp_ldb->clk_aux_bypass = devm_clk_get(dev, "aux_bypass");
+ if (IS_ERR(imx8qxp_ldb->clk_aux_bypass)) {
+ ret = PTR_ERR(imx8qxp_ldb->clk_aux_bypass);
+ goto disable_pm_runtime;
+ }
+
+ auxldb_np = of_parse_phandle(np, "fsl,auxldb", 0);
+ if (!auxldb_np) {
+ dev_err(dev,
+ "failed to find aux LDB node in device tree\n");
+ ret = -ENODEV;
+ goto disable_pm_runtime;
+ }
+
+ if (of_device_is_available(auxldb_np)) {
+ dev_err(dev, "aux LDB node is already in use\n");
+ of_node_put(auxldb_np);
+ ret = -ENODEV;
+ goto disable_pm_runtime;
+ }
+
+ imx8qxp_ldb->aux_regmap =
+ syscon_regmap_lookup_by_phandle(auxldb_np, "gpr");
+ if (IS_ERR(imx8qxp_ldb->aux_regmap)) {
+ ret = PTR_ERR(imx8qxp_ldb->aux_regmap);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "failed to get aux regmap\n");
+ of_node_put(auxldb_np);
+ goto disable_pm_runtime;
+ }
+
+ pm_runtime_get_sync(dev);
+ regmap_write(imx8qxp_ldb->aux_regmap, ldb->ctrl_reg, 0);
+ pm_runtime_put(dev);
+ }
+
+ for_each_child_of_node(np, child) {
+ struct imx8qxp_ldb_channel *imx8qxp_ldb_ch;
+
+ ret = of_property_read_u32(child, "reg", &i);
+ if (ret || i < 0 || i > 1) {
+ ret = -EINVAL;
+ goto free_child;
+ }
+
+ if (!of_device_is_available(child))
+ continue;
+
+ imx8qxp_ldb_ch = &imx8qxp_ldb->channel[i];
+
+ imx8qxp_ldb_ch->phy = devm_of_phy_get(dev, child, "ldb_phy");
+ if (IS_ERR(imx8qxp_ldb_ch->phy)) {
+ ret = PTR_ERR(imx8qxp_ldb_ch->phy);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "can't get channel%d phy: %d\n",
+ i, ret);
+ goto free_child;
+ }
+
+ ret = phy_init(imx8qxp_ldb_ch->phy);
+ if (ret < 0) {
+ dev_err(dev, "failed to initialize channel%d phy: %d\n",
+ i, ret);
+ goto free_child;
+ }
+
+ if (ldb->dual) {
+ imx8qxp_ldb_ch->aux_phy =
+ imx8qxp_ldb_get_aux_phy(auxldb_np);
+ if (IS_ERR(imx8qxp_ldb_ch->aux_phy)) {
+ ret = PTR_ERR(imx8qxp_ldb_ch->aux_phy);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev,
+ "can't get channel0 aux phy: %d\n",
+ ret);
+ goto free_child;
+ }
+
+ ret = phy_init(imx8qxp_ldb_ch->aux_phy);
+ if (ret < 0) {
+ dev_err(dev,
+ "failed to initialize channel0 aux phy: %d\n",
+ ret);
+ goto free_child;
+ }
+ }
+ }
+
+ if (ldb->dual)
+ of_node_put(auxldb_np);
+
+ for (i = 0; i < LDB_CH_NUM; i++) {
+ ldb_ch = &imx8qxp_ldb->channel[i].base;
+
+ if (!ldb_ch->is_valid)
+ continue;
+
+ ret = imx_drm_encoder_parse_of(drm, encoder[i], ldb_ch->child);
+ if (ret)
+ goto disable_pm_runtime;
+ }
+
+ return 0;
+
+free_child:
+ of_node_put(child);
+ if (ldb->dual)
+ of_node_put(auxldb_np);
+disable_pm_runtime:
+ pm_runtime_disable(dev);
+ imx8qxp_ldb_detach_pm_domains(imx8qxp_ldb);
+
+ return ret;
+}
+
+static void imx8qxp_ldb_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct imx8qxp_ldb *imx8qxp_ldb = dev_get_drvdata(dev);
+ struct ldb *ldb = &imx8qxp_ldb->base;
+ int i;
+
+ for (i = 0; i < LDB_CH_NUM; i++) {
+ struct imx8qxp_ldb_channel *imx8qxp_ldb_ch =
+ &imx8qxp_ldb->channel[i];
+
+ if (imx8qxp_ldb_ch->phy_is_on) {
+ phy_power_off(imx8qxp_ldb_ch->phy);
+ if (ldb->dual)
+ phy_power_off(imx8qxp_ldb_ch->aux_phy);
+ }
+
+ phy_exit(imx8qxp_ldb_ch->phy);
+ if (ldb->dual && i == 0)
+ phy_exit(imx8qxp_ldb_ch->aux_phy);
+ }
+
+ imx8qxp_ldb_detach_pm_domains(imx8qxp_ldb);
+}
+
+static const struct component_ops imx8qxp_ldb_ops = {
+ .bind = imx8qxp_ldb_bind,
+ .unbind = imx8qxp_ldb_unbind,
+};
+
+static int imx8qxp_ldb_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct imx8qxp_ldb *imx8qxp_ldb;
+
+ imx8qxp_ldb = devm_kzalloc(dev, sizeof(*imx8qxp_ldb), GFP_KERNEL);
+ if (!imx8qxp_ldb)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, imx8qxp_ldb);
+
+ return component_add(dev, &imx8qxp_ldb_ops);
+}
+
+static int imx8qxp_ldb_remove(struct platform_device *pdev)
+{
+ component_del(&pdev->dev, &imx8qxp_ldb_ops);
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int imx8qxp_ldb_suspend(struct device *dev)
+{
+ struct imx8qxp_ldb *imx8qxp_ldb = dev_get_drvdata(dev);
+ struct ldb *ldb = &imx8qxp_ldb->base;
+ int i;
+
+ if (imx8qxp_ldb == NULL)
+ return 0;
+
+ for (i = 0; i < LDB_CH_NUM; i++) {
+ phy_exit(imx8qxp_ldb->channel[i].phy);
+
+ if (ldb->dual && i == 0)
+ phy_exit(imx8qxp_ldb->channel[i].aux_phy);
+ }
+
+ return 0;
+}
+
+static int imx8qxp_ldb_resume(struct device *dev)
+{
+ struct imx8qxp_ldb *imx8qxp_ldb = dev_get_drvdata(dev);
+ struct ldb *ldb = &imx8qxp_ldb->base;
+ int i;
+
+ if (imx8qxp_ldb == NULL)
+ return 0;
+
+ for (i = 0; i < LDB_CH_NUM; i++) {
+ phy_init(imx8qxp_ldb->channel[i].phy);
+
+ if (ldb->dual && i == 0)
+ phy_init(imx8qxp_ldb->channel[i].aux_phy);
+ }
+
+ imx8qxp_ldb_init_sc_misc(imx8qxp_ldb->id, ldb->dual);
+
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops imx8qxp_ldb_pm_ops = {
+ SET_LATE_SYSTEM_SLEEP_PM_OPS(imx8qxp_ldb_suspend, imx8qxp_ldb_resume)
+};
+
+static struct platform_driver imx8qxp_ldb_driver = {
+ .probe = imx8qxp_ldb_probe,
+ .remove = imx8qxp_ldb_remove,
+ .driver = {
+ .of_match_table = imx8qxp_ldb_dt_ids,
+ .name = DRIVER_NAME,
+ .pm = &imx8qxp_ldb_pm_ops,
+ },
+};
+
+module_platform_driver(imx8qxp_ldb_driver);
+
+MODULE_DESCRIPTION("i.MX8QXP LVDS driver");
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRIVER_NAME);
diff --git a/drivers/gpu/drm/imx/imx93-ldb.c b/drivers/gpu/drm/imx/imx93-ldb.c
new file mode 100644
index 000000000000..fc02a1970874
--- /dev/null
+++ b/drivers/gpu/drm/imx/imx93-ldb.c
@@ -0,0 +1,347 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2022 NXP
+ */
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/phy/phy.h>
+
+#include <drm/bridge/fsl_imx_ldb.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+
+#include "imx-drm.h"
+
+#define DRIVER_NAME "imx93-ldb"
+
+#define LDB_CH0_MODE_EN_TO_DI0 (1 << 0)
+#define LDB_CH0_MODE_EN_TO_DI1 (3 << 0)
+#define LDB_CH0_MODE_EN_MASK (3 << 0)
+#define LDB_REG_CH0_FIFO_RESET (1 << 11)
+#define LDB_REG_ASYNC_FIFO_EN (1 << 24)
+#define LDB_FIFO_THRESHOLD (4 << 25)
+
+struct imx93_ldb;
+
+struct imx93_ldb_channel {
+ struct ldb_channel base;
+ struct imx93_ldb *imx93_ldb;
+
+ struct drm_connector connector;
+ struct drm_encoder encoder;
+
+ struct phy *phy;
+ bool phy_is_on;
+
+ u32 bus_flags;
+};
+
+static inline struct imx93_ldb_channel *
+con_to_imx93_ldb_ch(struct drm_connector *c)
+{
+ return container_of(c, struct imx93_ldb_channel, connector);
+}
+
+static inline struct imx93_ldb_channel *
+enc_to_imx93_ldb_ch(struct drm_encoder *e)
+{
+ return container_of(e, struct imx93_ldb_channel, encoder);
+}
+
+struct imx93_ldb {
+ struct ldb base;
+ struct imx93_ldb_channel channel;
+ struct clk *clk_root;
+};
+
+static struct drm_encoder *
+imx93_ldb_connector_best_encoder(struct drm_connector *connector)
+{
+ struct imx93_ldb_channel *imx93_ldb_ch = con_to_imx93_ldb_ch(connector);
+
+ return &imx93_ldb_ch->encoder;
+}
+
+static void imx93_ldb_encoder_enable(struct drm_encoder *encoder)
+{
+ struct imx93_ldb_channel *imx93_ldb_ch = enc_to_imx93_ldb_ch(encoder);
+ struct imx93_ldb *imx93_ldb = imx93_ldb_ch->imx93_ldb;
+ struct ldb *ldb = &imx93_ldb->base;
+
+ clk_prepare_enable(imx93_ldb->clk_root);
+
+ ldb->ldb_ctrl &= ~LDB_CH0_MODE_EN_MASK;
+ ldb->ldb_ctrl |= LDB_CH0_MODE_EN_TO_DI0;
+
+ phy_power_on(imx93_ldb_ch->phy);
+ imx93_ldb_ch->phy_is_on = true;
+}
+
+static void
+imx93_ldb_encoder_atomic_mode_set(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *connector_state)
+{
+ struct imx93_ldb_channel *imx93_ldb_ch = enc_to_imx93_ldb_ch(encoder);
+ struct imx93_ldb *imx93_ldb = imx93_ldb_ch->imx93_ldb;
+ struct ldb *ldb = &imx93_ldb->base;
+ struct drm_display_mode *mode = &crtc_state->adjusted_mode;
+ unsigned long serial_clk;
+
+ if (mode->clock > 80000)
+ dev_warn(ldb->dev,
+ "%s: mode exceeds 80 MHz pixel clock\n", __func__);
+
+ serial_clk = mode->clock * 7000UL;
+ clk_set_rate(imx93_ldb->clk_root, serial_clk);
+}
+
+static void imx93_ldb_encoder_disable(struct drm_encoder *encoder)
+{
+ struct imx93_ldb_channel *imx93_ldb_ch = enc_to_imx93_ldb_ch(encoder);
+ struct imx93_ldb *imx93_ldb = imx93_ldb_ch->imx93_ldb;
+
+ phy_power_off(imx93_ldb_ch->phy);
+ imx93_ldb_ch->phy_is_on = false;
+
+ clk_disable_unprepare(imx93_ldb->clk_root);
+}
+
+static int
+imx93_ldb_encoder_atomic_check(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc_state);
+ struct imx93_ldb_channel *imx93_ldb_ch = enc_to_imx93_ldb_ch(encoder);
+ struct ldb_channel *ldb_ch = &imx93_ldb_ch->base;
+ struct drm_display_info *di = &conn_state->connector->display_info;
+
+ ldb_ch->bus_format = di->bus_formats[0];
+ imx_crtc_state->bus_flags = di->bus_flags;
+
+ switch (ldb_ch->bus_format) {
+ case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
+ imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB666_1X18;
+ break;
+ case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
+ case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
+ imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB888_1X24;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct drm_connector_funcs imx93_ldb_connector_funcs = {
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = imx_drm_connector_destroy,
+ .reset = drm_atomic_helper_connector_reset,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static const struct drm_connector_helper_funcs
+imx93_ldb_connector_helper_funcs = {
+ .best_encoder = imx93_ldb_connector_best_encoder,
+};
+
+static const struct drm_encoder_helper_funcs imx93_ldb_encoder_helper_funcs = {
+ .atomic_mode_set = imx93_ldb_encoder_atomic_mode_set,
+ .enable = imx93_ldb_encoder_enable,
+ .disable = imx93_ldb_encoder_disable,
+ .atomic_check = imx93_ldb_encoder_atomic_check,
+};
+
+static const struct of_device_id imx93_ldb_dt_ids[] = {
+ { .compatible = "fsl,imx93-ldb", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, imx93_ldb_dt_ids);
+
+static int
+imx93_ldb_bind(struct device *dev, struct device *master, void *data)
+{
+ struct drm_device *drm = data;
+ struct device_node *np = dev->of_node;
+ struct device_node *child;
+ struct imx93_ldb *imx93_ldb = dev_get_drvdata(dev);
+ struct ldb *ldb = &imx93_ldb->base;
+ struct imx93_ldb_channel *imx93_ldb_ch = &imx93_ldb->channel;
+ struct ldb_channel *ldb_ch = &imx93_ldb_ch->base;
+ struct drm_encoder *encoder[0];
+ int ret;
+ int i;
+
+ ret = of_get_child_count(np);
+ if (ret > 1) {
+ dev_err(dev, "invalid child/LVDS channel count: %d\n", ret);
+ return -EINVAL;
+ }
+
+ ldb->dev = dev;
+ ldb->ctrl_reg = 0x20;
+ ldb->output_port = 1;
+
+ imx93_ldb_ch->imx93_ldb = imx93_ldb;
+ ldb->channel[0] = ldb_ch;
+
+ imx93_ldb->clk_root = devm_clk_get(dev, "ldb");
+ if (IS_ERR(imx93_ldb->clk_root))
+ return PTR_ERR(imx93_ldb->clk_root);
+
+ *encoder = &imx93_ldb_ch->encoder;
+ drm_encoder_helper_add(*encoder, &imx93_ldb_encoder_helper_funcs);
+ drm_simple_encoder_init(drm, *encoder, DRM_MODE_ENCODER_LVDS);
+
+ pm_runtime_enable(dev);
+
+ ret = ldb_bind(ldb, encoder);
+ if (ret)
+ goto disable_pm_runtime;
+
+ child = of_get_next_child(np, NULL);
+ if (!child) {
+ dev_err(dev, "no child node for LVDS channel\n");
+ ret = -ENODEV;
+ goto disable_pm_runtime;
+ }
+
+ ret = of_property_read_u32(child, "reg", &i);
+ if (ret || i != 0) {
+ dev_err(dev, "invalid LVDS channel number\n");
+ ret = -EINVAL;
+ goto free_child;
+ }
+
+ if (!of_device_is_available(child)) {
+ dev_info(dev, "LVDS channel is not available\n");
+ goto free_child;
+ }
+
+ imx93_ldb_ch->phy = devm_of_phy_get(dev, child, "ldb_phy");
+ if (IS_ERR(imx93_ldb_ch->phy)) {
+ ret = PTR_ERR(imx93_ldb_ch->phy);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "failed to get LVDS channel phy: %d\n",
+ ret);
+ goto free_child;
+ }
+
+ ret = phy_init(imx93_ldb_ch->phy);
+ if (ret < 0) {
+ dev_err(dev, "failed to initialize LVDS channel phy: %d\n",
+ ret);
+ goto free_child;
+ }
+
+ if (!ldb_ch->is_valid) {
+ drm_encoder_cleanup(*encoder);
+ goto free_child;
+ }
+
+ ret = imx_drm_encoder_parse_of(drm, *encoder, child);
+ if (ret)
+ goto free_child;
+
+ return 0;
+
+free_child:
+ of_node_put(child);
+disable_pm_runtime:
+ pm_runtime_disable(dev);
+
+ return ret;
+}
+
+static void imx93_ldb_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct imx93_ldb *imx93_ldb = dev_get_drvdata(dev);
+ struct imx93_ldb_channel *imx93_ldb_ch = &imx93_ldb->channel;
+
+ if (imx93_ldb_ch->phy_is_on)
+ phy_power_off(imx93_ldb_ch->phy);
+
+ phy_exit(imx93_ldb_ch->phy);
+
+ pm_runtime_disable(dev);
+}
+
+static const struct component_ops imx93_ldb_ops = {
+ .bind = imx93_ldb_bind,
+ .unbind = imx93_ldb_unbind,
+};
+
+static int imx93_ldb_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct imx93_ldb *imx93_ldb;
+
+ imx93_ldb = devm_kzalloc(dev, sizeof(*imx93_ldb), GFP_KERNEL);
+ if (!imx93_ldb)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, imx93_ldb);
+
+ return component_add(dev, &imx93_ldb_ops);
+}
+
+static int imx93_ldb_remove(struct platform_device *pdev)
+{
+ component_del(&pdev->dev, &imx93_ldb_ops);
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int imx93_ldb_suspend(struct device *dev)
+{
+ struct imx93_ldb *imx93_ldb = dev_get_drvdata(dev);
+
+ if (!imx93_ldb)
+ return 0;
+
+ phy_exit(imx93_ldb->channel.phy);
+
+ return 0;
+}
+
+static int imx93_ldb_resume(struct device *dev)
+{
+ struct imx93_ldb *imx93_ldb = dev_get_drvdata(dev);
+
+ if (!imx93_ldb)
+ return 0;
+
+ phy_init(imx93_ldb->channel.phy);
+
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops imx93_ldb_pm_ops = {
+ SET_LATE_SYSTEM_SLEEP_PM_OPS(imx93_ldb_suspend, imx93_ldb_resume)
+};
+
+static struct platform_driver imx93_ldb_driver = {
+ .probe = imx93_ldb_probe,
+ .remove = imx93_ldb_remove,
+ .driver = {
+ .of_match_table = imx93_ldb_dt_ids,
+ .name = DRIVER_NAME,
+ .pm = &imx93_ldb_pm_ops,
+ },
+};
+
+module_platform_driver(imx93_ldb_driver);
+
+MODULE_DESCRIPTION("i.MX93 LVDS driver");
+MODULE_AUTHOR("NXP Semiconductor");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRIVER_NAME);
diff --git a/drivers/gpu/drm/imx/imx93-parallel-disp-fmt.c b/drivers/gpu/drm/imx/imx93-parallel-disp-fmt.c
new file mode 100644
index 000000000000..8f60690255d8
--- /dev/null
+++ b/drivers/gpu/drm/imx/imx93-parallel-disp-fmt.c
@@ -0,0 +1,203 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/*
+ * Copyright 2022 NXP
+ */
+
+#include <linux/component.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_of.h>
+#include <drm/drm_simple_kms_helper.h>
+
+#include "imx-drm.h"
+
+#define DISPLAY_MUX_CTRL 0x60
+#define PARALLEL_DISP_FORMAT 0x700
+
+enum imx93_pdf_format {
+ RGB888_TO_RGB888 = 0x0,
+ RGB888_TO_RGB666 = 0x1 << 8,
+ RGB565_TO_RGB565 = 0x2 << 8,
+};
+
+struct imx93_pdf {
+ struct drm_encoder encoder;
+ struct device *dev;
+ struct regmap *regmap;
+ struct drm_bridge *bridge;
+ enum imx93_pdf_format format;
+ u32 bus_format;
+};
+
+static inline struct imx93_pdf *enc_to_pdf(struct drm_encoder *e)
+{
+ return container_of(e, struct imx93_pdf, encoder);
+}
+
+static void imx93_pdf_encoder_enable(struct drm_encoder *encoder)
+{
+ struct imx93_pdf *pdf = enc_to_pdf(encoder);
+
+ regmap_update_bits(pdf->regmap, DISPLAY_MUX_CTRL, PARALLEL_DISP_FORMAT,
+ pdf->format);
+}
+
+static int
+imx93_pdf_encoder_atomic_check(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct imx93_pdf *pdf = enc_to_pdf(encoder);
+ struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc_state);
+ struct drm_display_info *di = &conn_state->connector->display_info;
+ u32 bus_format;
+
+ if (!pdf->bus_format)
+ pdf->bus_format = di->bus_formats[0];
+
+ switch (pdf->bus_format) {
+ case MEDIA_BUS_FMT_RGB565_1X16:
+ pdf->format = RGB565_TO_RGB565;
+ bus_format = pdf->bus_format;
+ break;
+ case MEDIA_BUS_FMT_RGB666_1X18:
+ pdf->format = RGB888_TO_RGB666;
+ bus_format = MEDIA_BUS_FMT_RGB888_1X24;
+ break;
+ case MEDIA_BUS_FMT_RGB888_1X24:
+ pdf->format = RGB888_TO_RGB888;
+ bus_format = pdf->bus_format;
+ break;
+ default:
+ dev_dbg(pdf->dev, "invalid bus format 0x%x\n", pdf->bus_format);
+ return -EINVAL;
+ }
+
+ imx_crtc_state->bus_format = bus_format;
+ imx_crtc_state->bus_flags = di->bus_flags;
+
+ return 0;
+}
+
+static const struct drm_encoder_helper_funcs imx93_pdf_encoder_helper_funcs = {
+ .atomic_check = imx93_pdf_encoder_atomic_check,
+ .enable = imx93_pdf_encoder_enable,
+};
+
+static int imx93_pdf_register(struct drm_device *drm, struct imx93_pdf *pdf)
+{
+ struct drm_encoder *encoder = &pdf->encoder;
+ int ret;
+
+ ret = imx_drm_encoder_parse_of(drm, encoder, pdf->dev->of_node);
+ if (ret)
+ return ret;
+
+ drm_encoder_helper_add(encoder, &imx93_pdf_encoder_helper_funcs);
+ drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_DPI);
+
+ ret = drm_bridge_attach(encoder, pdf->bridge, NULL, 0);
+ if (ret < 0) {
+ dev_err(pdf->dev, "failed to attach bridge: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int imx93_pdf_bind(struct device *dev, struct device *master, void *data)
+{
+ struct drm_device *drm = data;
+ struct device_node *np = dev->of_node;
+ struct imx93_pdf *pdf = dev_get_drvdata(dev);
+ struct drm_panel *panel;
+ const char *fmt;
+ u32 bus_format = 0;
+ int ret;
+
+ pdf->regmap = syscon_node_to_regmap(np->parent);
+ if (IS_ERR(pdf->regmap))
+ return dev_err_probe(dev, PTR_ERR(pdf->regmap),
+ "failed to get regmap\n");
+
+ pdf->dev = dev;
+
+ ret = of_property_read_string(np, "fsl,interface-pix-fmt", &fmt);
+ if (!ret) {
+ if (!strcmp(fmt, "rgb565"))
+ bus_format = MEDIA_BUS_FMT_RGB565_1X16;
+ else if (!strcmp(fmt, "rgb666"))
+ bus_format = MEDIA_BUS_FMT_RGB666_1X18;
+ else if (!strcmp(fmt, "rgb888"))
+ bus_format = MEDIA_BUS_FMT_RGB888_1X24;
+ }
+ pdf->bus_format = bus_format;
+
+ /* port@1 is the output port */
+ ret = drm_of_find_panel_or_bridge(np, 1, 0, &panel, &pdf->bridge);
+ if (ret)
+ return ret;
+
+ if (panel) {
+ pdf->bridge = devm_drm_panel_bridge_add(dev, panel);
+ if (IS_ERR(pdf->bridge)) {
+ ret = PTR_ERR(pdf->bridge);
+ dev_err(dev, "failed to add panel bridge %d\n", ret);
+ return ret;
+ }
+ }
+
+ return imx93_pdf_register(drm, pdf);
+}
+
+static const struct component_ops imx93_pdf_ops = {
+ .bind = imx93_pdf_bind,
+};
+
+static int imx93_pdf_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct imx93_pdf *pdf;
+
+ pdf = devm_kzalloc(dev, sizeof(*pdf), GFP_KERNEL);
+ if (!pdf)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, pdf);
+
+ return component_add(dev, &imx93_pdf_ops);
+}
+
+static int imx93_pdf_remove(struct platform_device *pdev)
+{
+ component_del(&pdev->dev, &imx93_pdf_ops);
+
+ return 0;
+}
+
+static const struct of_device_id imx93_pdf_dt_ids[] = {
+ { .compatible = "fsl,imx93-parallel-display-format", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx93_pdf_dt_ids);
+
+static struct platform_driver imx93_pdf_driver = {
+ .probe = imx93_pdf_probe,
+ .remove = imx93_pdf_remove,
+ .driver = {
+ .of_match_table = imx93_pdf_dt_ids,
+ .name = "imx93-parallel-display-format",
+ },
+};
+
+module_platform_driver(imx93_pdf_driver);
+
+MODULE_DESCRIPTION("i.MX93 parallel display format driver");
+MODULE_AUTHOR("NXP Semiconductor");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:imx93-parallel-display-format");
diff --git a/drivers/gpu/drm/imx/ipuv3/Kconfig b/drivers/gpu/drm/imx/ipuv3/Kconfig
new file mode 100644
index 000000000000..718da3784070
--- /dev/null
+++ b/drivers/gpu/drm/imx/ipuv3/Kconfig
@@ -0,0 +1,6 @@
+config DRM_IMX_IPUV3
+ tristate
+ depends on DRM_IMX
+ depends on IMX_IPUV3_CORE
+ default y if DRM_IMX=y
+ default m if DRM_IMX=m
diff --git a/drivers/gpu/drm/imx/ipuv3/Makefile b/drivers/gpu/drm/imx/ipuv3/Makefile
new file mode 100644
index 000000000000..11643dfd23ce
--- /dev/null
+++ b/drivers/gpu/drm/imx/ipuv3/Makefile
@@ -0,0 +1,4 @@
+ccflags-y += -I $(srctree)/$(src)/../
+
+imx-ipuv3-crtc-objs := ipuv3-crtc.o ipuv3-plane.o ipuv3-kms.o
+obj-$(CONFIG_DRM_IMX_IPUV3) += imx-ipuv3-crtc.o
diff --git a/drivers/gpu/drm/imx/ipuv3-crtc.c b/drivers/gpu/drm/imx/ipuv3/ipuv3-crtc.c
index ba5b16618c23..665204d39b30 100644
--- a/drivers/gpu/drm/imx/ipuv3-crtc.c
+++ b/drivers/gpu/drm/imx/ipuv3/ipuv3-crtc.c
@@ -25,6 +25,7 @@
#include <drm/drm_vblank.h>
#include "imx-drm.h"
+#include "ipuv3-kms.h"
#include "ipuv3-plane.h"
#define DRIVER_DESC "i.MX IPUv3 Graphics"
@@ -108,7 +109,7 @@ static void ipu_crtc_atomic_disable(struct drm_crtc *crtc,
spin_unlock_irq(&crtc->dev->event_lock);
}
-static void imx_drm_crtc_reset(struct drm_crtc *crtc)
+static void ipu_drm_crtc_reset(struct drm_crtc *crtc)
{
struct imx_crtc_state *state;
@@ -123,7 +124,7 @@ static void imx_drm_crtc_reset(struct drm_crtc *crtc)
__drm_atomic_helper_crtc_reset(crtc, &state->base);
}
-static struct drm_crtc_state *imx_drm_crtc_duplicate_state(struct drm_crtc *crtc)
+static struct drm_crtc_state *ipu_drm_crtc_duplicate_state(struct drm_crtc *crtc)
{
struct imx_crtc_state *state;
@@ -139,7 +140,7 @@ static struct drm_crtc_state *imx_drm_crtc_duplicate_state(struct drm_crtc *crtc
return &state->base;
}
-static void imx_drm_crtc_destroy_state(struct drm_crtc *crtc,
+static void ipu_drm_crtc_destroy_state(struct drm_crtc *crtc,
struct drm_crtc_state *state)
{
__drm_atomic_helper_crtc_destroy_state(state);
@@ -165,9 +166,9 @@ static void ipu_disable_vblank(struct drm_crtc *crtc)
static const struct drm_crtc_funcs ipu_crtc_funcs = {
.set_config = drm_atomic_helper_set_config,
.page_flip = drm_atomic_helper_page_flip,
- .reset = imx_drm_crtc_reset,
- .atomic_duplicate_state = imx_drm_crtc_duplicate_state,
- .atomic_destroy_state = imx_drm_crtc_destroy_state,
+ .reset = ipu_drm_crtc_reset,
+ .atomic_duplicate_state = ipu_drm_crtc_duplicate_state,
+ .atomic_destroy_state = ipu_drm_crtc_destroy_state,
.enable_vblank = ipu_enable_vblank,
.disable_vblank = ipu_disable_vblank,
};
@@ -448,10 +449,16 @@ static int ipu_drm_remove(struct platform_device *pdev)
return 0;
}
-struct platform_driver ipu_drm_driver = {
+static struct platform_driver ipu_drm_driver = {
.driver = {
.name = "imx-ipuv3-crtc",
},
.probe = ipu_drm_probe,
.remove = ipu_drm_remove,
};
+module_platform_driver(ipu_drm_driver);
+
+MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imx-ipuv3-crtc");
diff --git a/drivers/gpu/drm/imx/ipuv3/ipuv3-kms.c b/drivers/gpu/drm/imx/ipuv3/ipuv3-kms.c
new file mode 100644
index 000000000000..c36a9a2eda49
--- /dev/null
+++ b/drivers/gpu/drm/imx/ipuv3/ipuv3-kms.c
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2019 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_vblank.h>
+#include "imx-drm.h"
+#include "ipuv3-plane.h"
+
+static int ipuv3_drm_atomic_check(struct drm_device *dev,
+ struct drm_atomic_state *state)
+{
+ int ret;
+
+ ret = drm_atomic_helper_check(dev, state);
+ if (ret)
+ return ret;
+
+ /*
+ * Check modeset again in case crtc_state->mode_changed is
+ * updated in plane's ->atomic_check callback.
+ */
+ ret = drm_atomic_helper_check_modeset(dev, state);
+ if (ret)
+ return ret;
+
+ /* Assign PRG/PRE channels and check if all constrains are satisfied. */
+ ret = ipu_planes_assign_pre(dev, state);
+ if (ret)
+ return ret;
+
+ return ret;
+}
+
+const struct drm_mode_config_funcs ipuv3_drm_mode_config_funcs = {
+ .fb_create = drm_gem_fb_create,
+ .atomic_check = ipuv3_drm_atomic_check,
+ .atomic_commit = drm_atomic_helper_commit,
+};
+
+static void ipuv3_drm_atomic_commit_tail(struct drm_atomic_state *state)
+{
+ struct drm_device *dev = state->dev;
+ struct drm_plane *plane;
+ struct drm_plane_state *old_plane_state, *new_plane_state;
+ bool plane_disabling = false;
+ int i;
+
+ drm_atomic_helper_commit_modeset_disables(dev, state);
+
+ drm_atomic_helper_commit_planes(dev, state,
+ DRM_PLANE_COMMIT_ACTIVE_ONLY |
+ DRM_PLANE_COMMIT_NO_DISABLE_AFTER_MODESET);
+
+ drm_atomic_helper_commit_modeset_enables(dev, state);
+
+ for_each_oldnew_plane_in_state(state, plane, old_plane_state, new_plane_state, i) {
+ if (drm_atomic_plane_disabling(old_plane_state, new_plane_state))
+ plane_disabling = true;
+ }
+
+ /*
+ * The flip done wait is only strictly required by imx-drm if a deferred
+ * plane disable is in-flight. As the core requires blocking commits
+ * to wait for the flip it is done here unconditionally. This keeps the
+ * workitem around a bit longer than required for the majority of
+ * non-blocking commits, but we accept that for the sake of simplicity.
+ */
+ drm_atomic_helper_wait_for_flip_done(dev, state);
+
+ if (plane_disabling) {
+ for_each_old_plane_in_state(state, plane, old_plane_state, i)
+ ipu_plane_disable_deferred(plane);
+
+ }
+
+ drm_atomic_helper_commit_hw_done(state);
+}
+
+const struct drm_mode_config_helper_funcs ipuv3_drm_mode_config_helpers = {
+ .atomic_commit_tail = ipuv3_drm_atomic_commit_tail,
+};
diff --git a/drivers/gpu/drm/imx/ipuv3/ipuv3-kms.h b/drivers/gpu/drm/imx/ipuv3/ipuv3-kms.h
new file mode 100644
index 000000000000..72f80a91e33e
--- /dev/null
+++ b/drivers/gpu/drm/imx/ipuv3/ipuv3-kms.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2019 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef _IPUV3_KMS_H_
+#define _IPUV3_KMS_H_
+
+extern const struct drm_mode_config_funcs ipuv3_drm_mode_config_funcs;
+extern struct drm_mode_config_helper_funcs ipuv3_drm_mode_config_helpers;
+
+#endif
diff --git a/drivers/gpu/drm/imx/ipuv3-plane.c b/drivers/gpu/drm/imx/ipuv3/ipuv3-plane.c
index 924a66f53951..924a66f53951 100644
--- a/drivers/gpu/drm/imx/ipuv3-plane.c
+++ b/drivers/gpu/drm/imx/ipuv3/ipuv3-plane.c
diff --git a/drivers/gpu/drm/imx/ipuv3-plane.h b/drivers/gpu/drm/imx/ipuv3/ipuv3-plane.h
index 6d544e6ce63f..f646121cc751 100644
--- a/drivers/gpu/drm/imx/ipuv3-plane.h
+++ b/drivers/gpu/drm/imx/ipuv3/ipuv3-plane.h
@@ -47,4 +47,7 @@ void ipu_plane_disable(struct ipu_plane *ipu_plane, bool disable_dp_channel);
void ipu_plane_disable_deferred(struct drm_plane *plane);
bool ipu_plane_atomic_update_pending(struct drm_plane *plane);
+int ipu_planes_assign_pre(struct drm_device *dev,
+ struct drm_atomic_state *state);
+
#endif
diff --git a/drivers/gpu/drm/imx/lcdif-mux-display.c b/drivers/gpu/drm/imx/lcdif-mux-display.c
new file mode 100644
index 000000000000..7e218269407d
--- /dev/null
+++ b/drivers/gpu/drm/imx/lcdif-mux-display.c
@@ -0,0 +1,254 @@
+/*
+ * Copyright 2020 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_of.h>
+#include <drm/drm_simple_kms_helper.h>
+
+#include "imx-drm.h"
+
+#define LCDIF_MUX_MODE_MASK 0x3
+
+enum lcdif_mux_mode {
+ LCDIF_MUX_MODE_LCDIF,
+ LCDIF_MUX_MODE_PL_RGB888,
+ LCDIF_MUX_MODE_PL_RGB666,
+ LCDIF_MUX_MODE_PL_RGB565,
+};
+
+struct imx_lcdif_mux_display {
+ struct drm_encoder encoder;
+ struct device *dev;
+ struct regmap *regmap;
+ struct clk *clk_bypass_div;
+ struct clk *clk_pixel;
+ struct drm_bridge *bridge;
+ u32 bus_format;
+ enum lcdif_mux_mode mux_mode;
+};
+
+static inline struct imx_lcdif_mux_display *enc_to_lmuxd(struct drm_encoder *e)
+{
+ return container_of(e, struct imx_lcdif_mux_display, encoder);
+}
+
+static void imx_lmuxd_encoder_enable(struct drm_encoder *encoder)
+{
+ struct imx_lcdif_mux_display *lmuxd = enc_to_lmuxd(encoder);
+
+ clk_prepare_enable(lmuxd->clk_pixel);
+}
+
+static void imx_lmuxd_encoder_disable(struct drm_encoder *encoder)
+{
+ struct imx_lcdif_mux_display *lmuxd = enc_to_lmuxd(encoder);
+
+ clk_disable_unprepare(lmuxd->clk_pixel);
+}
+
+static void
+imx_lmuxd_encoder_atomic_mode_set(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct imx_lcdif_mux_display *lmuxd = enc_to_lmuxd(encoder);
+ struct drm_display_mode *mode = &crtc_state->adjusted_mode;
+ unsigned long pixel_clk = mode->clock * 1000;
+
+ clk_set_rate(lmuxd->clk_bypass_div, pixel_clk);
+ clk_set_rate(lmuxd->clk_pixel, pixel_clk);
+
+ regmap_update_bits(lmuxd->regmap,
+ 0x0, LCDIF_MUX_MODE_MASK, lmuxd->mux_mode);
+}
+
+static int
+imx_lmuxd_encoder_atomic_check(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct imx_lcdif_mux_display *lmuxd = enc_to_lmuxd(encoder);
+ struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc_state);
+ struct drm_display_info *di = &conn_state->connector->display_info;
+ u32 bus_format;
+
+ imx_crtc_state->bus_flags = di->bus_flags;
+
+ if (!lmuxd->bus_format)
+ lmuxd->bus_format = di->bus_formats[0];
+
+ switch (lmuxd->bus_format) {
+ case MEDIA_BUS_FMT_RGB565_1X16:
+ bus_format = MEDIA_BUS_FMT_RGB565_1X30_PADLO;
+ lmuxd->mux_mode = LCDIF_MUX_MODE_PL_RGB565;
+ break;
+ case MEDIA_BUS_FMT_RGB666_1X18:
+ bus_format = MEDIA_BUS_FMT_RGB666_1X30_PADLO;
+ lmuxd->mux_mode = LCDIF_MUX_MODE_PL_RGB666;
+ break;
+ case MEDIA_BUS_FMT_RGB888_1X24:
+ bus_format = MEDIA_BUS_FMT_RGB888_1X30_PADLO;
+ lmuxd->mux_mode = LCDIF_MUX_MODE_PL_RGB888;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ imx_crtc_state->bus_format = bus_format;
+
+ return 0;
+}
+
+static const struct drm_encoder_helper_funcs imx_lmuxd_encoder_helper_funcs = {
+ .enable = imx_lmuxd_encoder_enable,
+ .disable = imx_lmuxd_encoder_disable,
+ .atomic_mode_set = imx_lmuxd_encoder_atomic_mode_set,
+ .atomic_check = imx_lmuxd_encoder_atomic_check,
+};
+
+static int imx_lmuxd_register(struct drm_device *drm,
+ struct imx_lcdif_mux_display *lmuxd)
+{
+ struct drm_encoder *encoder = &lmuxd->encoder;
+ int ret;
+
+ ret = imx_drm_encoder_parse_of(drm, encoder, lmuxd->dev->of_node);
+ if (ret)
+ return ret;
+
+ drm_encoder_helper_add(encoder, &imx_lmuxd_encoder_helper_funcs);
+ drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_DPI);
+
+ ret = drm_bridge_attach(encoder, lmuxd->bridge, NULL, 0);
+ if (ret < 0) {
+ dev_err(lmuxd->dev, "failed to attach bridge: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int imx_lmuxd_bind(struct device *dev, struct device *master, void *data)
+{
+ struct drm_device *drm = data;
+ struct device_node *np = dev->of_node;
+ struct imx_lcdif_mux_display *lmuxd = dev_get_drvdata(dev);
+ struct drm_panel *panel;
+ const char *fmt;
+ u32 bus_format = 0;
+ int ret;
+
+ lmuxd->regmap =
+ syscon_regmap_lookup_by_phandle(np, "fsl,lcdif-mux-regs");
+ if (IS_ERR(lmuxd->regmap)) {
+ dev_err(dev, "failed to get lcdif mux regmap\n");
+ return PTR_ERR(lmuxd->regmap);
+ }
+
+ lmuxd->clk_bypass_div = devm_clk_get(dev, "bypass_div");
+ if (IS_ERR(lmuxd->clk_bypass_div))
+ return PTR_ERR(lmuxd->clk_bypass_div);
+
+ lmuxd->clk_pixel = devm_clk_get(dev, "pixel");
+ if (IS_ERR(lmuxd->clk_bypass_div))
+ return PTR_ERR(lmuxd->clk_bypass_div);
+
+ ret = of_property_read_string(np, "fsl,interface-pix-fmt", &fmt);
+ if (!ret) {
+ if (!strcmp(fmt, "rgb565"))
+ bus_format = MEDIA_BUS_FMT_RGB565_1X16;
+ else if (!strcmp(fmt, "rgb666"))
+ bus_format = MEDIA_BUS_FMT_RGB666_1X18;
+ else if (!strcmp(fmt, "rgb888"))
+ bus_format = MEDIA_BUS_FMT_RGB888_1X24;
+ }
+ lmuxd->bus_format = bus_format;
+
+ /* port@1 is the output port */
+ ret = drm_of_find_panel_or_bridge(np, 1, 0, &panel, &lmuxd->bridge);
+ if (ret)
+ return ret;
+
+ if (panel) {
+ lmuxd->bridge = devm_drm_panel_bridge_add(dev, panel);
+ if (IS_ERR(lmuxd->bridge)) {
+ ret = PTR_ERR(lmuxd->bridge);
+ dev_err(dev, "failed to add panel bridge %d\n", ret);
+ return ret;
+ }
+ }
+
+ lmuxd->dev = dev;
+
+ return imx_lmuxd_register(drm, lmuxd);
+}
+
+static void imx_lmuxd_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+}
+
+static const struct component_ops imx_lmuxd_ops = {
+ .bind = imx_lmuxd_bind,
+ .unbind = imx_lmuxd_unbind,
+};
+
+static int imx_lmuxd_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct imx_lcdif_mux_display *lmuxd;
+
+ lmuxd = devm_kzalloc(dev, sizeof(*lmuxd), GFP_KERNEL);
+ if (!lmuxd)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, lmuxd);
+
+ return component_add(dev, &imx_lmuxd_ops);
+}
+
+static int imx_lmuxd_remove(struct platform_device *pdev)
+{
+ component_del(&pdev->dev, &imx_lmuxd_ops);
+
+ return 0;
+}
+
+static const struct of_device_id imx_lmuxd_dt_ids[] = {
+ { .compatible = "fsl,imx-lcdif-mux-display", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_lmuxd_dt_ids);
+
+static struct platform_driver imx_lmuxd_driver = {
+ .probe = imx_lmuxd_probe,
+ .remove = imx_lmuxd_remove,
+ .driver = {
+ .of_match_table = imx_lmuxd_dt_ids,
+ .name = "imx-lcdif-mux-display",
+ },
+};
+
+module_platform_driver(imx_lmuxd_driver);
+
+MODULE_DESCRIPTION("i.MX LCDIF mux display driver");
+MODULE_AUTHOR("NXP Semiconductor");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imx-lcdif-mux-display");
diff --git a/drivers/gpu/drm/imx/lcdif/Kconfig b/drivers/gpu/drm/imx/lcdif/Kconfig
new file mode 100644
index 000000000000..4460ffacd1f7
--- /dev/null
+++ b/drivers/gpu/drm/imx/lcdif/Kconfig
@@ -0,0 +1,8 @@
+config DRM_IMX_LCDIF
+ tristate "i.MX LCDIF controller DRM driver"
+ depends on DRM_IMX
+ depends on IMX_LCDIF_CORE
+ default y if DRM_IMX=y
+ default m if DRM_IMX=m
+ help
+ enable i.MX LCDIF controller DRM driver under DRM_IMX.
diff --git a/drivers/gpu/drm/imx/lcdif/Makefile b/drivers/gpu/drm/imx/lcdif/Makefile
new file mode 100644
index 000000000000..59fe9be6d78e
--- /dev/null
+++ b/drivers/gpu/drm/imx/lcdif/Makefile
@@ -0,0 +1,4 @@
+ccflags-y += -I $(srctree)/$(src)/../
+
+imx-lcdif-crtc-objs := lcdif-crtc.o lcdif-plane.o lcdif-kms.o
+obj-$(CONFIG_DRM_IMX_LCDIF) += imx-lcdif-crtc.o
diff --git a/drivers/gpu/drm/imx/lcdif/lcdif-crtc.c b/drivers/gpu/drm/imx/lcdif/lcdif-crtc.c
new file mode 100644
index 000000000000..195555343158
--- /dev/null
+++ b/drivers/gpu/drm/imx/lcdif/lcdif-crtc.c
@@ -0,0 +1,461 @@
+/*
+ * Copyright 2018,2021-2022 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/component.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_vblank.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_self_refresh_helper.h>
+#include <video/imx-lcdif.h>
+#include <video/videomode.h>
+
+#include "imx-drm.h"
+#include "lcdif-plane.h"
+#include "lcdif-kms.h"
+
+struct lcdif_crtc {
+ struct device *dev;
+
+ struct drm_crtc base;
+ struct lcdif_plane *plane[2];
+
+ int vbl_irq;
+ u32 pix_fmt; /* drm fourcc */
+};
+
+#define to_lcdif_crtc(crtc) container_of(crtc, struct lcdif_crtc, base)
+
+static void lcdif_crtc_reset(struct drm_crtc *crtc)
+{
+ struct imx_crtc_state *state;
+
+ if (crtc->state) {
+ __drm_atomic_helper_crtc_destroy_state(crtc->state);
+
+ state = to_imx_crtc_state(crtc->state);
+ kfree(state);
+ crtc->state = NULL;
+ }
+
+ state = kzalloc(sizeof(*state), GFP_KERNEL);
+ if (!state)
+ return;
+
+ crtc->state = &state->base;
+ crtc->state->crtc = crtc;
+}
+
+static struct drm_crtc_state *lcdif_crtc_duplicate_state(struct drm_crtc *crtc)
+{
+ struct imx_crtc_state *state, *orig_state;
+
+ if (WARN_ON(!crtc->state))
+ return NULL;
+
+ state = kzalloc(sizeof(*state), GFP_KERNEL);
+ if (!state)
+ return NULL;
+
+ __drm_atomic_helper_crtc_duplicate_state(crtc, &state->base);
+
+ orig_state = to_imx_crtc_state(crtc->state);
+ state->bus_format = orig_state->bus_format;
+ state->bus_flags = orig_state->bus_flags;
+ state->di_hsync_pin = orig_state->di_hsync_pin;
+ state->di_vsync_pin = orig_state->di_vsync_pin;
+
+ return &state->base;
+}
+
+static void lcdif_crtc_destroy_state(struct drm_crtc *crtc,
+ struct drm_crtc_state *state)
+{
+ __drm_atomic_helper_crtc_destroy_state(state);
+ kfree(to_imx_crtc_state(state));
+}
+
+static int lcdif_crtc_atomic_check(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+{
+ struct lcdif_crtc *lcdif_crtc = to_lcdif_crtc(crtc);
+ struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state,
+ crtc);
+ struct drm_crtc_state *old_crtc_state = drm_atomic_get_old_crtc_state(state,
+ crtc);
+ struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc_state);
+
+ /* Don't check 'bus_format' when CRTC is
+ * going to be disabled.
+ */
+ if (!crtc_state->enable)
+ return 0;
+
+ /* For the commit that the CRTC is active
+ * without planes attached to it should be
+ * invalid.
+ */
+ if (crtc_state->active && !crtc_state->plane_mask)
+ return -EINVAL;
+
+ /* check the requested bus format can be
+ * supported by LCDIF CTRC or not
+ */
+ switch (imx_crtc_state->bus_format) {
+ case MEDIA_BUS_FMT_RGB565_1X16:
+ case MEDIA_BUS_FMT_RGB666_1X18:
+ case MEDIA_BUS_FMT_RGB888_1X24:
+ break;
+ default:
+ dev_err(lcdif_crtc->dev,
+ "unsupported bus format: %#x\n",
+ imx_crtc_state->bus_format);
+ return -EINVAL;
+ }
+
+ /*
+ * Force the connectors_changed flag of the new CRTC state to true,
+ * if the active flag of the new CRTC state is set to false in the
+ * self refresh mode. This makes it possible for relevant encoder
+ * and bridges to be disabled if the entire display pipeline needs
+ * to be disabled in the self refresh mode, e.g., the fb emulation
+ * is to be blanked.
+ */
+ if (old_crtc_state->self_refresh_active && !crtc_state->active)
+ crtc_state->connectors_changed = true;
+
+ return 0;
+}
+
+static void lcdif_crtc_atomic_begin(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+{
+ drm_crtc_vblank_on(crtc);
+
+ spin_lock_irq(&crtc->dev->event_lock);
+ if (crtc->state->event) {
+ WARN_ON(drm_crtc_vblank_get(crtc));
+ drm_crtc_arm_vblank_event(crtc, crtc->state->event);
+ crtc->state->event = NULL;
+ }
+ spin_unlock_irq(&crtc->dev->event_lock);
+}
+
+static void lcdif_crtc_atomic_flush(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+{
+ /* LCDIF doesn't have command buffer */
+ return;
+}
+
+static void lcdif_crtc_atomic_enable(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+{
+ struct lcdif_crtc *lcdif_crtc = to_lcdif_crtc(crtc);
+ struct lcdif_soc *lcdif = dev_get_drvdata(lcdif_crtc->dev->parent);
+ struct drm_display_mode *mode = &crtc->state->adjusted_mode;
+ struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc->state);
+ struct videomode vm;
+ bool use_i80 = lcdif_drm_connector_is_self_refresh_aware(state);
+
+ drm_display_mode_to_videomode(mode, &vm);
+
+ if (imx_crtc_state->bus_flags & DRM_BUS_FLAG_DE_HIGH)
+ vm.flags |= DISPLAY_FLAGS_DE_HIGH;
+ else
+ vm.flags |= DISPLAY_FLAGS_DE_LOW;
+
+ if (imx_crtc_state->bus_flags & DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE)
+ vm.flags |= DISPLAY_FLAGS_PIXDATA_POSEDGE;
+ else
+ vm.flags |= DISPLAY_FLAGS_PIXDATA_NEGEDGE;
+
+ pm_runtime_get_sync(lcdif_crtc->dev->parent);
+
+ lcdif_set_mode(lcdif, &vm, use_i80);
+
+ /* config LCDIF output bus format */
+ lcdif_set_bus_fmt(lcdif, imx_crtc_state->bus_format);
+
+ /* defer the lcdif controller enable to plane update,
+ * since until then the lcdif config is complete to
+ * enable the controller to run actually.
+ */
+}
+
+static void lcdif_crtc_atomic_disable(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+{
+ struct drm_crtc_state *old_crtc_state = drm_atomic_get_old_crtc_state(state,
+ crtc);
+ struct lcdif_crtc *lcdif_crtc = to_lcdif_crtc(crtc);
+ struct lcdif_soc *lcdif = dev_get_drvdata(lcdif_crtc->dev->parent);
+ bool use_i80 = lcdif_drm_connector_is_self_refresh_aware(state);
+
+ if (old_crtc_state->self_refresh_active)
+ return;
+
+ spin_lock_irq(&crtc->dev->event_lock);
+ if (crtc->state->event) {
+ drm_crtc_send_vblank_event(crtc, crtc->state->event);
+ crtc->state->event = NULL;
+ }
+ spin_unlock_irq(&crtc->dev->event_lock);
+
+ drm_crtc_vblank_off(crtc);
+
+ lcdif_disable_controller(lcdif, use_i80);
+
+ pm_runtime_put(lcdif_crtc->dev->parent);
+}
+
+static enum drm_mode_status lcdif_crtc_mode_valid(struct drm_crtc *crtc,
+ const struct drm_display_mode *mode)
+{
+ u8 vic;
+ long rate;
+ struct drm_display_mode *dmt, copy;
+ struct lcdif_crtc *lcdif_crtc = to_lcdif_crtc(crtc);
+ struct lcdif_soc *lcdif = dev_get_drvdata(lcdif_crtc->dev->parent);
+
+ /* check CEA-861 mode */
+ vic = drm_match_cea_mode(mode);
+ if (vic)
+ goto check_pix_clk;
+
+ /* check DMT mode */
+ dmt = drm_mode_find_dmt(crtc->dev, mode->hdisplay, mode->vdisplay,
+ drm_mode_vrefresh(mode), false);
+ if (dmt) {
+ drm_mode_copy(&copy, dmt);
+ drm_mode_destroy(crtc->dev, dmt);
+
+ if (drm_mode_equal(mode, &copy))
+ goto check_pix_clk;
+ }
+
+ return MODE_OK;
+
+check_pix_clk:
+ rate = lcdif_pix_clk_round_rate(lcdif, mode->clock * 1000);
+
+ if (rate <= 0 || rate != mode->clock * 1000)
+ return MODE_BAD;
+
+ return MODE_OK;
+}
+
+static const struct drm_crtc_helper_funcs lcdif_helper_funcs = {
+ .atomic_check = lcdif_crtc_atomic_check,
+ .atomic_begin = lcdif_crtc_atomic_begin,
+ .atomic_flush = lcdif_crtc_atomic_flush,
+ .atomic_enable = lcdif_crtc_atomic_enable,
+ .atomic_disable = lcdif_crtc_atomic_disable,
+ .mode_valid = lcdif_crtc_mode_valid,
+};
+
+static int lcdif_enable_vblank(struct drm_crtc *crtc)
+{
+ struct lcdif_crtc *lcdif_crtc = to_lcdif_crtc(crtc);
+ struct lcdif_soc *lcdif = dev_get_drvdata(lcdif_crtc->dev->parent);
+
+ lcdif_vblank_irq_enable(lcdif);
+ enable_irq(lcdif_crtc->vbl_irq);
+
+ return 0;
+}
+
+static void lcdif_disable_vblank(struct drm_crtc *crtc)
+{
+ struct lcdif_crtc *lcdif_crtc = to_lcdif_crtc(crtc);
+ struct lcdif_soc *lcdif = dev_get_drvdata(lcdif_crtc->dev->parent);
+
+ disable_irq_nosync(lcdif_crtc->vbl_irq);
+ lcdif_vblank_irq_disable(lcdif);
+}
+
+static const struct drm_crtc_funcs lcdif_crtc_funcs = {
+ .set_config = drm_atomic_helper_set_config,
+ .destroy = drm_crtc_cleanup,
+ .page_flip = drm_atomic_helper_page_flip,
+ .reset = lcdif_crtc_reset,
+ .atomic_duplicate_state = lcdif_crtc_duplicate_state,
+ .atomic_destroy_state = lcdif_crtc_destroy_state,
+ .enable_vblank = lcdif_enable_vblank,
+ .disable_vblank = lcdif_disable_vblank,
+};
+
+static irqreturn_t lcdif_crtc_vblank_irq_handler(int irq, void *dev_id)
+{
+ struct lcdif_crtc *lcdif_crtc = dev_id;
+ struct lcdif_soc *lcdif = dev_get_drvdata(lcdif_crtc->dev->parent);
+
+ drm_crtc_handle_vblank(&lcdif_crtc->base);
+
+ lcdif_vblank_irq_clear(lcdif);
+
+ return IRQ_HANDLED;
+}
+
+static int lcdif_crtc_init(struct lcdif_crtc *lcdif_crtc,
+ struct lcdif_client_platformdata *pdata,
+ struct drm_device *drm)
+{
+ int ret;
+ struct lcdif_plane *primary = lcdif_crtc->plane[0];
+ struct lcdif_soc *lcdif = dev_get_drvdata(lcdif_crtc->dev->parent);
+
+ /* Primary plane
+ * The 'possible_crtcs' of primary plane will be
+ * recalculated during the 'crtc' initialization
+ * later.
+ */
+ primary = lcdif_plane_init(drm, lcdif, 0, DRM_PLANE_TYPE_PRIMARY, 0);
+ if (IS_ERR(primary))
+ return PTR_ERR(primary);
+ lcdif_crtc->plane[0] = primary;
+
+ /* TODO: Overlay plane */
+
+ lcdif_crtc->base.port = pdata->of_node;
+ drm_crtc_helper_add(&lcdif_crtc->base, &lcdif_helper_funcs);
+ ret = drm_crtc_init_with_planes(drm, &lcdif_crtc->base,
+ &lcdif_crtc->plane[0]->base, NULL,
+ &lcdif_crtc_funcs, NULL);
+ if (ret) {
+ dev_err(lcdif_crtc->dev, "failed to init crtc\n");
+ goto primary_plane_deinit;
+ }
+
+ lcdif_crtc->vbl_irq = lcdif_vblank_irq_get(lcdif);
+ WARN_ON(lcdif_crtc->vbl_irq < 0);
+
+ ret = devm_request_irq(lcdif_crtc->dev, lcdif_crtc->vbl_irq,
+ lcdif_crtc_vblank_irq_handler, 0,
+ dev_name(lcdif_crtc->dev), lcdif_crtc);
+ if (ret) {
+ dev_err(lcdif_crtc->dev,
+ "vblank irq request failed: %d\n", ret);
+ goto primary_plane_deinit;
+ }
+
+ disable_irq(lcdif_crtc->vbl_irq);
+
+ ret = drm_self_refresh_helper_init(&lcdif_crtc->base);
+ if (ret) {
+ dev_err(lcdif_crtc->dev,
+ "failed to init self refresh helper: %d\n", ret);
+ goto primary_plane_deinit;
+ }
+
+ return 0;
+
+primary_plane_deinit:
+ lcdif_plane_deinit(drm, primary);
+
+ return ret;
+}
+
+static int lcdif_crtc_bind(struct device *dev, struct device *master,
+ void *data)
+{
+ int ret;
+ struct drm_device *drm = data;
+ struct lcdif_crtc *lcdif_crtc = dev_get_drvdata(dev);
+ struct lcdif_client_platformdata *pdata = dev->platform_data;
+
+ dev_dbg(dev, "%s: lcdif crtc bind begin\n", __func__);
+
+ ret = lcdif_crtc_init(lcdif_crtc, pdata, drm);
+ if (ret)
+ return ret;
+
+ if (!drm->mode_config.funcs)
+ drm->mode_config.funcs = &lcdif_drm_mode_config_funcs;
+
+ if (!drm->mode_config.helper_private)
+ drm->mode_config.helper_private = &lcdif_drm_mode_config_helpers;
+
+ /* limit the max width and height */
+ drm->mode_config.max_width = 1920;
+ drm->mode_config.max_height = 1920;
+
+ dev_dbg(dev, "%s: lcdif crtc bind end\n", __func__);
+
+ return 0;
+}
+
+static void lcdif_crtc_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct drm_device *drm = data;
+ struct lcdif_crtc *lcdif_crtc = dev_get_drvdata(dev);
+
+ drm_self_refresh_helper_cleanup(&lcdif_crtc->base);
+
+ lcdif_plane_deinit(drm, lcdif_crtc->plane[0]);
+}
+
+static const struct component_ops lcdif_crtc_ops = {
+ .bind = lcdif_crtc_bind,
+ .unbind = lcdif_crtc_unbind,
+};
+
+static int lcdif_crtc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct lcdif_crtc *lcdif_crtc;
+
+ dev_dbg(&pdev->dev, "%s: lcdif crtc probe begin\n", __func__);
+
+ lcdif_crtc = devm_kzalloc(dev, sizeof(*lcdif_crtc), GFP_KERNEL);
+ if (!lcdif_crtc)
+ return -ENOMEM;
+
+ lcdif_crtc->dev = dev;
+
+ if (!dev->platform_data) {
+ dev_err(dev, "no platform data\n");
+ return -EINVAL;
+ }
+
+ dev_set_drvdata(dev, lcdif_crtc);
+
+ return component_add(dev, &lcdif_crtc_ops);
+}
+
+static int lcdif_crtc_remove(struct platform_device *pdev)
+{
+ component_del(&pdev->dev, &lcdif_crtc_ops);
+
+ return 0;
+}
+
+static struct platform_driver lcdif_crtc_driver = {
+ .probe = lcdif_crtc_probe,
+ .remove = lcdif_crtc_remove,
+ .driver = {
+ .name = "imx-lcdif-crtc",
+ },
+};
+module_platform_driver(lcdif_crtc_driver);
+
+MODULE_DESCRIPTION("NXP i.MX LCDIF DRM CRTC driver");
+MODULE_AUTHOR("Fancy Fang <chen.fang@nxp.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/imx/lcdif/lcdif-kms.c b/drivers/gpu/drm/imx/lcdif/lcdif-kms.c
new file mode 100644
index 000000000000..f4c83ed8d802
--- /dev/null
+++ b/drivers/gpu/drm/imx/lcdif/lcdif-kms.c
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2018 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <drm/drm_vblank.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+
+static void lcdif_drm_atomic_commit_tail(struct drm_atomic_state *state)
+{
+ struct drm_device *dev = state->dev;
+
+ drm_atomic_helper_commit_modeset_disables(dev, state);
+
+ drm_atomic_helper_commit_modeset_enables(dev, state);
+
+ drm_atomic_helper_commit_planes(dev, state, DRM_PLANE_COMMIT_ACTIVE_ONLY);
+
+ drm_atomic_helper_commit_hw_done(state);
+
+ drm_atomic_helper_wait_for_vblanks(dev, state);
+
+ drm_atomic_helper_cleanup_planes(dev, state);
+}
+
+const struct drm_mode_config_funcs lcdif_drm_mode_config_funcs = {
+ .fb_create = drm_gem_fb_create,
+ .atomic_check = drm_atomic_helper_check,
+ .atomic_commit = drm_atomic_helper_commit,
+};
+
+struct drm_mode_config_helper_funcs lcdif_drm_mode_config_helpers = {
+ .atomic_commit_tail = lcdif_drm_atomic_commit_tail,
+};
diff --git a/drivers/gpu/drm/imx/lcdif/lcdif-kms.h b/drivers/gpu/drm/imx/lcdif/lcdif-kms.h
new file mode 100644
index 000000000000..cbe5f7f4fa3f
--- /dev/null
+++ b/drivers/gpu/drm/imx/lcdif/lcdif-kms.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2018,2021 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __LCDIF_KMS_H
+#define __LCDIF_KMS_H
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_connector.h>
+
+extern const struct drm_mode_config_funcs lcdif_drm_mode_config_funcs;
+extern struct drm_mode_config_helper_funcs lcdif_drm_mode_config_helpers;
+
+static inline bool
+lcdif_drm_connector_is_self_refresh_aware(struct drm_atomic_state *state)
+{
+ struct drm_connector *conn;
+ struct drm_connector_state *conn_state;
+ int i;
+
+ for_each_new_connector_in_state(state, conn, conn_state, i) {
+ if (conn_state->self_refresh_aware)
+ return true;
+ }
+
+ return false;
+}
+
+#endif
diff --git a/drivers/gpu/drm/imx/lcdif/lcdif-plane.c b/drivers/gpu/drm/imx/lcdif/lcdif-plane.c
new file mode 100644
index 000000000000..4a938bb15415
--- /dev/null
+++ b/drivers/gpu/drm/imx/lcdif/lcdif-plane.c
@@ -0,0 +1,261 @@
+/*
+ * Copyright 2018,2021 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <drm/drm_vblank.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_plane_helper.h>
+#include <drm/drm_rect.h>
+#include <video/imx-lcdif.h>
+
+#include "lcdif-plane.h"
+#include "lcdif-kms.h"
+
+static uint32_t lcdif_pixel_formats[] = {
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_XBGR8888,
+ DRM_FORMAT_ABGR8888,
+ DRM_FORMAT_RGBX8888,
+ DRM_FORMAT_RGBA8888,
+ DRM_FORMAT_ARGB1555,
+ DRM_FORMAT_XRGB1555,
+ DRM_FORMAT_ABGR1555,
+ DRM_FORMAT_XBGR1555,
+ DRM_FORMAT_BGR565,
+};
+
+static int lcdif_plane_atomic_check(struct drm_plane *plane,
+ struct drm_atomic_state *state)
+{
+ int ret;
+ struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state,
+ plane);
+ struct drm_plane_state *old_state = plane->state;
+ struct drm_framebuffer *fb = plane_state->fb;
+ struct drm_framebuffer *old_fb = old_state->fb;
+ struct drm_crtc_state *crtc_state;
+ struct drm_display_mode *mode;
+ struct drm_rect clip = { 0 };
+ bool use_i80;
+
+ /* 'fb' should also be NULL which has been checked in
+ * the core sanity check function 'drm_atomic_plane_check()'
+ */
+ if (!plane_state->crtc) {
+ WARN_ON(fb);
+ return 0;
+ }
+
+ /* lcdif crtc can only display from (0,0) for each plane */
+ if (plane_state->crtc_x || plane_state->crtc_y)
+ return -EINVAL;
+
+ crtc_state = drm_atomic_get_existing_crtc_state(state,
+ plane_state->crtc);
+ mode = &crtc_state->adjusted_mode;
+
+ clip.x2 = mode->hdisplay;
+ clip.y2 = mode->vdisplay;
+
+ ret = drm_atomic_helper_check_plane_state(plane_state, crtc_state,
+ DRM_PLANE_HELPER_NO_SCALING,
+ DRM_PLANE_HELPER_NO_SCALING,
+ false, true);
+
+ if (ret)
+ return ret;
+
+ if (!plane_state->visible)
+ return -EINVAL;
+
+ /* force 'mode_changed' when fb pitches or format
+ * changed, since the pitch and format related
+ * registers configuration of LCDIF can not be
+ * done when LCDIF is running and 'mode_changed'
+ * means a full modeset is required.
+ */
+ if (old_fb && likely(!crtc_state->mode_changed)) {
+ if (old_fb->pitches[0] != fb->pitches[0] ||
+ old_fb->format->format != fb->format->format)
+ crtc_state->mode_changed = true;
+ }
+
+ /* Add affected connectors to check if we use i80 mode or not. */
+ ret = drm_atomic_add_affected_connectors(state, plane_state->crtc);
+ if (ret)
+ return ret;
+
+ use_i80 = lcdif_drm_connector_is_self_refresh_aware(state);
+
+ /* Do not support cropping in i80 mode. */
+ if (use_i80 && (plane_state->src_w >> 16 != fb->width))
+ return -EINVAL;
+
+ return 0;
+}
+
+static void lcdif_plane_atomic_update(struct drm_plane *plane,
+ struct drm_atomic_state *state)
+{
+ struct lcdif_plane *lcdif_plane = to_lcdif_plane(plane);
+ struct lcdif_soc *lcdif = lcdif_plane->lcdif;
+ struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state,
+ plane);
+ struct drm_framebuffer *fb = new_plane_state->fb;
+ struct drm_gem_cma_object *gem_obj = NULL;
+ u32 fb_addr, src_off, src_w, fb_idx, cpp, stride;
+ bool crop;
+ bool use_i80 = lcdif_drm_connector_is_self_refresh_aware(state);
+
+ /* plane and crtc is disabling */
+ if (!fb)
+ return;
+
+ /* TODO: for now we just update the next buf addr
+ * and the fb pixel format, since the mode set will
+ * be done in crtc's ->enable() helper func
+ */
+ switch (plane->type) {
+ case DRM_PLANE_TYPE_PRIMARY:
+ /* TODO: only support RGB */
+ gem_obj = drm_fb_cma_get_gem_obj(fb, 0);
+ src_off = (new_plane_state->src_y >> 16) * fb->pitches[0] +
+ (new_plane_state->src_x >> 16) * fb->format->cpp[0];
+ fb_addr = gem_obj->paddr + fb->offsets[0] + src_off;
+ fb_idx = 0;
+ break;
+ default:
+ /* TODO: add overlay later */
+ return;
+ }
+
+ lcdif_set_fb_addr(lcdif, fb_idx, fb_addr, use_i80);
+
+ /* Config pixel format and horizontal cropping
+ * if CRTC needs a full modeset which needs to
+ * enable LCDIF to run at the end.
+ */
+ if (unlikely(drm_atomic_crtc_needs_modeset(new_plane_state->crtc->state))) {
+ if (plane->type == DRM_PLANE_TYPE_PRIMARY)
+ lcdif_set_pix_fmt(lcdif, fb->format->format);
+
+ cpp = fb->format->cpp[0];
+ stride = DIV_ROUND_UP(fb->pitches[0], cpp);
+
+ src_w = new_plane_state->src_w >> 16;
+ WARN_ON(src_w > fb->width);
+
+ crop = src_w != stride ? true : false;
+ lcdif_set_fb_hcrop(lcdif, src_w, stride, crop);
+
+ lcdif_enable_controller(lcdif, use_i80);
+ } else if (use_i80) {
+ lcdif_enable_controller(lcdif, use_i80);
+ }
+}
+
+static void lcdif_plane_atomic_disable(struct drm_plane *plane,
+ struct drm_atomic_state *state)
+{
+ struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state,
+ plane);
+ struct drm_framebuffer *fb = new_plane_state->fb;
+
+ WARN_ON(fb);
+
+ /* TODO: CRTC disabled has been done by CRTC helper function,
+ * so it seems that no more required, the only possible thing
+ * is to set next buf addr to 0 in CRTC
+ */
+}
+
+static const struct drm_plane_helper_funcs lcdif_plane_helper_funcs = {
+ .atomic_check = lcdif_plane_atomic_check,
+ .atomic_update = lcdif_plane_atomic_update,
+ .atomic_disable = lcdif_plane_atomic_disable,
+};
+
+static void lcdif_plane_destroy(struct drm_plane *plane)
+{
+ struct lcdif_plane *lcdif_plane = to_lcdif_plane(plane);
+
+ drm_plane_cleanup(plane);
+ kfree(lcdif_plane);
+}
+
+static const struct drm_plane_funcs lcdif_plane_funcs = {
+ .update_plane = drm_atomic_helper_update_plane,
+ .disable_plane = drm_atomic_helper_disable_plane,
+ .destroy = lcdif_plane_destroy,
+ .reset = drm_atomic_helper_plane_reset,
+ .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
+};
+
+struct lcdif_plane *lcdif_plane_init(struct drm_device *dev,
+ struct lcdif_soc *lcdif,
+ unsigned int possible_crtcs,
+ enum drm_plane_type type,
+ unsigned int zpos)
+{
+ int ret;
+ struct lcdif_plane *lcdif_plane;
+
+ /* lcdif doesn't support fb modifiers */
+ if (zpos || dev->mode_config.allow_fb_modifiers)
+ return ERR_PTR(-EINVAL);
+
+ lcdif_plane = kzalloc(sizeof(*lcdif_plane), GFP_KERNEL);
+ if (!lcdif_plane)
+ return ERR_PTR(-ENOMEM);
+
+ lcdif_plane->lcdif = lcdif;
+
+ drm_plane_helper_add(&lcdif_plane->base, &lcdif_plane_helper_funcs);
+ ret = drm_universal_plane_init(dev, &lcdif_plane->base, possible_crtcs,
+ &lcdif_plane_funcs, lcdif_pixel_formats,
+ ARRAY_SIZE(lcdif_pixel_formats), NULL,
+ type, NULL);
+ if (ret) {
+ kfree(lcdif_plane);
+ return ERR_PTR(ret);
+ }
+
+ ret = drm_plane_create_zpos_immutable_property(&lcdif_plane->base, zpos);
+ if (ret) {
+ kfree(lcdif_plane);
+ return ERR_PTR(ret);
+ }
+
+ return lcdif_plane;
+}
+
+void lcdif_plane_deinit(struct drm_device *dev,
+ struct lcdif_plane *lcdif_plane)
+{
+ struct drm_plane *plane = &lcdif_plane->base;
+
+ if (plane->zpos_property)
+ drm_property_destroy(dev, plane->zpos_property);
+
+ lcdif_plane_destroy(plane);
+}
diff --git a/drivers/gpu/drm/imx/lcdif/lcdif-plane.h b/drivers/gpu/drm/imx/lcdif/lcdif-plane.h
new file mode 100644
index 000000000000..acd7aead606a
--- /dev/null
+++ b/drivers/gpu/drm/imx/lcdif/lcdif-plane.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2018 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __LCDIF_PLANE_H
+#define __LCDIF_PLANE_H
+
+#include <drm/drm_plane.h>
+#include <video/imx-lcdif.h>
+
+struct lcdif_plane {
+ struct drm_plane base;
+ struct lcdif_soc *lcdif;
+};
+
+#define to_lcdif_plane(plane) container_of(plane, struct lcdif_plane, base)
+
+struct lcdif_plane *lcdif_plane_init(struct drm_device *drm,
+ struct lcdif_soc *lcdif,
+ unsigned int possible_crtcs,
+ enum drm_plane_type type,
+ unsigned int zpos);
+
+void lcdif_plane_deinit(struct drm_device *dev,
+ struct lcdif_plane *lcdif_plane);
+
+#endif
diff --git a/drivers/gpu/drm/imx/lcdifv3/Kconfig b/drivers/gpu/drm/imx/lcdifv3/Kconfig
new file mode 100644
index 000000000000..6d64c29778b9
--- /dev/null
+++ b/drivers/gpu/drm/imx/lcdifv3/Kconfig
@@ -0,0 +1,8 @@
+config DRM_IMX_LCDIFV3
+ tristate "i.MX LCDIFV3 controller DRM driver"
+ depends on DRM_IMX
+ depends on IMX_LCDIFV3_CORE
+ default y if DRM_IMX=y
+ default m if DRM_IMX=m
+ help
+ enable i.MX LCDIFV3 controller DRM driver under DRM_IMX.
diff --git a/drivers/gpu/drm/imx/lcdifv3/Makefile b/drivers/gpu/drm/imx/lcdifv3/Makefile
new file mode 100644
index 000000000000..2f2b91078dba
--- /dev/null
+++ b/drivers/gpu/drm/imx/lcdifv3/Makefile
@@ -0,0 +1,4 @@
+ccflags-y += -I $(srctree)/$(src)/../
+
+imx-lcdifv3-crtc-objs := lcdifv3-crtc.o lcdifv3-plane.o lcdifv3-kms.o
+obj-$(CONFIG_DRM_IMX_LCDIFV3) += imx-lcdifv3-crtc.o
diff --git a/drivers/gpu/drm/imx/lcdifv3/lcdifv3-crtc.c b/drivers/gpu/drm/imx/lcdifv3/lcdifv3-crtc.c
new file mode 100644
index 000000000000..5a7133743850
--- /dev/null
+++ b/drivers/gpu/drm/imx/lcdifv3/lcdifv3-crtc.c
@@ -0,0 +1,421 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2019,2020,2022 NXP
+ */
+
+#include <linux/component.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_vblank.h>
+#include <video/imx-lcdifv3.h>
+#include <video/videomode.h>
+
+#include "imx-drm.h"
+#include "lcdifv3-plane.h"
+#include "lcdifv3-kms.h"
+
+struct lcdifv3_crtc {
+ struct device *dev;
+
+ struct drm_crtc base;
+ struct lcdifv3_plane *plane[2];
+
+ int vbl_irq;
+ u32 pix_fmt; /* drm fourcc */
+};
+
+#define to_lcdifv3_crtc(crtc) container_of(crtc, struct lcdifv3_crtc, base)
+
+static void lcdifv3_crtc_reset(struct drm_crtc *crtc)
+{
+ struct imx_crtc_state *state;
+
+ if (crtc->state) {
+ __drm_atomic_helper_crtc_destroy_state(crtc->state);
+
+ state = to_imx_crtc_state(crtc->state);
+ kfree(state);
+ crtc->state = NULL;
+ }
+
+ state = kzalloc(sizeof(*state), GFP_KERNEL);
+ if (!state)
+ return;
+
+ crtc->state = &state->base;
+ crtc->state->crtc = crtc;
+}
+
+static struct drm_crtc_state *lcdifv3_crtc_duplicate_state(struct drm_crtc *crtc)
+{
+ struct imx_crtc_state *state, *orig_state;
+
+ if (WARN_ON(!crtc->state))
+ return NULL;
+
+ state = kzalloc(sizeof(*state), GFP_KERNEL);
+ if (!state)
+ return NULL;
+
+ __drm_atomic_helper_crtc_duplicate_state(crtc, &state->base);
+
+ orig_state = to_imx_crtc_state(crtc->state);
+ state->bus_format = orig_state->bus_format;
+ state->bus_flags = orig_state->bus_flags;
+ state->di_hsync_pin = orig_state->di_hsync_pin;
+ state->di_vsync_pin = orig_state->di_vsync_pin;
+
+ return &state->base;
+}
+
+static void lcdifv3_crtc_destroy_state(struct drm_crtc *crtc,
+ struct drm_crtc_state *state)
+{
+ __drm_atomic_helper_crtc_destroy_state(state);
+ kfree(to_imx_crtc_state(state));
+}
+
+static int lcdifv3_crtc_atomic_check(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+{
+ struct lcdifv3_crtc *lcdifv3_crtc = to_lcdifv3_crtc(crtc);
+ struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state,
+ crtc);
+ struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc_state);
+
+ /* Don't check 'bus_format' when CRTC is
+ * going to be disabled.
+ */
+ if (!crtc_state->enable)
+ return 0;
+
+ /* For the commit that the CRTC is active
+ * without planes attached to it should be
+ * invalid.
+ */
+ if (crtc_state->active && !crtc_state->plane_mask)
+ return -EINVAL;
+
+ /* check the requested bus format can be
+ * supported by LCDIF CTRC or not
+ */
+ switch (imx_crtc_state->bus_format) {
+ case MEDIA_BUS_FMT_RGB565_1X16:
+ case MEDIA_BUS_FMT_RGB666_1X18:
+ case MEDIA_BUS_FMT_RGB888_1X24:
+ break;
+ default:
+ dev_err(lcdifv3_crtc->dev,
+ "unsupported bus format: %#x\n",
+ imx_crtc_state->bus_format);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void lcdifv3_crtc_atomic_begin(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+{
+ drm_crtc_vblank_on(crtc);
+
+ spin_lock_irq(&crtc->dev->event_lock);
+ if (crtc->state->event) {
+ WARN_ON(drm_crtc_vblank_get(crtc));
+ drm_crtc_arm_vblank_event(crtc, crtc->state->event);
+ crtc->state->event = NULL;
+ }
+ spin_unlock_irq(&crtc->dev->event_lock);
+}
+
+static void lcdifv3_crtc_atomic_flush(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+{
+ struct lcdifv3_crtc *lcdifv3_crtc = to_lcdifv3_crtc(crtc);
+ struct lcdifv3_soc *lcdifv3 = dev_get_drvdata(lcdifv3_crtc->dev->parent);
+
+ /* kick shadow load for plane config */
+ lcdifv3_en_shadow_load(lcdifv3);
+}
+
+static void lcdifv3_crtc_atomic_enable(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+{
+ struct lcdifv3_crtc *lcdifv3_crtc = to_lcdifv3_crtc(crtc);
+ struct lcdifv3_soc *lcdifv3 = dev_get_drvdata(lcdifv3_crtc->dev->parent);
+ struct drm_display_mode *mode = &crtc->state->adjusted_mode;
+ struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc->state);
+ struct videomode vm;
+
+ drm_display_mode_to_videomode(mode, &vm);
+
+ if (imx_crtc_state->bus_flags & DRM_BUS_FLAG_DE_HIGH)
+ vm.flags |= DISPLAY_FLAGS_DE_HIGH;
+ else
+ vm.flags |= DISPLAY_FLAGS_DE_LOW;
+
+ if (imx_crtc_state->bus_flags & DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE)
+ vm.flags |= DISPLAY_FLAGS_PIXDATA_POSEDGE;
+ else
+ vm.flags |= DISPLAY_FLAGS_PIXDATA_NEGEDGE;
+
+ pm_runtime_get_sync(lcdifv3_crtc->dev->parent);
+
+ lcdifv3_set_mode(lcdifv3, &vm);
+
+ /* config LCDIF output bus format */
+ lcdifv3_set_bus_fmt(lcdifv3, imx_crtc_state->bus_format);
+
+ /* run LCDIFv3 */
+ lcdifv3_enable_controller(lcdifv3);
+}
+
+static void lcdifv3_crtc_atomic_disable(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+{
+ struct lcdifv3_crtc *lcdifv3_crtc = to_lcdifv3_crtc(crtc);
+ struct lcdifv3_soc *lcdifv3 = dev_get_drvdata(lcdifv3_crtc->dev->parent);
+
+ spin_lock_irq(&crtc->dev->event_lock);
+ if (crtc->state->event) {
+ drm_crtc_send_vblank_event(crtc, crtc->state->event);
+ crtc->state->event = NULL;
+ }
+ spin_unlock_irq(&crtc->dev->event_lock);
+
+ drm_crtc_vblank_off(crtc);
+
+ lcdifv3_disable_controller(lcdifv3);
+
+ pm_runtime_put(lcdifv3_crtc->dev->parent);
+}
+
+static enum drm_mode_status lcdifv3_crtc_mode_valid(struct drm_crtc * crtc,
+ const struct drm_display_mode *mode)
+{
+ u8 vic;
+ long rounded_rate;
+ unsigned long pclk_rate;
+ struct drm_display_mode *dmt, copy;
+ struct lcdifv3_crtc *lcdifv3_crtc = to_lcdifv3_crtc(crtc);
+ struct lcdifv3_soc *lcdifv3 = dev_get_drvdata(lcdifv3_crtc->dev->parent);
+
+ /* check CEA-861 mode */
+ vic = drm_match_cea_mode(mode);
+ if (vic)
+ goto check_pix_clk;
+
+ /* check DMT mode */
+ dmt = drm_mode_find_dmt(crtc->dev, mode->hdisplay, mode->vdisplay,
+ drm_mode_vrefresh(mode), false);
+ if (dmt) {
+ drm_mode_copy(&copy, dmt);
+ drm_mode_destroy(crtc->dev, dmt);
+
+ if (drm_mode_equal(mode, &copy))
+ goto check_pix_clk;
+ }
+
+ return MODE_OK;
+
+check_pix_clk:
+ pclk_rate = mode->clock * 1000;
+
+ rounded_rate = lcdifv3_pix_clk_round_rate(lcdifv3, pclk_rate);
+
+ if (rounded_rate <= 0)
+ return MODE_BAD;
+
+ /* allow +/-0.5% HDMI pixel clock rate shift */
+ if (rounded_rate < pclk_rate * 995 / 1000 ||
+ rounded_rate > pclk_rate * 1005 / 1000)
+ return MODE_BAD;
+
+ return MODE_OK;
+}
+
+static const struct drm_crtc_helper_funcs lcdifv3_helper_funcs = {
+ .atomic_check = lcdifv3_crtc_atomic_check,
+ .atomic_begin = lcdifv3_crtc_atomic_begin,
+ .atomic_flush = lcdifv3_crtc_atomic_flush,
+ .atomic_enable = lcdifv3_crtc_atomic_enable,
+ .atomic_disable = lcdifv3_crtc_atomic_disable,
+ .mode_valid = lcdifv3_crtc_mode_valid,
+};
+
+static int lcdifv3_enable_vblank(struct drm_crtc *crtc)
+{
+ struct lcdifv3_crtc *lcdifv3_crtc = to_lcdifv3_crtc(crtc);
+ struct lcdifv3_soc *lcdifv3 = dev_get_drvdata(lcdifv3_crtc->dev->parent);
+
+ lcdifv3_vblank_irq_enable(lcdifv3);
+ enable_irq(lcdifv3_crtc->vbl_irq);
+
+ return 0;
+}
+
+static void lcdifv3_disable_vblank(struct drm_crtc *crtc)
+{
+ struct lcdifv3_crtc *lcdifv3_crtc = to_lcdifv3_crtc(crtc);
+ struct lcdifv3_soc *lcdifv3 = dev_get_drvdata(lcdifv3_crtc->dev->parent);
+
+ disable_irq_nosync(lcdifv3_crtc->vbl_irq);
+ lcdifv3_vblank_irq_disable(lcdifv3);
+}
+
+static const struct drm_crtc_funcs lcdifv3_crtc_funcs = {
+ .set_config = drm_atomic_helper_set_config,
+ .destroy = drm_crtc_cleanup,
+ .page_flip = drm_atomic_helper_page_flip,
+ .reset = lcdifv3_crtc_reset,
+ .atomic_duplicate_state = lcdifv3_crtc_duplicate_state,
+ .atomic_destroy_state = lcdifv3_crtc_destroy_state,
+ .enable_vblank = lcdifv3_enable_vblank,
+ .disable_vblank = lcdifv3_disable_vblank,
+};
+
+static irqreturn_t lcdifv3_crtc_vblank_irq_handler(int irq, void *dev_id)
+{
+ struct lcdifv3_crtc *lcdifv3_crtc = dev_id;
+ struct lcdifv3_soc *lcdifv3 = dev_get_drvdata(lcdifv3_crtc->dev->parent);
+
+ drm_crtc_handle_vblank(&lcdifv3_crtc->base);
+
+ lcdifv3_vblank_irq_clear(lcdifv3);
+
+ return IRQ_HANDLED;
+}
+
+static int lcdifv3_crtc_init(struct lcdifv3_crtc *lcdifv3_crtc,
+ struct lcdifv3_client_platformdata *pdata,
+ struct drm_device *drm)
+{
+ int ret;
+ struct lcdifv3_plane *primary = lcdifv3_crtc->plane[0];
+ struct lcdifv3_soc *lcdifv3 = dev_get_drvdata(lcdifv3_crtc->dev->parent);
+
+ /* Primary plane
+ * The 'possible_crtcs' of primary plane will be
+ * recalculated during the 'crtc' initialization
+ * later.
+ */
+ primary = lcdifv3_plane_init(drm, lcdifv3, 0, DRM_PLANE_TYPE_PRIMARY, 0);
+ if (IS_ERR(primary))
+ return PTR_ERR(primary);
+ lcdifv3_crtc->plane[0] = primary;
+
+ /* TODO: Overlay plane */
+
+ lcdifv3_crtc->base.port = pdata->of_node;
+ drm_crtc_helper_add(&lcdifv3_crtc->base, &lcdifv3_helper_funcs);
+ ret = drm_crtc_init_with_planes(drm, &lcdifv3_crtc->base,
+ &lcdifv3_crtc->plane[0]->base, NULL,
+ &lcdifv3_crtc_funcs, NULL);
+ if (ret) {
+ dev_err(lcdifv3_crtc->dev, "failed to init crtc\n");
+ return ret;
+ }
+
+ lcdifv3_crtc->vbl_irq = lcdifv3_vblank_irq_get(lcdifv3);
+ WARN_ON(lcdifv3_crtc->vbl_irq < 0);
+
+ ret = devm_request_irq(lcdifv3_crtc->dev, lcdifv3_crtc->vbl_irq,
+ lcdifv3_crtc_vblank_irq_handler, 0,
+ dev_name(lcdifv3_crtc->dev), lcdifv3_crtc);
+ if (ret) {
+ dev_err(lcdifv3_crtc->dev,
+ "vblank irq request failed: %d\n", ret);
+ return ret;
+ }
+
+ disable_irq(lcdifv3_crtc->vbl_irq);
+
+ return 0;
+}
+
+static int lcdifv3_crtc_bind(struct device *dev, struct device *master,
+ void *data)
+{
+ int ret;
+ struct drm_device *drm = data;
+ struct lcdifv3_crtc *lcdifv3_crtc = dev_get_drvdata(dev);
+ struct lcdifv3_client_platformdata *pdata = dev->platform_data;
+
+ dev_dbg(dev, "%s: lcdifv3 crtc bind begin\n", __func__);
+
+ ret = lcdifv3_crtc_init(lcdifv3_crtc, pdata, drm);
+ if (ret)
+ return ret;
+
+ if (!drm->mode_config.funcs)
+ drm->mode_config.funcs = &lcdifv3_drm_mode_config_funcs;
+
+ if (!drm->mode_config.helper_private)
+ drm->mode_config.helper_private = &lcdifv3_drm_mode_config_helpers;
+
+ /* limit the max width and height */
+ drm->mode_config.max_width = 4096;
+ drm->mode_config.max_height = 4096;
+
+ dev_dbg(dev, "%s: lcdifv3 crtc bind end\n", __func__);
+
+ return 0;
+}
+
+static void lcdifv3_crtc_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ /* No special to be done */
+}
+
+static const struct component_ops lcdifv3_crtc_ops = {
+ .bind = lcdifv3_crtc_bind,
+ .unbind = lcdifv3_crtc_unbind,
+};
+
+static int lcdifv3_crtc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct lcdifv3_crtc *lcdifv3_crtc;
+
+ dev_dbg(&pdev->dev, "%s: lcdifv3 crtc probe begin\n", __func__);
+
+ if (!dev->platform_data) {
+ dev_err(dev, "no platform data\n");
+ return -EINVAL;
+ }
+
+ lcdifv3_crtc = devm_kzalloc(dev, sizeof(*lcdifv3_crtc), GFP_KERNEL);
+ if (!lcdifv3_crtc)
+ return -ENOMEM;
+
+ lcdifv3_crtc->dev = dev;
+ dev_set_drvdata(dev, lcdifv3_crtc);
+
+ return component_add(dev, &lcdifv3_crtc_ops);
+}
+
+static int lcdifv3_crtc_remove(struct platform_device *pdev)
+{
+ component_del(&pdev->dev, &lcdifv3_crtc_ops);
+
+ return 0;
+}
+
+static struct platform_driver lcdifv3_crtc_driver = {
+ .probe = lcdifv3_crtc_probe,
+ .remove = lcdifv3_crtc_remove,
+ .driver = {
+ .name = "imx-lcdifv3-crtc",
+ },
+};
+module_platform_driver(lcdifv3_crtc_driver);
+
+MODULE_DESCRIPTION("NXP i.MX LCDIFV3 DRM CRTC driver");
+MODULE_AUTHOR("Fancy Fang <chen.fang@nxp.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/imx/lcdifv3/lcdifv3-kms.c b/drivers/gpu/drm/imx/lcdifv3/lcdifv3-kms.c
new file mode 100644
index 000000000000..3d08a3215d53
--- /dev/null
+++ b/drivers/gpu/drm/imx/lcdifv3/lcdifv3-kms.c
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2019 NXP
+ */
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+
+static void lcdifv3_drm_atomic_commit_tail(struct drm_atomic_state *state)
+{
+ struct drm_device *dev = state->dev;
+
+ drm_atomic_helper_commit_modeset_disables(dev, state);
+
+ drm_atomic_helper_commit_modeset_enables(dev, state);
+
+ drm_atomic_helper_commit_planes(dev, state, DRM_PLANE_COMMIT_ACTIVE_ONLY);
+
+ drm_atomic_helper_commit_hw_done(state);
+
+ drm_atomic_helper_wait_for_vblanks(dev, state);
+
+ drm_atomic_helper_cleanup_planes(dev, state);
+}
+
+const struct drm_mode_config_funcs lcdifv3_drm_mode_config_funcs = {
+ .fb_create = drm_gem_fb_create,
+ .atomic_check = drm_atomic_helper_check,
+ .atomic_commit = drm_atomic_helper_commit,
+};
+
+struct drm_mode_config_helper_funcs lcdifv3_drm_mode_config_helpers = {
+ .atomic_commit_tail = lcdifv3_drm_atomic_commit_tail,
+};
diff --git a/drivers/gpu/drm/imx/lcdifv3/lcdifv3-kms.h b/drivers/gpu/drm/imx/lcdifv3/lcdifv3-kms.h
new file mode 100644
index 000000000000..9a7caf4b0c2d
--- /dev/null
+++ b/drivers/gpu/drm/imx/lcdifv3/lcdifv3-kms.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2019 NXP
+ */
+
+#ifndef __LCDIFV3_KMS_H
+#define __LCDIFV3_KMS_H
+
+extern const struct drm_mode_config_funcs lcdifv3_drm_mode_config_funcs;
+extern struct drm_mode_config_helper_funcs lcdifv3_drm_mode_config_helpers;
+
+#endif
diff --git a/drivers/gpu/drm/imx/lcdifv3/lcdifv3-plane.c b/drivers/gpu/drm/imx/lcdifv3/lcdifv3-plane.c
new file mode 100644
index 000000000000..6f8425333ee0
--- /dev/null
+++ b/drivers/gpu/drm/imx/lcdifv3/lcdifv3-plane.c
@@ -0,0 +1,207 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2019,2020 NXP
+ */
+
+#include <linux/module.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_plane_helper.h>
+#include <drm/drm_rect.h>
+#include <drm/drm_vblank.h>
+#include <video/imx-lcdifv3.h>
+
+#include "lcdifv3-plane.h"
+
+static uint32_t lcdifv3_pixel_formats[] = {
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_XBGR8888,
+ DRM_FORMAT_ABGR8888,
+ DRM_FORMAT_ARGB1555,
+ DRM_FORMAT_XRGB1555,
+};
+
+static int lcdifv3_plane_atomic_check(struct drm_plane *plane,
+ struct drm_atomic_state *state)
+{
+ int ret;
+ struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state,
+ plane);
+ struct drm_plane_state *old_state = plane->state;
+ struct drm_framebuffer *fb = plane_state->fb;
+ struct drm_framebuffer *old_fb = old_state->fb;
+ struct drm_crtc_state *crtc_state;
+ struct drm_display_mode *mode;
+
+ /* 'fb' should also be NULL which has been checked in
+ * the core sanity check function 'drm_atomic_plane_check()'
+ */
+ if (!plane_state->crtc) {
+ WARN_ON(fb);
+ return 0;
+ }
+
+ /* lcdifv3 crtc can only display from (0,0) for each plane */
+ if (plane_state->crtc_x || plane_state->crtc_y)
+ return -EINVAL;
+
+ crtc_state = drm_atomic_get_existing_crtc_state(state,
+ plane_state->crtc);
+ mode = &crtc_state->adjusted_mode;
+
+ ret = drm_atomic_helper_check_plane_state(plane_state, crtc_state,
+ DRM_PLANE_HELPER_NO_SCALING,
+ DRM_PLANE_HELPER_NO_SCALING,
+ false, true);
+ if (ret)
+ return ret;
+
+ if (!plane_state->visible)
+ return -EINVAL;
+
+ /* force 'mode_changed' when fb pitches changed, since
+ * the pitch related registers configuration of LCDIF
+ * can not be done when LCDIF is running.
+ */
+ if (old_fb && likely(!crtc_state->mode_changed)) {
+ if (old_fb->pitches[0] != fb->pitches[0])
+ crtc_state->mode_changed = true;
+ }
+
+ return 0;
+}
+
+static void lcdifv3_plane_atomic_update(struct drm_plane *plane,
+ struct drm_atomic_state *state)
+{
+ struct lcdifv3_plane *lcdifv3_plane = to_lcdifv3_plane(plane);
+ struct lcdifv3_soc *lcdifv3 = lcdifv3_plane->lcdifv3;
+ struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state,
+ plane);
+ struct drm_framebuffer *fb = new_plane_state->fb;
+ struct drm_gem_cma_object *gem_obj = NULL;
+ u32 fb_addr, src_off, src_w, fb_idx, cpp, stride;
+ bool crop;
+
+ /* plane and crtc is disabling */
+ if (!fb)
+ return;
+
+ /* TODO: for now we just update the next buf addr
+ * and the fb pixel format, since the mode set will
+ * be done in crtc's ->enable() helper func
+ */
+ if (plane->type == DRM_PLANE_TYPE_PRIMARY)
+ lcdifv3_set_pix_fmt(lcdifv3, fb->format->format);
+
+ switch (plane->type) {
+ case DRM_PLANE_TYPE_PRIMARY:
+ /* TODO: only support RGB */
+ gem_obj = drm_fb_cma_get_gem_obj(fb, 0);
+ src_off = (new_plane_state->src_y >> 16) * fb->pitches[0] +
+ (new_plane_state->src_x >> 16) * fb->format->cpp[0];
+ fb_addr = gem_obj->paddr + fb->offsets[0] + src_off;
+ fb_idx = 0;
+ break;
+ default:
+ /* TODO: add overlay later */
+ return;
+ }
+
+ lcdifv3_set_fb_addr(lcdifv3, fb_idx, fb_addr);
+
+ /* config horizontal cropping if crtc needs modeset */
+ if (unlikely(drm_atomic_crtc_needs_modeset(new_plane_state->crtc->state))) {
+ cpp = fb->format->cpp[0];
+ stride = DIV_ROUND_UP(fb->pitches[0], cpp);
+
+ src_w = new_plane_state->src_w >> 16;
+ WARN_ON(src_w > fb->width);
+
+ crop = src_w != stride ? true : false;
+ lcdifv3_set_fb_hcrop(lcdifv3, src_w, fb->pitches[0], crop);
+ }
+}
+
+static void lcdifv3_plane_atomic_disable(struct drm_plane *plane,
+ struct drm_atomic_state *state)
+{
+ struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state,
+ plane);
+ struct drm_framebuffer *fb = new_plane_state->fb;
+
+ WARN_ON(fb);
+
+ /* TODO: CRTC disabled has been done by CRTC helper function,
+ * so it seems that no more required, the only possible thing
+ * is to set next buf addr to 0 in CRTC
+ */
+}
+
+static const struct drm_plane_helper_funcs lcdifv3_plane_helper_funcs = {
+ .atomic_check = lcdifv3_plane_atomic_check,
+ .atomic_update = lcdifv3_plane_atomic_update,
+ .atomic_disable = lcdifv3_plane_atomic_disable,
+};
+
+static void lcdifv3_plane_destroy(struct drm_plane *plane)
+{
+ struct lcdifv3_plane *lcdifv3_plane = to_lcdifv3_plane(plane);
+
+ drm_plane_cleanup(plane);
+ kfree(lcdifv3_plane);
+}
+
+static const struct drm_plane_funcs lcdifv3_plane_funcs = {
+ .update_plane = drm_atomic_helper_update_plane,
+ .disable_plane = drm_atomic_helper_disable_plane,
+ .destroy = lcdifv3_plane_destroy,
+ .reset = drm_atomic_helper_plane_reset,
+ .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
+};
+
+struct lcdifv3_plane *lcdifv3_plane_init(struct drm_device *dev,
+ struct lcdifv3_soc *lcdifv3,
+ unsigned int possible_crtcs,
+ enum drm_plane_type type,
+ unsigned int zpos)
+{
+ int ret;
+ struct lcdifv3_plane *lcdifv3_plane;
+
+ /* lcdifv3 doesn't support fb modifiers */
+ if (zpos || dev->mode_config.allow_fb_modifiers)
+ return ERR_PTR(-EINVAL);
+
+ lcdifv3_plane = kzalloc(sizeof(*lcdifv3_plane), GFP_KERNEL);
+ if (!lcdifv3_plane)
+ return ERR_PTR(-ENOMEM);
+
+ lcdifv3_plane->lcdifv3 = lcdifv3;
+
+ drm_plane_helper_add(&lcdifv3_plane->base, &lcdifv3_plane_helper_funcs);
+ ret = drm_universal_plane_init(dev, &lcdifv3_plane->base, possible_crtcs,
+ &lcdifv3_plane_funcs, lcdifv3_pixel_formats,
+ ARRAY_SIZE(lcdifv3_pixel_formats), NULL,
+ type, NULL);
+ if (ret) {
+ kfree(lcdifv3_plane);
+ return ERR_PTR(ret);
+ }
+
+ ret = drm_plane_create_zpos_immutable_property(&lcdifv3_plane->base, zpos);
+ if (ret) {
+ kfree(lcdifv3_plane);
+ return ERR_PTR(ret);
+ }
+
+ return lcdifv3_plane;
+}
diff --git a/drivers/gpu/drm/imx/lcdifv3/lcdifv3-plane.h b/drivers/gpu/drm/imx/lcdifv3/lcdifv3-plane.h
new file mode 100644
index 000000000000..437b7d97e69f
--- /dev/null
+++ b/drivers/gpu/drm/imx/lcdifv3/lcdifv3-plane.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2019,2020 NXP
+ */
+
+#ifndef __LCDIFV3_PLANE_H
+#define __LCDIFV3_PLANE_H
+
+#include <drm/drm_plane.h>
+#include <video/imx-lcdifv3.h>
+
+struct lcdifv3_plane {
+ struct drm_plane base;
+ struct lcdifv3_soc *lcdifv3;
+};
+
+#define to_lcdifv3_plane(plane) container_of(plane, struct lcdifv3_plane, base)
+
+struct lcdifv3_plane *lcdifv3_plane_init(struct drm_device *drm,
+ struct lcdifv3_soc *lcdifv3,
+ unsigned int possible_crtcs,
+ enum drm_plane_type type,
+ unsigned int zpos);
+#endif
diff --git a/drivers/gpu/drm/imx/mhdp/Kconfig b/drivers/gpu/drm/imx/mhdp/Kconfig
new file mode 100644
index 000000000000..225ccc3638a8
--- /dev/null
+++ b/drivers/gpu/drm/imx/mhdp/Kconfig
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config DRM_IMX_CDNS_MHDP
+ tristate "NXP i.MX MX8 DRM HDMI/DP"
+ select DRM_CDNS_MHDP
+ select DRM_CDNS_DP
+ select DRM_CDNS_HDMI
+ select DRM_CDNS_AUDIO
+ select DRM_CDNS_HDMI_HDCP
+ depends on DRM_IMX
+ help
+ Choose this if you want to use HDMI on i.MX8.
diff --git a/drivers/gpu/drm/imx/mhdp/Makefile b/drivers/gpu/drm/imx/mhdp/Makefile
new file mode 100644
index 000000000000..235fa2d515e9
--- /dev/null
+++ b/drivers/gpu/drm/imx/mhdp/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+
+cdns_mhdp_imx-objs := cdns-mhdp-imxdrv.o cdns-mhdp-dp-phy.o \
+ cdns-mhdp-hdmi-phy.o cdns-mhdp-imx8qm.o cdns-mhdp-ls1028a.o
+obj-$(CONFIG_DRM_IMX_CDNS_MHDP) += cdns_mhdp_imx.o
diff --git a/drivers/gpu/drm/imx/mhdp/cdns-mhdp-dp-phy.c b/drivers/gpu/drm/imx/mhdp/cdns-mhdp-dp-phy.c
new file mode 100644
index 000000000000..190bd25f5cbb
--- /dev/null
+++ b/drivers/gpu/drm/imx/mhdp/cdns-mhdp-dp-phy.c
@@ -0,0 +1,534 @@
+/*
+ * Cadence Display Port Interface (DP) PHY driver
+ *
+ * Copyright 2019 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+#include <linux/clk.h>
+#include <linux/kernel.h>
+#include <drm/drm_print.h>
+#include <drm/drm_dp_helper.h>
+#include <drm/bridge/cdns-mhdp.h>
+#include "cdns-mhdp-phy.h"
+
+enum dp_link_rate {
+ RATE_1_6 = 162000,
+ RATE_2_1 = 216000,
+ RATE_2_4 = 243000,
+ RATE_2_7 = 270000,
+ RATE_3_2 = 324000,
+ RATE_4_3 = 432000,
+ RATE_5_4 = 540000,
+ RATE_8_1 = 810000,
+};
+
+struct phy_pll_reg {
+ u16 val[7];
+ u32 addr;
+};
+
+static const struct phy_pll_reg phy_pll_27m_cfg[] = {
+ /* 1.62 2.16 2.43 2.7 3.24 4.32 5.4 register address */
+ {{ 0x010E, 0x010E, 0x010E, 0x010E, 0x010E, 0x010E, 0x010E }, CMN_PLL0_VCOCAL_INIT_TMR },
+ {{ 0x001B, 0x001B, 0x001B, 0x001B, 0x001B, 0x001B, 0x001B }, CMN_PLL0_VCOCAL_ITER_TMR },
+ {{ 0x30B9, 0x3087, 0x3096, 0x30B4, 0x30B9, 0x3087, 0x30B4 }, CMN_PLL0_VCOCAL_START },
+ {{ 0x0077, 0x009F, 0x00B3, 0x00C7, 0x0077, 0x009F, 0x00C7 }, CMN_PLL0_INTDIV },
+ {{ 0xF9DA, 0xF7CD, 0xF6C7, 0xF5C1, 0xF9DA, 0xF7CD, 0xF5C1 }, CMN_PLL0_FRACDIV },
+ {{ 0x001E, 0x0028, 0x002D, 0x0032, 0x001E, 0x0028, 0x0032 }, CMN_PLL0_HIGH_THR },
+ {{ 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020 }, CMN_PLL0_DSM_DIAG },
+ {{ 0x0000, 0x1000, 0x1000, 0x1000, 0x0000, 0x1000, 0x1000 }, CMN_PLLSM0_USER_DEF_CTRL },
+ {{ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 }, CMN_DIAG_PLL0_OVRD },
+ {{ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 }, CMN_DIAG_PLL0_FBH_OVRD },
+ {{ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 }, CMN_DIAG_PLL0_FBL_OVRD },
+ {{ 0x0006, 0x0007, 0x0007, 0x0007, 0x0006, 0x0007, 0x0007 }, CMN_DIAG_PLL0_V2I_TUNE },
+ {{ 0x0043, 0x0043, 0x0043, 0x0042, 0x0043, 0x0043, 0x0042 }, CMN_DIAG_PLL0_CP_TUNE },
+ {{ 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008 }, CMN_DIAG_PLL0_LF_PROG },
+ {{ 0x0100, 0x0001, 0x0001, 0x0001, 0x0100, 0x0001, 0x0001 }, CMN_DIAG_PLL0_PTATIS_TUNE1 },
+ {{ 0x0007, 0x0001, 0x0001, 0x0001, 0x0007, 0x0001, 0x0001 }, CMN_DIAG_PLL0_PTATIS_TUNE2 },
+ {{ 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020 }, CMN_DIAG_PLL0_TEST_MODE},
+ {{ 0x0016, 0x0016, 0x0016, 0x0016, 0x0016, 0x0016, 0x0016 }, CMN_PSM_CLK_CTRL }
+};
+
+static const struct phy_pll_reg phy_pll_24m_cfg[] = {
+ /* 1.62 2.16 2.43 2.7 3.24 4.32 5.4 register address */
+ {{ 0x00F0, 0x00F0, 0x00F0, 0x00F0, 0x00F0, 0x00F0, 0x00F0 }, CMN_PLL0_VCOCAL_INIT_TMR },
+ {{ 0x0018, 0x0018, 0x0018, 0x0018, 0x0018, 0x0018, 0x0018 }, CMN_PLL0_VCOCAL_ITER_TMR },
+ {{ 0x3061, 0x3092, 0x30B3, 0x30D0, 0x3061, 0x3092, 0x30D0 }, CMN_PLL0_VCOCAL_START },
+ {{ 0x0086, 0x00B3, 0x00CA, 0x00E0, 0x0086, 0x00B3, 0x00E0 }, CMN_PLL0_INTDIV },
+ {{ 0xF917, 0xF6C7, 0x75A1, 0xF479, 0xF917, 0xF6C7, 0xF479 }, CMN_PLL0_FRACDIV },
+ {{ 0x0022, 0x002D, 0x0033, 0x0038, 0x0022, 0x002D, 0x0038 }, CMN_PLL0_HIGH_THR },
+ {{ 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020 }, CMN_PLL0_DSM_DIAG },
+ {{ 0x0000, 0x1000, 0x1000, 0x1000, 0x0000, 0x1000, 0x1000 }, CMN_PLLSM0_USER_DEF_CTRL },
+ {{ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 }, CMN_DIAG_PLL0_OVRD },
+ {{ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 }, CMN_DIAG_PLL0_FBH_OVRD },
+ {{ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 }, CMN_DIAG_PLL0_FBL_OVRD },
+ {{ 0x0006, 0x0007, 0x0007, 0x0007, 0x0006, 0x0007, 0x0007 }, CMN_DIAG_PLL0_V2I_TUNE },
+ {{ 0x0026, 0x0029, 0x0029, 0x0029, 0x0026, 0x0029, 0x0029 }, CMN_DIAG_PLL0_CP_TUNE },
+ {{ 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008, 0x0008 }, CMN_DIAG_PLL0_LF_PROG },
+ {{ 0x008C, 0x008C, 0x008C, 0x008C, 0x008C, 0x008C, 0x008C }, CMN_DIAG_PLL0_PTATIS_TUNE1 },
+ {{ 0x002E, 0x002E, 0x002E, 0x002E, 0x002E, 0x002E, 0x002E }, CMN_DIAG_PLL0_PTATIS_TUNE2 },
+ {{ 0x0022, 0x0022, 0x0022, 0x0022, 0x0022, 0x0022, 0x0022 }, CMN_DIAG_PLL0_TEST_MODE},
+ {{ 0x0016, 0x0016, 0x0016, 0x0016, 0x0016, 0x0016, 0x0016 }, CMN_PSM_CLK_CTRL }
+};
+
+static int link_rate_index(u32 rate)
+{
+ switch (rate) {
+ case RATE_1_6:
+ return 0;
+ case RATE_2_1:
+ return 1;
+ case RATE_2_4:
+ return 2;
+ case RATE_2_7:
+ return 3;
+ case RATE_3_2:
+ return 4;
+ case RATE_4_3:
+ return 5;
+ case RATE_5_4:
+ return 6;
+ default:
+ return -1;
+ }
+}
+
+static void dp_aux_cfg(struct cdns_mhdp_device *mhdp)
+{
+ /* Power up Aux */
+ cdns_phy_reg_write(mhdp, TXDA_CYA_AUXDA_CYA, 1);
+
+ cdns_phy_reg_write(mhdp, TX_DIG_CTRL_REG_1, 0x3);
+ ndelay(150);
+ cdns_phy_reg_write(mhdp, TX_DIG_CTRL_REG_2, 36);
+ ndelay(150);
+ cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_2, 0x0100);
+ ndelay(150);
+ cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_2, 0x0300);
+ ndelay(150);
+ cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_3, 0x0000);
+ ndelay(150);
+ cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_1, 0x2008);
+ ndelay(150);
+ cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_1, 0x2018);
+ ndelay(150);
+ cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_1, 0xA018);
+ ndelay(150);
+ cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_2, 0x030C);
+ ndelay(150);
+ cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_5, 0x0000);
+ ndelay(150);
+ cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_4, 0x1001);
+ ndelay(150);
+ cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_1, 0xA098);
+ ndelay(150);
+ cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_1, 0xA198);
+ ndelay(150);
+ cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_2, 0x030d);
+ ndelay(150);
+ cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_2, 0x030f);
+}
+
+/* PMA common configuration for 24MHz */
+static void dp_phy_pma_cmn_cfg_24mhz(struct cdns_mhdp_device *mhdp)
+{
+ int k;
+ u32 num_lanes = 4;
+ u16 val;
+
+ val = cdns_phy_reg_read(mhdp, PHY_PMA_CMN_CTRL1);
+ val &= 0xFFF7;
+ val |= 0x0008;
+ cdns_phy_reg_write(mhdp, PHY_PMA_CMN_CTRL1, val);
+
+ for (k = 0; k < num_lanes; k++) {
+ /* Transceiver control and diagnostic registers */
+ cdns_phy_reg_write(mhdp, XCVR_DIAG_LANE_FCM_EN_MGN_TMR | (k << 9), 0x0090);
+ /* Transmitter receiver detect registers */
+ cdns_phy_reg_write(mhdp, TX_RCVDET_EN_TMR | (k << 9), 0x0960);
+ cdns_phy_reg_write(mhdp, TX_RCVDET_ST_TMR | (k << 9), 0x0030);
+ }
+}
+
+/* Valid for 24 MHz only */
+static void dp_phy_pma_cmn_pll0_24mhz(struct cdns_mhdp_device *mhdp)
+{
+ u32 num_lanes = 4;
+ u32 link_rate = mhdp->dp.rate;
+ u16 val;
+ int index, i, k;
+
+ /*
+ * PLL reference clock source select
+ * for single ended reference clock val |= 0x0030;
+ * for differential clock val |= 0x0000;
+ */
+ val = cdns_phy_reg_read(mhdp, PHY_PMA_CMN_CTRL1);
+ val = val & 0xFF8F;
+ val = val | 0x0030;
+ cdns_phy_reg_write(mhdp, PHY_PMA_CMN_CTRL1, val);
+
+ /* DP PLL data rate 0/1 clock divider value */
+ val = cdns_phy_reg_read(mhdp, PHY_HDP_CLK_CTL);
+ val &= 0x00FF;
+ if (link_rate <= RATE_2_7)
+ val |= 0x2400;
+ else
+ val |= 0x1200;
+ cdns_phy_reg_write(mhdp, PHY_HDP_CLK_CTL, val);
+
+ /* High speed clock 0/1 div */
+ val = cdns_phy_reg_read(mhdp, CMN_DIAG_HSCLK_SEL);
+ val &= 0xFFCC;
+ if (link_rate <= RATE_2_7)
+ val |= 0x0011;
+ cdns_phy_reg_write(mhdp, CMN_DIAG_HSCLK_SEL, val);
+
+ for (k = 0; k < num_lanes; k = k + 1) {
+ val = cdns_phy_reg_read(mhdp, (XCVR_DIAG_HSCLK_SEL | (k << 9)));
+ val &= 0xCFFF;
+ if (link_rate <= RATE_2_7)
+ val |= 0x1000;
+ cdns_phy_reg_write(mhdp, (XCVR_DIAG_HSCLK_SEL | (k << 9)), val);
+ }
+
+ /* DP PHY PLL 24MHz configuration */
+ index = link_rate_index(link_rate);
+ if (index < 0) {
+ dev_err(mhdp->dev, "wrong link rate index\n");
+ return;
+ }
+ for (i = 0; i < ARRAY_SIZE(phy_pll_24m_cfg); i++)
+ cdns_phy_reg_write(mhdp, phy_pll_24m_cfg[i].addr, phy_pll_24m_cfg[i].val[index]);
+
+ /* Transceiver control and diagnostic registers */
+ for (k = 0; k < num_lanes; k = k + 1) {
+ val = cdns_phy_reg_read(mhdp, (XCVR_DIAG_PLLDRC_CTRL | (k << 9)));
+ val &= 0x8FFF;
+ if (link_rate <= RATE_2_7)
+ val |= 0x2000;
+ else
+ val |= 0x1000;
+ cdns_phy_reg_write(mhdp, (XCVR_DIAG_PLLDRC_CTRL | (k << 9)), val);
+ }
+
+ for (k = 0; k < num_lanes; k = k + 1) {
+ cdns_phy_reg_write(mhdp, (XCVR_PSM_RCTRL | (k << 9)), 0xBEFC);
+ cdns_phy_reg_write(mhdp, (TX_PSC_A0 | (k << 9)), 0x6799);
+ cdns_phy_reg_write(mhdp, (TX_PSC_A1 | (k << 9)), 0x6798);
+ cdns_phy_reg_write(mhdp, (TX_PSC_A2 | (k << 9)), 0x0098);
+ cdns_phy_reg_write(mhdp, (TX_PSC_A3 | (k << 9)), 0x0098);
+ }
+}
+
+/* PMA common configuration for 27MHz */
+static void dp_phy_pma_cmn_cfg_27mhz(struct cdns_mhdp_device *mhdp)
+{
+ u32 num_lanes = 4;
+ u16 val;
+ int k;
+
+ val = cdns_phy_reg_read(mhdp, PHY_PMA_CMN_CTRL1);
+ val &= 0xFFF7;
+ val |= 0x0008;
+ cdns_phy_reg_write(mhdp, PHY_PMA_CMN_CTRL1, val);
+
+ /* Startup state machine registers */
+ cdns_phy_reg_write(mhdp, CMN_SSM_BIAS_TMR, 0x0087);
+ cdns_phy_reg_write(mhdp, CMN_PLLSM0_PLLEN_TMR, 0x001B);
+ cdns_phy_reg_write(mhdp, CMN_PLLSM0_PLLPRE_TMR, 0x0036);
+ cdns_phy_reg_write(mhdp, CMN_PLLSM0_PLLVREF_TMR, 0x001B);
+ cdns_phy_reg_write(mhdp, CMN_PLLSM0_PLLLOCK_TMR, 0x006C);
+
+ /* Current calibration registers */
+ cdns_phy_reg_write(mhdp, CMN_ICAL_INIT_TMR, 0x0044);
+ cdns_phy_reg_write(mhdp, CMN_ICAL_ITER_TMR, 0x0006);
+ cdns_phy_reg_write(mhdp, CMN_ICAL_ADJ_INIT_TMR, 0x0022);
+ cdns_phy_reg_write(mhdp, CMN_ICAL_ADJ_ITER_TMR, 0x0006);
+
+ /* Resistor calibration registers */
+ cdns_phy_reg_write(mhdp, CMN_TXPUCAL_INIT_TMR, 0x0022);
+ cdns_phy_reg_write(mhdp, CMN_TXPUCAL_ITER_TMR, 0x0006);
+ cdns_phy_reg_write(mhdp, CMN_TXPU_ADJ_INIT_TMR, 0x0022);
+ cdns_phy_reg_write(mhdp, CMN_TXPU_ADJ_ITER_TMR, 0x0006);
+ cdns_phy_reg_write(mhdp, CMN_TXPDCAL_INIT_TMR, 0x0022);
+ cdns_phy_reg_write(mhdp, CMN_TXPDCAL_ITER_TMR, 0x0006);
+ cdns_phy_reg_write(mhdp, CMN_TXPD_ADJ_INIT_TMR, 0x0022);
+ cdns_phy_reg_write(mhdp, CMN_TXPD_ADJ_ITER_TMR, 0x0006);
+ cdns_phy_reg_write(mhdp, CMN_RXCAL_INIT_TMR, 0x0022);
+ cdns_phy_reg_write(mhdp, CMN_RXCAL_ITER_TMR, 0x0006);
+ cdns_phy_reg_write(mhdp, CMN_RX_ADJ_INIT_TMR, 0x0022);
+ cdns_phy_reg_write(mhdp, CMN_RX_ADJ_ITER_TMR, 0x0006);
+
+ for (k = 0; k < num_lanes; k = k + 1) {
+ /* Power state machine registers */
+ cdns_phy_reg_write(mhdp, XCVR_PSM_CAL_TMR | (k << 9), 0x016D);
+ cdns_phy_reg_write(mhdp, XCVR_PSM_A0IN_TMR | (k << 9), 0x016D);
+ /* Transceiver control and diagnostic registers */
+ cdns_phy_reg_write(mhdp, XCVR_DIAG_LANE_FCM_EN_MGN_TMR | (k << 9), 0x00A2);
+ cdns_phy_reg_write(mhdp, TX_DIAG_BGREF_PREDRV_DELAY | (k << 9), 0x0097);
+ /* Transmitter receiver detect registers */
+ cdns_phy_reg_write(mhdp, TX_RCVDET_EN_TMR | (k << 9), 0x0A8C);
+ cdns_phy_reg_write(mhdp, TX_RCVDET_ST_TMR | (k << 9), 0x0036);
+ }
+}
+
+static void dp_phy_pma_cmn_pll0_27mhz(struct cdns_mhdp_device *mhdp)
+{
+ u32 num_lanes = 4;
+ u32 link_rate = mhdp->dp.rate;
+ u16 val;
+ int index, i, k;
+
+ /*
+ * PLL reference clock source select
+ * for single ended reference clock val |= 0x0030;
+ * for differential clock val |= 0x0000;
+ */
+ val = cdns_phy_reg_read(mhdp, PHY_PMA_CMN_CTRL1);
+ val &= 0xFF8F;
+ cdns_phy_reg_write(mhdp, PHY_PMA_CMN_CTRL1, val);
+
+ /* for differential clock on the refclk_p and refclk_m off chip pins:
+ * CMN_DIAG_ACYA[8]=1'b1
+ */
+ cdns_phy_reg_write(mhdp, CMN_DIAG_ACYA, 0x0100);
+
+ /* DP PLL data rate 0/1 clock divider value */
+ val = cdns_phy_reg_read(mhdp, PHY_HDP_CLK_CTL);
+ val &= 0x00FF;
+ if (link_rate <= RATE_2_7)
+ val |= 0x2400;
+ else
+ val |= 0x1200;
+ cdns_phy_reg_write(mhdp, PHY_HDP_CLK_CTL, val);
+
+ /* High speed clock 0/1 div */
+ val = cdns_phy_reg_read(mhdp, CMN_DIAG_HSCLK_SEL);
+ val &= 0xFFCC;
+ if (link_rate <= RATE_2_7)
+ val |= 0x0011;
+ cdns_phy_reg_write(mhdp, CMN_DIAG_HSCLK_SEL, val);
+
+ for (k = 0; k < num_lanes; k++) {
+ val = cdns_phy_reg_read(mhdp, (XCVR_DIAG_HSCLK_SEL | (k << 9)));
+ val = val & 0xCFFF;
+ if (link_rate <= RATE_2_7)
+ val |= 0x1000;
+ cdns_phy_reg_write(mhdp, (XCVR_DIAG_HSCLK_SEL | (k << 9)), val);
+ }
+
+ /* DP PHY PLL 27MHz configuration */
+ index = link_rate_index(link_rate);
+ if (index < 0) {
+ dev_err(mhdp->dev, "wrong link rate index\n");
+ return;
+ }
+ for (i = 0; i < ARRAY_SIZE(phy_pll_27m_cfg); i++)
+ cdns_phy_reg_write(mhdp, phy_pll_27m_cfg[i].addr, phy_pll_27m_cfg[i].val[index]);
+
+ /* Transceiver control and diagnostic registers */
+ for (k = 0; k < num_lanes; k++) {
+ val = cdns_phy_reg_read(mhdp, (XCVR_DIAG_PLLDRC_CTRL | (k << 9)));
+ val = val & 0x8FFF;
+ if (link_rate <= RATE_2_7)
+ val |= 0x2000;
+ else
+ val |= 0x1000;
+ cdns_phy_reg_write(mhdp, (XCVR_DIAG_PLLDRC_CTRL | (k << 9)), val);
+ }
+
+ for (k = 0; k < num_lanes; k = k + 1) {
+ /* Power state machine registers */
+ cdns_phy_reg_write(mhdp, (XCVR_PSM_RCTRL | (k << 9)), 0xBEFC);
+ cdns_phy_reg_write(mhdp, (TX_PSC_A0 | (k << 9)), 0x6799);
+ cdns_phy_reg_write(mhdp, (TX_PSC_A1 | (k << 9)), 0x6798);
+ cdns_phy_reg_write(mhdp, (TX_PSC_A2 | (k << 9)), 0x0098);
+ cdns_phy_reg_write(mhdp, (TX_PSC_A3 | (k << 9)), 0x0098);
+ /* Receiver calibration power state definition register */
+ val = cdns_phy_reg_read(mhdp, RX_PSC_CAL | (k << 9));
+ val &= 0xFFBB;
+ cdns_phy_reg_write(mhdp, (RX_PSC_CAL | (k << 9)), val);
+ val = cdns_phy_reg_read(mhdp, RX_PSC_A0 | (k << 9));
+ val &= 0xFFBB;
+ cdns_phy_reg_write(mhdp, (RX_PSC_A0 | (k << 9)), val);
+ }
+}
+
+static void dp_phy_power_down(struct cdns_mhdp_device *mhdp)
+{
+ u16 val;
+ int i;
+
+ if (!mhdp->power_up)
+ return;
+
+ /* Place the PHY lanes in the A3 power state. */
+ cdns_phy_reg_write(mhdp, PHY_HDP_MODE_CTRL, 0x8);
+ /* Wait for Power State A3 Ack */
+ for (i = 0; i < 10; i++) {
+ val = cdns_phy_reg_read(mhdp, PHY_HDP_MODE_CTRL);
+ if (val & (1 << 7))
+ break;
+ }
+ if (i == 10) {
+ dev_err(mhdp->dev, "Wait A3 Ack failed\n");
+ return;
+ }
+ DRM_DEBUG_DRIVER("Wait A3 Ack count - %d", i);
+
+ /* Disable HDP PLL’s data rate and full rate clocks out of PMA. */
+ val = cdns_phy_reg_read(mhdp, PHY_HDP_CLK_CTL);
+ val &= ~(1 << 2);
+ cdns_phy_reg_write(mhdp, PHY_HDP_CLK_CTL, val);
+ /* Wait for PLL clock gate ACK */
+ for (i = 0; i < 10; i++) {
+ val = cdns_phy_reg_read(mhdp, PHY_HDP_CLK_CTL);
+ if (!(val & (1 << 3)))
+ break;
+ }
+ if (i == 10) {
+ dev_err(mhdp->dev, "Wait PLL clock gate Ack failed\n");
+ return;
+ }
+ DRM_DEBUG_DRIVER("Wait PLL clock gate - %d", i);
+
+ /* Disable HDP PLL’s for high speed clocks */
+ val = cdns_phy_reg_read(mhdp, PHY_HDP_CLK_CTL);
+ val &= ~(1 << 0);
+ cdns_phy_reg_write(mhdp, PHY_HDP_CLK_CTL, val);
+ /* Wait for PLL disable ACK */
+ for (i = 0; i < 10; i++) {
+ val = cdns_phy_reg_read(mhdp, PHY_HDP_CLK_CTL);
+ if (!(val & (1 << 1)))
+ break;
+ }
+ if (i == 10) {
+ dev_err(mhdp->dev, "Wait PLL disable Ack failed\n");
+ return;
+ }
+ DRM_DEBUG_DRIVER("Wait PLL disable - %d", i);
+
+}
+
+int cdns_dp_phy_power_up(struct cdns_mhdp_device *mhdp)
+{
+ u32 val, i;
+
+ /* Enable HDP PLL’s for high speed clocks */
+ val = cdns_phy_reg_read(mhdp, PHY_HDP_CLK_CTL);
+ val |= (1 << 0);
+ cdns_phy_reg_write(mhdp, PHY_HDP_CLK_CTL, val);
+ /* Wait for PLL ready ACK */
+ for (i = 0; i < 10; i++) {
+ val = cdns_phy_reg_read(mhdp, PHY_HDP_CLK_CTL);
+ if (val & (1 << 1))
+ break;
+ msleep(20);
+ }
+ if (i == 10) {
+ dev_err(mhdp->dev, "Wait PLL Ack failed\n");
+ return -1;
+ }
+
+ /* Enable HDP PLL’s data rate and full rate clocks out of PMA. */
+ val = cdns_phy_reg_read(mhdp, PHY_HDP_CLK_CTL);
+ val |= (1 << 2);
+ cdns_phy_reg_write(mhdp, PHY_HDP_CLK_CTL, val);
+ /* Wait for PLL clock enable ACK */
+ for (i = 0; i < 10; i++) {
+ val = cdns_phy_reg_read(mhdp, PHY_HDP_CLK_CTL);
+ if (val & (1 << 3))
+ break;
+ msleep(20);
+ }
+ if (i == 10) {
+ dev_err(mhdp->dev, "Wait PLL clock enable ACk failed\n");
+ return -1;
+ }
+
+ /* Configure PHY in A2 Mode */
+ cdns_phy_reg_write(mhdp, PHY_HDP_MODE_CTRL, 0x0004);
+ /* Wait for Power State A2 Ack */
+ for (i = 0; i < 10; i++) {
+ val = cdns_phy_reg_read(mhdp, PHY_HDP_MODE_CTRL);
+ if (val & (1 << 6))
+ break;
+ msleep(20);
+ }
+ if (i == 10) {
+ dev_err(mhdp->dev, "Wait A2 Ack failed\n");
+ return -1;
+ }
+
+ /* Configure PHY in A0 mode (PHY must be in the A0 power
+ * state in order to transmit data)
+ */
+ cdns_phy_reg_write(mhdp, PHY_HDP_MODE_CTRL, 0x0101);
+
+ /* Wait for Power State A0 Ack */
+ for (i = 0; i < 10; i++) {
+ val = cdns_phy_reg_read(mhdp, PHY_HDP_MODE_CTRL);
+ if (val & (1 << 4))
+ break;
+ msleep(20);
+ }
+ if (i == 10) {
+ dev_err(mhdp->dev, "Wait A0 Ack failed\n");
+ return -1;
+ }
+
+ mhdp->power_up = true;
+
+ return 0;
+}
+
+int cdns_dp_phy_set_imx8mq(struct cdns_mhdp_device *mhdp)
+{
+ /* Disable phy clock if PHY in power up state */
+ dp_phy_power_down(mhdp);
+
+ dp_phy_pma_cmn_cfg_27mhz(mhdp);
+
+ dp_phy_pma_cmn_pll0_27mhz(mhdp);
+
+ cdns_phy_reg_write(mhdp, TX_DIAG_ACYA_0, 1);
+ cdns_phy_reg_write(mhdp, TX_DIAG_ACYA_1, 1);
+ cdns_phy_reg_write(mhdp, TX_DIAG_ACYA_2, 1);
+ cdns_phy_reg_write(mhdp, TX_DIAG_ACYA_3, 1);
+
+ dp_aux_cfg(mhdp);
+
+ return true;
+}
+
+int cdns_dp_phy_set_imx8qm(struct cdns_mhdp_device *mhdp)
+{
+ /* Disable phy clock if PHY in power up state */
+ dp_phy_power_down(mhdp);
+
+ dp_phy_pma_cmn_cfg_24mhz(mhdp);
+
+ dp_phy_pma_cmn_pll0_24mhz(mhdp);
+
+ cdns_phy_reg_write(mhdp, TX_DIAG_ACYA_0, 1);
+ cdns_phy_reg_write(mhdp, TX_DIAG_ACYA_1, 1);
+ cdns_phy_reg_write(mhdp, TX_DIAG_ACYA_2, 1);
+ cdns_phy_reg_write(mhdp, TX_DIAG_ACYA_3, 1);
+
+ dp_aux_cfg(mhdp);
+
+ return true;
+}
+
+int cdns_dp_phy_shutdown(struct cdns_mhdp_device *mhdp)
+{
+ dp_phy_power_down(mhdp);
+ DRM_DEBUG_DRIVER("dp phy shutdown complete\n");
+ return 0;
+}
diff --git a/drivers/gpu/drm/imx/mhdp/cdns-mhdp-hdmi-phy.c b/drivers/gpu/drm/imx/mhdp/cdns-mhdp-hdmi-phy.c
new file mode 100644
index 000000000000..dbcb14a6f98f
--- /dev/null
+++ b/drivers/gpu/drm/imx/mhdp/cdns-mhdp-hdmi-phy.c
@@ -0,0 +1,796 @@
+/*
+ * Cadence High-Definition Multimedia Interface (HDMI) driver
+ *
+ * Copyright 2019-2021 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+#include <drm/drm_of.h>
+#include <drm/drm_vblank.h>
+#include <drm/drm_print.h>
+#include <drm/drm_crtc_helper.h>
+#include <linux/io.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_encoder_slave.h>
+#include <drm/drm_atomic.h>
+#include <linux/io.h>
+
+#include <drm/bridge/cdns-mhdp.h>
+#include "cdns-mhdp-phy.h"
+#include "cdns-mhdp-imx.h"
+
+/* HDMI TX clock control settings */
+struct hdmi_ctrl {
+ u32 pixel_clk_freq_min;
+ u32 pixel_clk_freq_max;
+ u32 feedback_factor;
+ u32 data_range_kbps_min;
+ u32 data_range_kbps_max;
+ u32 cmnda_pll0_ip_div;
+ u32 cmn_ref_clk_dig_div;
+ u32 ref_clk_divider_scaler;
+ u32 pll_fb_div_total;
+ u32 cmnda_pll0_fb_div_low;
+ u32 cmnda_pll0_fb_div_high;
+ u32 pixel_div_total;
+ u32 cmnda_pll0_pxdiv_low;
+ u32 cmnda_pll0_pxdiv_high;
+ u32 vco_freq_min;
+ u32 vco_freq_max;
+ u32 vco_ring_select;
+ u32 cmnda_hs_clk_0_sel;
+ u32 cmnda_hs_clk_1_sel;
+ u32 hsclk_div_at_xcvr;
+ u32 hsclk_div_tx_sub_rate;
+ u32 cmnda_pll0_hs_sym_div_sel;
+ u32 cmnda_pll0_clk_freq_min;
+ u32 cmnda_pll0_clk_freq_max;
+};
+
+/* HDMI TX clock control settings, pixel clock is output */
+static const struct hdmi_ctrl imx8mq_ctrl_table[] = {
+/*Minclk Maxclk Fdbak DR_min DR_max ip_d dig DS Totl */
+{ 27000, 27000, 1000, 270000, 270000, 0x03, 0x1, 0x1, 240, 0x0BC, 0x030, 80, 0x026, 0x026, 2160000, 2160000, 0, 2, 2, 2, 4, 0x3, 27000, 27000},
+{ 27000, 27000, 1250, 337500, 337500, 0x03, 0x1, 0x1, 300, 0x0EC, 0x03C, 100, 0x030, 0x030, 2700000, 2700000, 0, 2, 2, 2, 4, 0x3, 33750, 33750},
+{ 27000, 27000, 1500, 405000, 405000, 0x03, 0x1, 0x1, 360, 0x11C, 0x048, 120, 0x03A, 0x03A, 3240000, 3240000, 0, 2, 2, 2, 4, 0x3, 40500, 40500},
+{ 27000, 27000, 2000, 540000, 540000, 0x03, 0x1, 0x1, 240, 0x0BC, 0x030, 80, 0x026, 0x026, 2160000, 2160000, 0, 2, 2, 2, 4, 0x2, 54000, 54000},
+{ 54000, 54000, 1000, 540000, 540000, 0x03, 0x1, 0x1, 480, 0x17C, 0x060, 80, 0x026, 0x026, 4320000, 4320000, 1, 2, 2, 2, 4, 0x3, 54000, 54000},
+{ 54000, 54000, 1250, 675000, 675000, 0x04, 0x1, 0x1, 400, 0x13C, 0x050, 50, 0x017, 0x017, 2700000, 2700000, 0, 1, 1, 2, 4, 0x2, 67500, 67500},
+{ 54000, 54000, 1500, 810000, 810000, 0x04, 0x1, 0x1, 480, 0x17C, 0x060, 60, 0x01C, 0x01C, 3240000, 3240000, 0, 2, 2, 2, 2, 0x2, 81000, 81000},
+{ 54000, 54000, 2000, 1080000, 1080000, 0x03, 0x1, 0x1, 240, 0x0BC, 0x030, 40, 0x012, 0x012, 2160000, 2160000, 0, 2, 2, 2, 1, 0x1, 108000, 108000},
+{ 74250, 74250, 1000, 742500, 742500, 0x03, 0x1, 0x1, 660, 0x20C, 0x084, 80, 0x026, 0x026, 5940000, 5940000, 1, 2, 2, 2, 4, 0x3, 74250, 74250},
+{ 74250, 74250, 1250, 928125, 928125, 0x04, 0x1, 0x1, 550, 0x1B4, 0x06E, 50, 0x017, 0x017, 3712500, 3712500, 1, 1, 1, 2, 4, 0x2, 92812, 92812},
+{ 74250, 74250, 1500, 1113750, 1113750, 0x04, 0x1, 0x1, 660, 0x20C, 0x084, 60, 0x01C, 0x01C, 4455000, 4455000, 1, 2, 2, 2, 2, 0x2, 111375, 111375},
+{ 74250, 74250, 2000, 1485000, 1485000, 0x03, 0x1, 0x1, 330, 0x104, 0x042, 40, 0x012, 0x012, 2970000, 2970000, 0, 2, 2, 2, 1, 0x1, 148500, 148500},
+{ 99000, 99000, 1000, 990000, 990000, 0x03, 0x1, 0x1, 440, 0x15C, 0x058, 40, 0x012, 0x012, 3960000, 3960000, 1, 2, 2, 2, 2, 0x2, 99000, 99000},
+{ 99000, 99000, 1250, 1237500, 1237500, 0x03, 0x1, 0x1, 275, 0x0D8, 0x037, 25, 0x00B, 0x00A, 2475000, 2475000, 0, 1, 1, 2, 2, 0x1, 123750, 123750},
+{ 99000, 99000, 1500, 1485000, 1485000, 0x03, 0x1, 0x1, 330, 0x104, 0x042, 30, 0x00D, 0x00D, 2970000, 2970000, 0, 2, 2, 2, 1, 0x1, 148500, 148500},
+{ 99000, 99000, 2000, 1980000, 1980000, 0x03, 0x1, 0x1, 440, 0x15C, 0x058, 40, 0x012, 0x012, 3960000, 3960000, 1, 2, 2, 2, 1, 0x1, 198000, 198000},
+{148500, 148500, 1000, 1485000, 1485000, 0x03, 0x1, 0x1, 660, 0x20C, 0x084, 40, 0x012, 0x012, 5940000, 5940000, 1, 2, 2, 2, 2, 0x2, 148500, 148500},
+{148500, 148500, 1250, 1856250, 1856250, 0x04, 0x1, 0x1, 550, 0x1B4, 0x06E, 25, 0x00B, 0x00A, 3712500, 3712500, 1, 1, 1, 2, 2, 0x1, 185625, 185625},
+{148500, 148500, 1500, 2227500, 2227500, 0x03, 0x1, 0x1, 495, 0x188, 0x063, 30, 0x00D, 0x00D, 4455000, 4455000, 1, 1, 1, 2, 2, 0x1, 222750, 222750},
+{148500, 148500, 2000, 2970000, 2970000, 0x03, 0x1, 0x1, 660, 0x20C, 0x084, 40, 0x012, 0x012, 5940000, 5940000, 1, 2, 2, 2, 1, 0x1, 297000, 297000},
+{198000, 198000, 1000, 1980000, 1980000, 0x03, 0x1, 0x1, 220, 0x0AC, 0x02C, 10, 0x003, 0x003, 1980000, 1980000, 0, 1, 1, 2, 1, 0x0, 198000, 198000},
+{198000, 198000, 1250, 2475000, 2475000, 0x03, 0x1, 0x1, 550, 0x1B4, 0x06E, 25, 0x00B, 0x00A, 4950000, 4950000, 1, 1, 1, 2, 2, 0x1, 247500, 247500},
+{198000, 198000, 1500, 2970000, 2970000, 0x03, 0x1, 0x1, 330, 0x104, 0x042, 15, 0x006, 0x005, 2970000, 2970000, 0, 1, 1, 2, 1, 0x0, 297000, 297000},
+{198000, 198000, 2000, 3960000, 3960000, 0x03, 0x1, 0x1, 440, 0x15C, 0x058, 20, 0x008, 0x008, 3960000, 3960000, 1, 1, 1, 2, 1, 0x0, 396000, 396000},
+{297000, 297000, 1000, 2970000, 2970000, 0x03, 0x1, 0x1, 330, 0x104, 0x042, 10, 0x003, 0x003, 2970000, 2970000, 0, 1, 1, 2, 1, 0x0, 297000, 297000},
+{297000, 297000, 1500, 4455000, 4455000, 0x03, 0x1, 0x1, 495, 0x188, 0x063, 15, 0x006, 0x005, 4455000, 4455000, 1, 1, 1, 2, 1, 0x0, 445500, 445500},
+{297000, 297000, 2000, 5940000, 5940000, 0x03, 0x1, 0x1, 660, 0x20C, 0x084, 20, 0x008, 0x008, 5940000, 5940000, 1, 1, 1, 2, 1, 0x0, 594000, 594000},
+{594000, 594000, 1000, 5940000, 5940000, 0x03, 0x1, 0x1, 660, 0x20C, 0x084, 10, 0x003, 0x003, 5940000, 5940000, 1, 1, 1, 2, 1, 0x0, 594000, 594000},
+{594000, 594000, 750, 4455000, 4455000, 0x03, 0x1, 0x1, 495, 0x188, 0x063, 10, 0x003, 0x003, 4455000, 4455000, 1, 1, 1, 2, 1, 0x0, 445500, 445500},
+{594000, 594000, 625, 3712500, 3712500, 0x04, 0x1, 0x1, 550, 0x1B4, 0x06E, 10, 0x003, 0x003, 3712500, 3712500, 1, 1, 1, 2, 1, 0x0, 371250, 371250},
+{594000, 594000, 500, 2970000, 2970000, 0x03, 0x1, 0x1, 660, 0x20C, 0x084, 10, 0x003, 0x003, 5940000, 5940000, 1, 1, 1, 2, 2, 0x1, 297000, 297000},
+};
+
+/* HDMI TX clock control settings, pixel clock is input */
+static const struct hdmi_ctrl imx8qm_ctrl_table[] = {
+/*pclk_l pclk_h fd DRR_L DRR_H PLLD */
+{ 25000, 42500, 1000, 250000, 425000, 0x05, 0x01, 0x01, 400, 0x182, 0x00A, 0, 0, 0, 2000000, 3400000, 0, 2, 2, 2, 4, 0x03, 25000, 42500},
+{ 42500, 85000, 1000, 425000, 850000, 0x08, 0x03, 0x01, 320, 0x132, 0x00A, 0, 0, 0, 1700000, 3400000, 0, 1, 1, 2, 4, 0x02, 42500, 85000},
+{ 85000, 170000, 1000, 850000, 1700000, 0x11, 0x00, 0x07, 340, 0x146, 0x00A, 0, 0, 0, 1700000, 3400000, 0, 1, 1, 2, 2, 0x01, 85000, 170000},
+{170000, 340000, 1000, 1700000, 3400000, 0x22, 0x01, 0x07, 340, 0x146, 0x00A, 0, 0, 0, 1700000, 3400000, 0, 1, 1, 2, 1, 0x00, 170000, 340000},
+{340000, 600000, 1000, 3400000, 6000000, 0x3C, 0x03, 0x06, 600, 0x24A, 0x00A, 0, 0, 0, 3400000, 6000000, 1, 1, 1, 2, 1, 0x00, 340000, 600000},
+{ 25000, 34000, 1250, 312500, 425000, 0x04, 0x01, 0x01, 400, 0x182, 0x00A, 0, 0, 0, 2500000, 3400000, 0, 2, 2, 2, 4, 0x03, 31250, 42500},
+{ 34000, 68000, 1250, 425000, 850000, 0x06, 0x02, 0x01, 300, 0x11E, 0x00A, 0, 0, 0, 1700000, 3400000, 0, 1, 1, 2, 4, 0x02, 42500, 85000},
+{ 68000, 136000, 1250, 850000, 1700000, 0x0D, 0x02, 0x02, 325, 0x137, 0x00A, 0, 0, 0, 1700000, 3400000, 0, 1, 1, 2, 2, 0x01, 85000, 170000},
+{136000, 272000, 1250, 1700000, 3400000, 0x1A, 0x02, 0x04, 325, 0x137, 0x00A, 0, 0, 0, 1700000, 3400000, 0, 1, 1, 2, 1, 0x00, 170000, 340000},
+{272000, 480000, 1250, 3400000, 6000000, 0x30, 0x03, 0x05, 600, 0x24A, 0x00A, 0, 0, 0, 3400000, 6000000, 1, 1, 1, 2, 1, 0x00, 340000, 600000},
+{ 25000, 28000, 1500, 375000, 420000, 0x03, 0x01, 0x01, 360, 0x15A, 0x00A, 0, 0, 0, 3000000, 3360000, 0, 2, 2, 2, 4, 0x03, 37500, 42000},
+{ 28000, 56000, 1500, 420000, 840000, 0x06, 0x02, 0x01, 360, 0x15A, 0x00A, 0, 0, 0, 1680000, 3360000, 0, 1, 1, 2, 4, 0x02, 42000, 84000},
+{ 56000, 113000, 1500, 840000, 1695000, 0x0B, 0x00, 0x05, 330, 0x13C, 0x00A, 0, 0, 0, 1680000, 3390000, 0, 1, 1, 2, 2, 0x01, 84000, 169500},
+{113000, 226000, 1500, 1695000, 3390000, 0x16, 0x01, 0x05, 330, 0x13C, 0x00A, 0, 0, 0, 1695000, 3390000, 0, 1, 1, 2, 1, 0x00, 169500, 339000},
+{226000, 400000, 1500, 3390000, 6000000, 0x28, 0x03, 0x04, 600, 0x24A, 0x00A, 0, 0, 0, 3390000, 6000000, 1, 1, 1, 2, 1, 0x00, 339000, 600000},
+{ 25000, 42500, 2000, 500000, 850000, 0x05, 0x01, 0x01, 400, 0x182, 0x00A, 0, 0, 0, 2000000, 3400000, 0, 1, 1, 2, 4, 0x02, 50000, 85000},
+{ 42500, 85000, 2000, 850000, 1700000, 0x08, 0x03, 0x01, 320, 0x132, 0x00A, 0, 0, 0, 1700000, 3400000, 0, 1, 1, 2, 2, 0x01, 85000, 170000},
+{ 85000, 170000, 2000, 1700000, 3400000, 0x11, 0x00, 0x07, 340, 0x146, 0x00A, 0, 0, 0, 1700000, 3400000, 0, 1, 1, 2, 1, 0x00, 170000, 340000},
+{170000, 300000, 2000, 3400000, 6000000, 0x22, 0x01, 0x06, 680, 0x29A, 0x00A, 0, 0, 0, 3400000, 6000000, 1, 1, 1, 2, 1, 0x00, 340000, 600000},
+{594000, 594000, 5000, 2970000, 2970000, 0x3C, 0x03, 0x06, 600, 0x24A, 0x00A, 0, 0, 0, 5940000, 5940000, 1, 1, 1, 2, 2, 0x01, 297000, 297000},
+{594000, 594000, 6250, 3712500, 3712500, 0x3C, 0x03, 0x06, 375, 0x169, 0x00A, 0, 0, 0, 3712500, 3712500, 1, 1, 1, 2, 1, 0x00, 371250, 371250},
+{594000, 594000, 7500, 4455000, 4455000, 0x3C, 0x03, 0x06, 450, 0x1B4, 0x00A, 0, 0, 0, 4455000, 4455000, 1, 1, 1, 2, 1, 0x00, 445500, 445500},
+};
+
+/* HDMI TX PLL tuning settings */
+struct hdmi_pll_tuning {
+ u32 vco_freq_bin;
+ u32 vco_freq_min;
+ u32 vco_freq_max;
+ u32 volt_to_current_coarse;
+ u32 volt_to_current;
+ u32 ndac_ctrl;
+ u32 pmos_ctrl;
+ u32 ptat_ndac_ctrl;
+ u32 feedback_div_total;
+ u32 charge_pump_gain;
+ u32 coarse_code;
+ u32 v2i_code;
+ u32 vco_cal_code;
+};
+
+/* HDMI TX PLL tuning settings, pixel clock is output */
+static const struct hdmi_pll_tuning imx8mq_pll_table[] = {
+/* bin VCO_freq min/max coar cod NDAC PMOS PTAT div-T P-Gain Coa V2I CAL */
+ { 1, 1980000, 1980000, 0x4, 0x3, 0x0, 0x09, 0x09, 220, 0x42, 160, 5, 183 },
+ { 2, 2160000, 2160000, 0x4, 0x3, 0x0, 0x09, 0x09, 240, 0x42, 166, 6, 208 },
+ { 3, 2475000, 2475000, 0x5, 0x3, 0x1, 0x00, 0x07, 275, 0x42, 167, 6, 209 },
+ { 4, 2700000, 2700000, 0x5, 0x3, 0x1, 0x00, 0x07, 300, 0x42, 188, 6, 230 },
+ { 4, 2700000, 2700000, 0x5, 0x3, 0x1, 0x00, 0x07, 400, 0x4C, 188, 6, 230 },
+ { 5, 2970000, 2970000, 0x6, 0x3, 0x1, 0x00, 0x07, 330, 0x42, 183, 6, 225 },
+ { 6, 3240000, 3240000, 0x6, 0x3, 0x1, 0x00, 0x07, 360, 0x42, 203, 7, 256 },
+ { 6, 3240000, 3240000, 0x6, 0x3, 0x1, 0x00, 0x07, 480, 0x4C, 203, 7, 256 },
+ { 7, 3712500, 3712500, 0x4, 0x3, 0x0, 0x07, 0x0F, 550, 0x4C, 212, 7, 257 },
+ { 8, 3960000, 3960000, 0x5, 0x3, 0x0, 0x07, 0x0F, 440, 0x42, 184, 6, 226 },
+ { 9, 4320000, 4320000, 0x5, 0x3, 0x1, 0x07, 0x0F, 480, 0x42, 205, 7, 258 },
+ { 10, 4455000, 4455000, 0x5, 0x3, 0x0, 0x07, 0x0F, 495, 0x42, 219, 7, 272 },
+ { 10, 4455000, 4455000, 0x5, 0x3, 0x0, 0x07, 0x0F, 660, 0x4C, 219, 7, 272 },
+ { 11, 4950000, 4950000, 0x6, 0x3, 0x1, 0x00, 0x07, 550, 0x42, 213, 7, 258 },
+ { 12, 5940000, 5940000, 0x7, 0x3, 0x1, 0x00, 0x07, 660, 0x42, 244, 8, 292 },
+};
+
+/* HDMI TX PLL tuning settings, pixel clock is input */
+static const struct hdmi_pll_tuning imx8qm_pll_table[] = {
+/* bin VCO_freq min/max coar cod NDAC PMOS PTAT div-T P-Gain pad only */
+ { 0, 1700000, 2000000, 0x3, 0x1, 0x0, 0x8C, 0x2E, 300, 0x08D, 0, 0, 0 },
+ { 0, 1700000, 2000000, 0x3, 0x1, 0x0, 0x8C, 0x2E, 320, 0x08E, 0, 0, 0 },
+ { 0, 1700000, 2000000, 0x3, 0x1, 0x0, 0x8C, 0x2E, 325, 0x08E, 0, 0, 0 },
+ { 0, 1700000, 2000000, 0x3, 0x1, 0x0, 0x8C, 0x2E, 330, 0x08E, 0, 0, 0 },
+ { 0, 1700000, 2000000, 0x3, 0x1, 0x0, 0x8C, 0x2E, 340, 0x08F, 0, 0, 0 },
+ { 0, 1700000, 2000000, 0x3, 0x1, 0x0, 0x8C, 0x2E, 360, 0x0A7, 0, 0, 0 },
+ { 0, 1700000, 2000000, 0x3, 0x1, 0x0, 0x8C, 0x2E, 400, 0x0C5, 0, 0, 0 },
+ { 1, 2000000, 2400000, 0x3, 0x1, 0x0, 0x8C, 0x2E, 300, 0x086, 0, 0, 0 },
+ { 1, 2000000, 2400000, 0x3, 0x1, 0x0, 0x8C, 0x2E, 320, 0x087, 0, 0, 0 },
+ { 1, 2000000, 2400000, 0x3, 0x1, 0x0, 0x8C, 0x2E, 325, 0x087, 0, 0, 0 },
+ { 1, 2000000, 2400000, 0x3, 0x1, 0x0, 0x8C, 0x2E, 330, 0x104, 0, 0, 0 },
+ { 1, 2000000, 2400000, 0x3, 0x1, 0x0, 0x8C, 0x2E, 340, 0x08B, 0, 0, 0 },
+ { 1, 2000000, 2400000, 0x3, 0x1, 0x0, 0x8C, 0x2E, 360, 0x08D, 0, 0, 0 },
+ { 1, 2000000, 2400000, 0x3, 0x1, 0x0, 0x8C, 0x2E, 400, 0x0A6, 0, 0, 0 },
+ { 2, 2400000, 2800000, 0x3, 0x1, 0x0, 0x04, 0x0D, 300, 0x04E, 0, 0, 0 },
+ { 2, 2400000, 2800000, 0x3, 0x1, 0x0, 0x04, 0x0D, 320, 0x04F, 0, 0, 0 },
+ { 2, 2400000, 2800000, 0x3, 0x1, 0x0, 0x04, 0x0D, 325, 0x04F, 0, 0, 0 },
+ { 2, 2400000, 2800000, 0x3, 0x1, 0x0, 0x04, 0x0D, 330, 0x085, 0, 0, 0 },
+ { 2, 2400000, 2800000, 0x3, 0x1, 0x0, 0x04, 0x0D, 340, 0x085, 0, 0, 0 },
+ { 2, 2400000, 2800000, 0x3, 0x1, 0x0, 0x04, 0x0D, 360, 0x086, 0, 0, 0 },
+ { 2, 2400000, 2800000, 0x3, 0x1, 0x0, 0x04, 0x0D, 400, 0x08B, 0, 0, 0 },
+ { 3, 2800000, 3400000, 0x3, 0x1, 0x0, 0x04, 0x0D, 300, 0x047, 0, 0, 0 },
+ { 3, 2800000, 3400000, 0x3, 0x1, 0x0, 0x04, 0x0D, 320, 0x04B, 0, 0, 0 },
+ { 3, 2800000, 3400000, 0x3, 0x1, 0x0, 0x04, 0x0D, 325, 0x04B, 0, 0, 0 },
+ { 3, 2800000, 3400000, 0x3, 0x1, 0x0, 0x04, 0x0D, 330, 0x04B, 0, 0, 0 },
+ { 3, 2800000, 3400000, 0x3, 0x1, 0x0, 0x04, 0x0D, 340, 0x04D, 0, 0, 0 },
+ { 3, 2800000, 3400000, 0x3, 0x1, 0x0, 0x04, 0x0D, 360, 0x04E, 0, 0, 0 },
+ { 3, 2800000, 3400000, 0x3, 0x1, 0x0, 0x04, 0x0D, 400, 0x085, 0, 0, 0 },
+ { 4, 3400000, 3900000, 0x7, 0x1, 0x0, 0x8E, 0x2F, 375, 0x041, 0, 0, 0 },
+ { 4, 3400000, 3900000, 0x7, 0x1, 0x0, 0x8E, 0x2F, 600, 0x08D, 0, 0, 0 },
+ { 4, 3400000, 3900000, 0x7, 0x1, 0x0, 0x8E, 0x2F, 680, 0x0A6, 0, 0, 0 },
+ { 5, 3900000, 4500000, 0x7, 0x1, 0x0, 0x8E, 0x2F, 450, 0x041, 0, 0, 0 },
+ { 5, 3900000, 4500000, 0x7, 0x1, 0x0, 0x8E, 0x2F, 600, 0x087, 0, 0, 0 },
+ { 5, 3900000, 4500000, 0x7, 0x1, 0x0, 0x8E, 0x2F, 680, 0x0A4, 0, 0, 0 },
+ { 6, 4500000, 5200000, 0x7, 0x1, 0x0, 0x04, 0x0D, 600, 0x04F, 0, 0, 0 },
+ { 6, 4500000, 5200000, 0x7, 0x1, 0x0, 0x04, 0x0D, 680, 0x086, 0, 0, 0 },
+ { 7, 5200000, 6000000, 0x7, 0x1, 0x0, 0x04, 0x0D, 600, 0x04D, 0, 0, 0 },
+ { 7, 5200000, 6000000, 0x7, 0x1, 0x0, 0x04, 0x0D, 680, 0x04F, 0, 0, 0 }
+};
+
+static void hdmi_arc_config(struct cdns_mhdp_device *mhdp)
+{
+ u16 txpu_calib_code;
+ u16 txpd_calib_code;
+ u16 txpu_adj_calib_code;
+ u16 txpd_adj_calib_code;
+ u16 prev_calib_code;
+ u16 new_calib_code;
+ u16 rdata;
+
+ /* Power ARC */
+ cdns_phy_reg_write(mhdp, TXDA_CYA_AUXDA_CYA, 0x0001);
+
+ prev_calib_code = cdns_phy_reg_read(mhdp, TX_DIG_CTRL_REG_2);
+ txpu_calib_code = cdns_phy_reg_read(mhdp, CMN_TXPUCAL_CTRL);
+ txpd_calib_code = cdns_phy_reg_read(mhdp, CMN_TXPDCAL_CTRL);
+ txpu_adj_calib_code = cdns_phy_reg_read(mhdp, CMN_TXPU_ADJ_CTRL);
+ txpd_adj_calib_code = cdns_phy_reg_read(mhdp, CMN_TXPD_ADJ_CTRL);
+
+ new_calib_code = ((txpu_calib_code + txpd_calib_code) / 2)
+ + txpu_adj_calib_code + txpd_adj_calib_code;
+
+ if (new_calib_code != prev_calib_code) {
+ rdata = cdns_phy_reg_read(mhdp, TX_ANA_CTRL_REG_1);
+ rdata &= 0xDFFF;
+ cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_1, rdata);
+ cdns_phy_reg_write(mhdp, TX_DIG_CTRL_REG_2, new_calib_code);
+ mdelay(10);
+ rdata |= 0x2000;
+ cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_1, rdata);
+ udelay(150);
+ }
+
+ cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_2, 0x0100);
+ udelay(100);
+ cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_2, 0x0300);
+ udelay(100);
+ cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_3, 0x0000);
+ udelay(100);
+ cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_1, 0x2008);
+ udelay(100);
+ cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_1, 0x2018);
+ udelay(100);
+ cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_1, 0x2098);
+ cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_2, 0x030C);
+ cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_5, 0x0010);
+ udelay(100);
+ cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_4, 0x4001);
+ mdelay(5);
+ cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_1, 0x2198);
+ mdelay(5);
+ cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_2, 0x030D);
+ udelay(100);
+ cdns_phy_reg_write(mhdp, TX_ANA_CTRL_REG_2, 0x030F);
+}
+
+static void hdmi_phy_set_vswing(struct cdns_mhdp_device *mhdp)
+{
+ const u32 num_lanes = 4;
+ u32 k;
+
+ for (k = 0; k < num_lanes; k++) {
+ cdns_phy_reg_write(mhdp, (TX_DIAG_TX_DRV | (k << 9)), 0x7c0);
+ cdns_phy_reg_write(mhdp, (TX_TXCC_CPOST_MULT_00_0 | (k << 9)), 0x0);
+ cdns_phy_reg_write(mhdp, (TX_TXCC_CAL_SCLR_MULT_0 | (k << 9)), 0x120);
+ }
+}
+
+static int hdmi_feedback_factor(struct cdns_mhdp_device *mhdp)
+{
+ u32 feedback_factor;
+
+ switch (mhdp->video_info.color_fmt) {
+ case YCBCR_4_2_2:
+ feedback_factor = 1000;
+ break;
+ case YCBCR_4_2_0:
+ switch (mhdp->video_info.color_depth) {
+ case 8:
+ feedback_factor = 500;
+ break;
+ case 10:
+ feedback_factor = 625;
+ break;
+ case 12:
+ feedback_factor = 750;
+ break;
+ case 16:
+ feedback_factor = 1000;
+ break;
+ default:
+ DRM_ERROR("Invalid ColorDepth\n");
+ return 0;
+ }
+ break;
+ default:
+ /* Assume RGB/YUV444 */
+ switch (mhdp->video_info.color_depth) {
+ case 10:
+ feedback_factor = 1250;
+ break;
+ case 12:
+ feedback_factor = 1500;
+ break;
+ case 16:
+ feedback_factor = 2000;
+ break;
+ default:
+ feedback_factor = 1000;
+ }
+ }
+ return feedback_factor;
+}
+
+static int hdmi_phy_config(struct cdns_mhdp_device *mhdp,
+ const struct hdmi_ctrl *p_ctrl_table,
+ const struct hdmi_pll_tuning *p_pll_table,
+ char pclk_in)
+{
+ const u32 num_lanes = 4;
+ u32 val, i, k;
+
+ /* enable PHY isolation mode only for CMN */
+ cdns_phy_reg_write(mhdp, PHY_PMA_ISOLATION_CTRL, 0xD000);
+
+ /* set cmn_pll0_clk_datart1_div/cmn_pll0_clk_datart0_div dividers */
+ val = cdns_phy_reg_read(mhdp, PHY_PMA_ISO_PLL_CTRL1);
+ val &= 0xFF00;
+ val |= 0x0012;
+ cdns_phy_reg_write(mhdp, PHY_PMA_ISO_PLL_CTRL1, val);
+
+ /* assert PHY reset from isolation register */
+ cdns_phy_reg_write(mhdp, PHY_ISO_CMN_CTRL, 0x0000);
+ /* assert PMA CMN reset */
+ cdns_phy_reg_write(mhdp, PHY_PMA_ISO_CMN_CTRL, 0x0000);
+
+ /* register XCVR_DIAG_BIDI_CTRL */
+ for (k = 0; k < num_lanes; k++)
+ cdns_phy_reg_write(mhdp, XCVR_DIAG_BIDI_CTRL | (k << 9), 0x00FF);
+
+ /* Describing Task phy_cfg_hdp */
+
+ val = cdns_phy_reg_read(mhdp, PHY_PMA_CMN_CTRL1);
+ val &= 0xFFF7;
+ val |= 0x0008;
+ cdns_phy_reg_write(mhdp, PHY_PMA_CMN_CTRL1, val);
+
+ /* PHY Registers */
+ val = cdns_phy_reg_read(mhdp, PHY_PMA_CMN_CTRL1);
+ val &= 0xCFFF;
+ val |= p_ctrl_table->cmn_ref_clk_dig_div << 12;
+ cdns_phy_reg_write(mhdp, PHY_PMA_CMN_CTRL1, val);
+
+ val = cdns_phy_reg_read(mhdp, PHY_HDP_CLK_CTL);
+ val &= 0x00FF;
+ val |= 0x1200;
+ cdns_phy_reg_write(mhdp, PHY_HDP_CLK_CTL, val);
+
+ /* Common control module control and diagnostic registers */
+ val = cdns_phy_reg_read(mhdp, CMN_CDIAG_REFCLK_CTRL);
+ val &= 0x8FFF;
+ val |= p_ctrl_table->ref_clk_divider_scaler << 12;
+ val |= 0x00C0;
+ cdns_phy_reg_write(mhdp, CMN_CDIAG_REFCLK_CTRL, val);
+
+ /* High speed clock used */
+ val = cdns_phy_reg_read(mhdp, CMN_DIAG_HSCLK_SEL);
+ val &= 0xFF00;
+ val |= (p_ctrl_table->cmnda_hs_clk_0_sel >> 1) << 0;
+ val |= (p_ctrl_table->cmnda_hs_clk_1_sel >> 1) << 4;
+ cdns_phy_reg_write(mhdp, CMN_DIAG_HSCLK_SEL, val);
+
+ for (k = 0; k < num_lanes; k++) {
+ val = cdns_phy_reg_read(mhdp, (XCVR_DIAG_HSCLK_SEL | (k << 9)));
+ val &= 0xCFFF;
+ val |= (p_ctrl_table->cmnda_hs_clk_0_sel >> 1) << 12;
+ cdns_phy_reg_write(mhdp, (XCVR_DIAG_HSCLK_SEL | (k << 9)), val);
+ }
+
+ /* PLL 0 control state machine registers */
+ val = p_ctrl_table->vco_ring_select << 12;
+ cdns_phy_reg_write(mhdp, CMN_PLLSM0_USER_DEF_CTRL, val);
+
+ if (pclk_in == true)
+ val = 0x30A0;
+ else {
+ val = cdns_phy_reg_read(mhdp, CMN_PLL0_VCOCAL_START);
+ val &= 0xFE00;
+ val |= p_pll_table->vco_cal_code;
+ }
+ cdns_phy_reg_write(mhdp, CMN_PLL0_VCOCAL_START, val);
+
+ cdns_phy_reg_write(mhdp, CMN_PLL0_VCOCAL_INIT_TMR, 0x0064);
+ cdns_phy_reg_write(mhdp, CMN_PLL0_VCOCAL_ITER_TMR, 0x000A);
+
+ /* Common functions control and diagnostics registers */
+ val = p_ctrl_table->cmnda_pll0_hs_sym_div_sel << 8;
+ val |= p_ctrl_table->cmnda_pll0_ip_div;
+ cdns_phy_reg_write(mhdp, CMN_DIAG_PLL0_INCLK_CTRL, val);
+
+ cdns_phy_reg_write(mhdp, CMN_DIAG_PLL0_OVRD, 0x0000);
+
+ val = p_ctrl_table->cmnda_pll0_fb_div_high;
+ val |= (1 << 15);
+ cdns_phy_reg_write(mhdp, CMN_DIAG_PLL0_FBH_OVRD, val);
+
+ val = p_ctrl_table->cmnda_pll0_fb_div_low;
+ val |= (1 << 15);
+ cdns_phy_reg_write(mhdp, CMN_DIAG_PLL0_FBL_OVRD, val);
+
+ if (pclk_in == false) {
+ val = p_ctrl_table->cmnda_pll0_pxdiv_low;
+ cdns_phy_reg_write(mhdp, CMN_DIAG_PLL0_PXL_DIVL, val);
+
+ val = p_ctrl_table->cmnda_pll0_pxdiv_high;
+ val |= (1 << 15);
+ cdns_phy_reg_write(mhdp, CMN_DIAG_PLL0_PXL_DIVH, val);
+ }
+
+ val = p_pll_table->volt_to_current_coarse;
+ val |= (p_pll_table->volt_to_current) << 4;
+ cdns_phy_reg_write(mhdp, CMN_DIAG_PLL0_V2I_TUNE, val);
+
+ val = p_pll_table->charge_pump_gain;
+ cdns_phy_reg_write(mhdp, CMN_DIAG_PLL0_CP_TUNE, val);
+
+ cdns_phy_reg_write(mhdp, CMN_DIAG_PLL0_LF_PROG, 0x0008);
+
+ val = p_pll_table->pmos_ctrl;
+ val |= (p_pll_table->ndac_ctrl) << 8;
+ cdns_phy_reg_write(mhdp, CMN_DIAG_PLL0_PTATIS_TUNE1, val);
+
+ val = p_pll_table->ptat_ndac_ctrl;
+ cdns_phy_reg_write(mhdp, CMN_DIAG_PLL0_PTATIS_TUNE2, val);
+
+ if (pclk_in == true)
+ cdns_phy_reg_write(mhdp, CMN_DIAG_PLL0_TEST_MODE, 0x0022);
+ else
+ cdns_phy_reg_write(mhdp, CMN_DIAG_PLL0_TEST_MODE, 0x0020);
+ cdns_phy_reg_write(mhdp, CMN_PSM_CLK_CTRL, 0x0016);
+
+ /* Transceiver control and diagnostic registers */
+ for (k = 0; k < num_lanes; k++) {
+ val = cdns_phy_reg_read(mhdp, (XCVR_DIAG_PLLDRC_CTRL | (k << 9)));
+ val &= 0xBFFF;
+ cdns_phy_reg_write(mhdp, (XCVR_DIAG_PLLDRC_CTRL | (k << 9)), val);
+ }
+
+ for (k = 0; k < num_lanes; k++) {
+ val = cdns_phy_reg_read(mhdp, (TX_DIAG_TX_CTRL | (k << 9)));
+ val &= 0xFF3F;
+ val |= (p_ctrl_table->hsclk_div_tx_sub_rate >> 1) << 6;
+ cdns_phy_reg_write(mhdp, (TX_DIAG_TX_CTRL | (k << 9)), val);
+ }
+
+ /*
+ * for single ended reference clock val |= 0x0030;
+ * for differential clock val |= 0x0000;
+ */
+ val = cdns_phy_reg_read(mhdp, PHY_PMA_CMN_CTRL1);
+ val &= 0xFF8F;
+ if (pclk_in == true)
+ val |= 0x0030;
+ cdns_phy_reg_write(mhdp, PHY_PMA_CMN_CTRL1, val);
+
+ /* for differential clock on the refclk_p and
+ * refclk_m off chip pins: CMN_DIAG_ACYA[8]=1'b1 */
+ cdns_phy_reg_write(mhdp, CMN_DIAG_ACYA, 0x0100);
+
+ /* Deassert PHY reset */
+ cdns_phy_reg_write(mhdp, PHY_ISO_CMN_CTRL, 0x0001);
+ cdns_phy_reg_write(mhdp, PHY_PMA_ISO_CMN_CTRL, 0x0003);
+
+ /* Power state machine registers */
+ for (k = 0; k < num_lanes; k++)
+ cdns_phy_reg_write(mhdp, XCVR_PSM_RCTRL | (k << 9), 0xFEFC);
+
+ /* Assert cmn_macro_pwr_en */
+ cdns_phy_reg_write(mhdp, PHY_PMA_ISO_CMN_CTRL, 0x0013);
+
+ /* wait for cmn_macro_pwr_en_ack */
+ for (i = 0; i < 10; i++) {
+ val = cdns_phy_reg_read(mhdp, PHY_PMA_ISO_CMN_CTRL);
+ if (val & (1 << 5))
+ break;
+ msleep(20);
+ }
+ if (i == 10) {
+ DRM_ERROR("PMA ouput macro power up failed\n");
+ return false;
+ }
+
+ /* wait for cmn_ready */
+ for (i = 0; i < 10; i++) {
+ val = cdns_phy_reg_read(mhdp, PHY_PMA_CMN_CTRL1);
+ if (val & (1 << 0))
+ break;
+ msleep(20);
+ }
+ if (i == 10) {
+ DRM_ERROR("PMA output ready failed\n");
+ return false;
+ }
+
+ for (k = 0; k < num_lanes; k++) {
+ cdns_phy_reg_write(mhdp, TX_PSC_A0 | (k << 9), 0x6791);
+ cdns_phy_reg_write(mhdp, TX_PSC_A1 | (k << 9), 0x6790);
+ cdns_phy_reg_write(mhdp, TX_PSC_A2 | (k << 9), 0x0090);
+ cdns_phy_reg_write(mhdp, TX_PSC_A3 | (k << 9), 0x0090);
+
+ val = cdns_phy_reg_read(mhdp, RX_PSC_CAL | (k << 9));
+ val &= 0xFFBB;
+ cdns_phy_reg_write(mhdp, RX_PSC_CAL | (k << 9), val);
+
+ val = cdns_phy_reg_read(mhdp, RX_PSC_A0 | (k << 9));
+ val &= 0xFFBB;
+ cdns_phy_reg_write(mhdp, RX_PSC_A0 | (k << 9), val);
+ }
+ return true;
+}
+
+static int hdmi_phy_cfg_t28hpc(struct cdns_mhdp_device *mhdp,
+ struct drm_display_mode *mode)
+{
+ const struct hdmi_ctrl *p_ctrl_table;
+ const struct hdmi_pll_tuning *p_pll_table;
+ const u32 refclk_freq_khz = 27000;
+ const u8 pclk_in = false;
+ u32 pixel_freq = mode->clock;
+ u32 vco_freq, char_freq;
+ u32 div_total, feedback_factor;
+ u32 i, ret;
+
+ feedback_factor = hdmi_feedback_factor(mhdp);
+
+ char_freq = pixel_freq * feedback_factor / 1000;
+
+ DRM_INFO("Pixel clock: %d KHz, character clock: %d, bpc is %0d-bit, fmt %d\n",
+ pixel_freq, char_freq, mhdp->video_info.color_depth, mhdp->video_info.color_fmt);
+
+ /* Get right row from the ctrl_table table.
+ * Check if 'pixel_freq_khz' value matches the PIXEL_CLK_FREQ column.
+ * Consider only the rows with FEEDBACK_FACTOR column matching feedback_factor. */
+ for (i = 0; i < ARRAY_SIZE(imx8mq_ctrl_table); i++) {
+ if (feedback_factor == imx8mq_ctrl_table[i].feedback_factor &&
+ pixel_freq == imx8mq_ctrl_table[i].pixel_clk_freq_min) {
+ p_ctrl_table = &imx8mq_ctrl_table[i];
+ break;
+ }
+ }
+ if (i == ARRAY_SIZE(imx8mq_ctrl_table)) {
+ DRM_WARN("Pixel clk (%d KHz) not supported, color depth (%0d-bit)\n",
+ pixel_freq, mhdp->video_info.color_depth);
+ return 0;
+ }
+
+ div_total = p_ctrl_table->pll_fb_div_total;
+ vco_freq = refclk_freq_khz * div_total / p_ctrl_table->cmnda_pll0_ip_div;
+
+ /* Get right row from the imx8mq_pll_table table.
+ * Check if vco_freq_khz and feedback_div_total
+ * column matching with imx8mq_pll_table. */
+ for (i = 0; i < ARRAY_SIZE(imx8mq_pll_table); i++) {
+ if (vco_freq == imx8mq_pll_table[i].vco_freq_min &&
+ div_total == imx8mq_pll_table[i].feedback_div_total) {
+ p_pll_table = &imx8mq_pll_table[i];
+ break;
+ }
+ }
+ if (i == ARRAY_SIZE(imx8mq_pll_table)) {
+ DRM_WARN("VCO (%d KHz) not supported\n", vco_freq);
+ return 0;
+ }
+ DRM_INFO("VCO frequency is %d KHz\n", vco_freq);
+
+ ret = hdmi_phy_config(mhdp, p_ctrl_table, p_pll_table, pclk_in);
+ if (ret == false)
+ return 0;
+
+ return char_freq;
+}
+
+static int hdmi_phy_cfg_ss28fdsoi(struct cdns_mhdp_device *mhdp,
+ struct drm_display_mode *mode)
+{
+ const struct hdmi_ctrl *p_ctrl_table;
+ const struct hdmi_pll_tuning *p_pll_table;
+ const u8 pclk_in = true;
+ u32 pixel_freq = mode->clock;
+ u32 vco_freq, char_freq;
+ u32 div_total, feedback_factor;
+ u32 ret, i;
+
+ feedback_factor = hdmi_feedback_factor(mhdp);
+
+ char_freq = pixel_freq * feedback_factor / 1000;
+
+ DRM_INFO("Pixel clock: %d KHz, character clock: %d, bpc is %0d-bit.\n",
+ pixel_freq, char_freq, mhdp->video_info.color_depth);
+
+ /* Get right row from the ctrl_table table.
+ * Check if 'pixel_freq_khz' value matches the PIXEL_CLK_FREQ column.
+ * Consider only the rows with FEEDBACK_FACTOR column matching feedback_factor. */
+ for (i = 0; i < ARRAY_SIZE(imx8qm_ctrl_table); i++) {
+ if (feedback_factor == imx8qm_ctrl_table[i].feedback_factor &&
+ pixel_freq >= imx8qm_ctrl_table[i].pixel_clk_freq_min &&
+ pixel_freq <= imx8qm_ctrl_table[i].pixel_clk_freq_max) {
+ p_ctrl_table = &imx8qm_ctrl_table[i];
+ break;
+ }
+ }
+ if (i == ARRAY_SIZE(imx8qm_ctrl_table)) {
+ DRM_WARN("Pixel clk (%d KHz) not supported, color depth (%0d-bit)\n",
+ pixel_freq, mhdp->video_info.color_depth);
+ return 0;
+ }
+
+ div_total = p_ctrl_table->pll_fb_div_total;
+ vco_freq = pixel_freq * div_total / p_ctrl_table->cmnda_pll0_ip_div;
+
+ /* Get right row from the imx8mq_pll_table table.
+ * Check if vco_freq_khz and feedback_div_total
+ * column matching with imx8mq_pll_table. */
+ for (i = 0; i < ARRAY_SIZE(imx8qm_pll_table); i++) {
+ if (vco_freq >= imx8qm_pll_table[i].vco_freq_min &&
+ vco_freq < imx8qm_pll_table[i].vco_freq_max &&
+ div_total == imx8qm_pll_table[i].feedback_div_total) {
+ p_pll_table = &imx8qm_pll_table[i];
+ break;
+ }
+ }
+ if (i == ARRAY_SIZE(imx8qm_pll_table)) {
+ DRM_WARN("VCO (%d KHz) not supported\n", vco_freq);
+ return 0;
+ }
+ DRM_INFO("VCO frequency is %d KHz\n", vco_freq);
+
+ ret = hdmi_phy_config(mhdp, p_ctrl_table, p_pll_table, pclk_in);
+ if (ret == false)
+ return 0;
+
+ return char_freq;
+}
+
+static int hdmi_arc_power_up(struct cdns_mhdp_device *mhdp)
+{
+ u32 val, i;
+
+ /* set Power State to A2 */
+ cdns_phy_reg_write(mhdp, PHY_HDP_MODE_CTRL, 0x0004);
+
+ cdns_phy_reg_write(mhdp, TX_DIAG_ACYA_0, 1);
+ cdns_phy_reg_write(mhdp, TX_DIAG_ACYA_1, 1);
+ cdns_phy_reg_write(mhdp, TX_DIAG_ACYA_2, 1);
+ cdns_phy_reg_write(mhdp, TX_DIAG_ACYA_3, 1);
+
+ /* Wait for Power State A2 Ack */
+ for (i = 0; i < 10; i++) {
+ val = cdns_phy_reg_read(mhdp, PHY_HDP_MODE_CTRL);
+ if (val & (1 << 6))
+ break;
+ msleep(20);
+ }
+ if (i == 10) {
+ dev_err(mhdp->dev, "Wait A2 Ack failed\n");
+ return -1;
+ }
+
+ /* Power up ARC */
+ hdmi_arc_config(mhdp);
+
+ return 0;
+}
+
+bool cdns_hdmi_phy_video_valid_imx8mq(struct cdns_mhdp_device *mhdp)
+{
+ u32 rate = mhdp->valid_mode->clock;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(imx8mq_ctrl_table); i++)
+ if(rate == imx8mq_ctrl_table[i].pixel_clk_freq_min)
+ return true;
+ return false;
+}
+
+int cdns_hdmi_phy_set_imx8mq(struct cdns_mhdp_device *mhdp)
+{
+ struct drm_display_mode *mode = &mhdp->mode;
+ int ret;
+
+ /* Check HDMI FW alive before HDMI PHY init */
+ ret = cdns_mhdp_check_alive(mhdp);
+ if (ret == false) {
+ DRM_ERROR("NO HDMI FW running\n");
+ return -ENXIO;
+ }
+
+ /* Configure PHY */
+ mhdp->hdmi.char_rate = hdmi_phy_cfg_t28hpc(mhdp, mode);
+ if (mhdp->hdmi.char_rate == 0) {
+ DRM_ERROR("failed to set phy pclock\n");
+ return -EINVAL;
+ }
+
+ ret = hdmi_arc_power_up(mhdp);
+ if (ret < 0)
+ return ret;
+
+ hdmi_phy_set_vswing(mhdp);
+
+ return true;
+}
+
+bool cdns_hdmi_phy_video_valid_imx8qm(struct cdns_mhdp_device *mhdp)
+{
+ u32 rate = mhdp->valid_mode->clock;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(imx8qm_ctrl_table); i++)
+ if(rate >= imx8qm_ctrl_table[i].pixel_clk_freq_min &&
+ rate <= imx8qm_ctrl_table[i].pixel_clk_freq_max)
+ return true;
+ return false;
+}
+
+int cdns_hdmi_phy_set_imx8qm(struct cdns_mhdp_device *mhdp)
+{
+ struct drm_display_mode *mode = &mhdp->mode;
+ int ret;
+
+ /* Check HDMI FW alive before HDMI PHY init */
+ ret = cdns_mhdp_check_alive(mhdp);
+ if (ret == false) {
+ DRM_ERROR("NO HDMI FW running\n");
+ return -ENXIO;
+ }
+ imx8qm_phy_reset(0);
+
+ /* Configure PHY */
+ mhdp->hdmi.char_rate = hdmi_phy_cfg_ss28fdsoi(mhdp, mode);
+ if (mhdp->hdmi.char_rate == 0) {
+ DRM_ERROR("failed to set phy pclock\n");
+ return -EINVAL;
+ }
+ imx8qm_phy_reset(1);
+
+ ret = hdmi_arc_power_up(mhdp);
+ if (ret < 0)
+ return ret;
+
+ hdmi_phy_set_vswing(mhdp);
+
+ return true;
+}
+
+int cdns_hdmi_phy_power_up(struct cdns_mhdp_device *mhdp)
+{
+ u32 val, i;
+
+ /* Configure PHY in A0 mode (PHY must be in the A0 power
+ * state in order to transmit data)
+ */
+ cdns_phy_reg_write(mhdp, PHY_HDP_MODE_CTRL, 0x0001);
+
+ /* Wait for Power State A0 Ack */
+ for (i = 0; i < 10; i++) {
+ val = cdns_phy_reg_read(mhdp, PHY_HDP_MODE_CTRL);
+ if (val & (1 << 4))
+ break;
+ msleep(20);
+ }
+ if (i == 10) {
+ dev_err(mhdp->dev, "Wait A0 Ack failed\n");
+ return -1;
+ }
+ return 0;
+}
+
+int cdns_hdmi_phy_shutdown(struct cdns_mhdp_device *mhdp)
+{
+ int timeout;
+ u32 reg_val;
+
+ reg_val = cdns_phy_reg_read(mhdp, PHY_HDP_MODE_CTRL);
+ reg_val &= 0xfff0;
+ /* PHY_DP_MODE_CTL set to A3 power state*/
+ cdns_phy_reg_write(mhdp, PHY_HDP_MODE_CTRL, reg_val | 0x8);
+
+ /* PHY_DP_MODE_CTL */
+ timeout = 0;
+ do {
+ reg_val = cdns_phy_reg_read(mhdp, PHY_HDP_MODE_CTRL);
+ DRM_INFO("Reg val is 0x%04x\n", reg_val);
+ timeout++;
+ msleep(100);
+ } while (!(reg_val & (0x8 << 4)) && (timeout < 10)); /* Wait for A3 acknowledge */
+
+ DRM_INFO("hdmi phy shutdown complete\n");
+ return 0;
+}
diff --git a/drivers/gpu/drm/imx/mhdp/cdns-mhdp-imx.h b/drivers/gpu/drm/imx/mhdp/cdns-mhdp-imx.h
new file mode 100644
index 000000000000..e5ec3cc4073e
--- /dev/null
+++ b/drivers/gpu/drm/imx/mhdp/cdns-mhdp-imx.h
@@ -0,0 +1,76 @@
+/*
+ * Cadence High-Definition Multimedia Interface (HDMI) driver
+ *
+ * Copyright 2019-2021 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+#ifndef CDNS_MHDP_IMX_H_
+#define CDNS_MHDP_IMX_H_
+
+#include <drm/bridge/cdns-mhdp.h>
+#include <drm/drm_encoder_slave.h>
+
+
+struct imx_mhdp_device;
+
+struct imx_hdp_clks {
+ struct clk *av_pll;
+ struct clk *dig_pll;
+ struct clk *clk_ipg;
+ struct clk *clk_core;
+ struct clk *clk_pxl;
+ struct clk *clk_pxl_mux;
+ struct clk *clk_pxl_link;
+
+ struct clk *lpcg_hdp;
+ struct clk *lpcg_msi;
+ struct clk *lpcg_pxl;
+ struct clk *lpcg_vif;
+ struct clk *lpcg_lis;
+ struct clk *lpcg_apb;
+ struct clk *lpcg_apb_csr;
+ struct clk *lpcg_apb_ctrl;
+
+ struct clk *lpcg_i2s;
+ struct clk *clk_i2s_bypass;
+};
+
+struct imx_mhdp_device {
+ struct cdns_mhdp_device mhdp;
+ struct drm_encoder encoder;
+
+ struct mutex audio_mutex;
+ spinlock_t audio_lock;
+ bool connected;
+ bool active;
+ bool suspended;
+ struct imx_hdp_clks clks;
+ const struct firmware *fw;
+ const char *firmware_name;
+
+ int bus_type;
+
+ struct device *pd_mhdp_dev;
+ struct device *pd_pll0_dev;
+ struct device *pd_pll1_dev;
+ struct device_link *pd_mhdp_link;
+ struct device_link *pd_pll0_link;
+ struct device_link *pd_pll1_link;
+};
+
+void cdns_mhdp_plat_init_imx8qm(struct cdns_mhdp_device *mhdp);
+void cdns_mhdp_plat_deinit_imx8qm(struct cdns_mhdp_device *mhdp);
+void cdns_mhdp_pclk_rate_imx8qm(struct cdns_mhdp_device *mhdp);
+int cdns_mhdp_firmware_init_imx8qm(struct cdns_mhdp_device *mhdp);
+int cdns_mhdp_resume_imx8qm(struct cdns_mhdp_device *mhdp);
+int cdns_mhdp_suspend_imx8qm(struct cdns_mhdp_device *mhdp);
+int cdns_mhdp_power_on_imx8qm(struct cdns_mhdp_device *mhdp);
+int cdns_mhdp_power_on_ls1028a(struct cdns_mhdp_device *mhdp);
+void cdns_mhdp_pclk_rate_ls1028a(struct cdns_mhdp_device *mhdp);
+void imx8qm_phy_reset(u8 reset);
+#endif /* CDNS_MHDP_IMX_H_ */
diff --git a/drivers/gpu/drm/imx/mhdp/cdns-mhdp-imx8qm.c b/drivers/gpu/drm/imx/mhdp/cdns-mhdp-imx8qm.c
new file mode 100644
index 000000000000..4fca888c3991
--- /dev/null
+++ b/drivers/gpu/drm/imx/mhdp/cdns-mhdp-imx8qm.c
@@ -0,0 +1,642 @@
+/*
+ * Copyright 2019-2022 NXP
+ *
+ * this program is free software; you can redistribute it and/or modify
+ * it under the terms of the gnu general public license version 2 as
+ * published by the free software foundation.
+ */
+#include <dt-bindings/firmware/imx/rsrc.h>
+#include <linux/firmware/imx/sci.h>
+#include <linux/firmware.h>
+#include <linux/pm_domain.h>
+#include <linux/clk.h>
+#include <drm/drm_vblank.h>
+#include <drm/drm_print.h>
+
+#include "cdns-mhdp-imx.h"
+
+#define FW_IRAM_OFFSET 0x2000
+#define FW_IRAM_SIZE 0x10000
+#define FW_DRAM_SIZE 0x8000
+
+#define PLL_800MHZ (800000000)
+
+#define HDP_DUAL_MODE_MIN_PCLK_RATE 300000 /* KHz */
+#define HDP_SINGLE_MODE_MAX_WIDTH 2560
+
+#define CSR_PIXEL_LINK_MUX_CTL 0x00
+#define CSR_PIXEL_LINK_MUX_VCP_OFFSET 5
+#define CSR_PIXEL_LINK_MUX_HCP_OFFSET 4
+
+static bool imx8qm_video_dual_mode(struct cdns_mhdp_device *mhdp)
+{
+ struct drm_display_mode *mode = &mhdp->mode;
+ return (mode->clock > HDP_DUAL_MODE_MIN_PCLK_RATE ||
+ mode->hdisplay > HDP_SINGLE_MODE_MAX_WIDTH) ? true : false;
+}
+
+static void imx8qm_pixel_link_mux(struct imx_mhdp_device *imx_mhdp)
+{
+ struct drm_display_mode *mode = &imx_mhdp->mhdp.mode;
+ bool dual_mode;
+ u32 val;
+
+ dual_mode = imx8qm_video_dual_mode(&imx_mhdp->mhdp);
+
+ val = 0x4; /* RGB */
+ if (dual_mode)
+ val |= 0x2; /* pixel link 0 and 1 are active */
+ if (mode->flags & DRM_MODE_FLAG_PVSYNC)
+ val |= 1 << CSR_PIXEL_LINK_MUX_VCP_OFFSET;
+ if (mode->flags & DRM_MODE_FLAG_PHSYNC)
+ val |= 1 << CSR_PIXEL_LINK_MUX_HCP_OFFSET;
+ if (mode->flags & DRM_MODE_FLAG_INTERLACE)
+ val |= 0x2;
+
+ writel(val, imx_mhdp->mhdp.regs_sec);
+}
+
+static void imx8qm_pixel_link_valid(u32 dual_mode)
+{
+ struct imx_sc_ipc *handle;
+
+ imx_scu_get_handle(&handle);
+
+ imx_sc_misc_set_control(handle, IMX_SC_R_DC_0, IMX_SC_C_PXL_LINK_MST1_VLD, 1);
+ if (dual_mode)
+ imx_sc_misc_set_control(handle, IMX_SC_R_DC_0, IMX_SC_C_PXL_LINK_MST2_VLD, 1);
+}
+
+static void imx8qm_pixel_link_invalid(u32 dual_mode)
+{
+ struct imx_sc_ipc *handle;
+
+ imx_scu_get_handle(&handle);
+
+ imx_sc_misc_set_control(handle, IMX_SC_R_DC_0, IMX_SC_C_PXL_LINK_MST1_VLD, 0);
+ if (dual_mode)
+ imx_sc_misc_set_control(handle, IMX_SC_R_DC_0, IMX_SC_C_PXL_LINK_MST2_VLD, 0);
+}
+
+static void imx8qm_pixel_link_sync_enable(u32 dual_mode)
+{
+ struct imx_sc_ipc *handle;
+
+ imx_scu_get_handle(&handle);
+
+ if (dual_mode)
+ imx_sc_misc_set_control(handle, IMX_SC_R_DC_0, IMX_SC_C_SYNC_CTRL, 3);
+ else
+ imx_sc_misc_set_control(handle, IMX_SC_R_DC_0, IMX_SC_C_SYNC_CTRL0, 1);
+}
+
+static void imx8qm_pixel_link_sync_disable(u32 dual_mode)
+{
+ struct imx_sc_ipc *handle;
+
+ imx_scu_get_handle(&handle);
+
+ if (dual_mode)
+ imx_sc_misc_set_control(handle, IMX_SC_R_DC_0, IMX_SC_C_SYNC_CTRL, 0);
+ else
+ imx_sc_misc_set_control(handle, IMX_SC_R_DC_0, IMX_SC_C_SYNC_CTRL0, 0);
+}
+
+void imx8qm_phy_reset(u8 reset)
+{
+ struct imx_sc_ipc *handle;
+
+ imx_scu_get_handle(&handle);
+
+ /* set the pixel link mode and pixel type */
+ imx_sc_misc_set_control(handle, IMX_SC_R_HDMI, IMX_SC_C_PHY_RESET, reset);
+}
+
+static void imx8qm_clk_mux(u8 is_dp)
+{
+ struct imx_sc_ipc *handle;
+
+ imx_scu_get_handle(&handle);
+
+ if (is_dp)
+ /* Enable the 24MHz for HDP PHY */
+ imx_sc_misc_set_control(handle, IMX_SC_R_HDMI, IMX_SC_C_MODE, 1);
+ else
+ imx_sc_misc_set_control(handle, IMX_SC_R_HDMI, IMX_SC_C_MODE, 0);
+}
+
+int imx8qm_clocks_init(struct imx_mhdp_device *imx_mhdp)
+{
+ struct device *dev = imx_mhdp->mhdp.dev;
+ struct imx_hdp_clks *clks = &imx_mhdp->clks;
+
+ clks->dig_pll = devm_clk_get(dev, "dig_pll");
+ if (IS_ERR(clks->dig_pll)) {
+ dev_warn(dev, "failed to get dig pll clk\n");
+ return PTR_ERR(clks->dig_pll);
+ }
+
+ clks->av_pll = devm_clk_get(dev, "av_pll");
+ if (IS_ERR(clks->av_pll)) {
+ dev_warn(dev, "failed to get av pll clk\n");
+ return PTR_ERR(clks->av_pll);
+ }
+
+ clks->clk_ipg = devm_clk_get(dev, "clk_ipg");
+ if (IS_ERR(clks->clk_ipg)) {
+ dev_warn(dev, "failed to get dp ipg clk\n");
+ return PTR_ERR(clks->clk_ipg);
+ }
+
+ clks->clk_core = devm_clk_get(dev, "clk_core");
+ if (IS_ERR(clks->clk_core)) {
+ dev_warn(dev, "failed to get hdp core clk\n");
+ return PTR_ERR(clks->clk_core);
+ }
+
+ clks->clk_pxl = devm_clk_get(dev, "clk_pxl");
+ if (IS_ERR(clks->clk_pxl)) {
+ dev_warn(dev, "failed to get pxl clk\n");
+ return PTR_ERR(clks->clk_pxl);
+ }
+
+ clks->clk_pxl_mux = devm_clk_get(dev, "clk_pxl_mux");
+ if (IS_ERR(clks->clk_pxl_mux)) {
+ dev_warn(dev, "failed to get pxl mux clk\n");
+ return PTR_ERR(clks->clk_pxl_mux);
+ }
+
+ clks->clk_pxl_link = devm_clk_get(dev, "clk_pxl_link");
+ if (IS_ERR(clks->clk_pxl_link)) {
+ dev_warn(dev, "failed to get pxl link clk\n");
+ return PTR_ERR(clks->clk_pxl_link);
+ }
+
+ clks->lpcg_hdp = devm_clk_get(dev, "lpcg_hdp");
+ if (IS_ERR(clks->lpcg_hdp)) {
+ dev_warn(dev, "failed to get lpcg hdp clk\n");
+ return PTR_ERR(clks->lpcg_hdp);
+ }
+
+ clks->lpcg_msi = devm_clk_get(dev, "lpcg_msi");
+ if (IS_ERR(clks->lpcg_msi)) {
+ dev_warn(dev, "failed to get lpcg msi clk\n");
+ return PTR_ERR(clks->lpcg_msi);
+ }
+
+ clks->lpcg_pxl = devm_clk_get(dev, "lpcg_pxl");
+ if (IS_ERR(clks->lpcg_pxl)) {
+ dev_warn(dev, "failed to get lpcg pxl clk\n");
+ return PTR_ERR(clks->lpcg_pxl);
+ }
+
+ clks->lpcg_vif = devm_clk_get(dev, "lpcg_vif");
+ if (IS_ERR(clks->lpcg_vif)) {
+ dev_warn(dev, "failed to get lpcg vif clk\n");
+ return PTR_ERR(clks->lpcg_vif);
+ }
+
+ clks->lpcg_lis = devm_clk_get(dev, "lpcg_lis");
+ if (IS_ERR(clks->lpcg_lis)) {
+ dev_warn(dev, "failed to get lpcg lis clk\n");
+ return PTR_ERR(clks->lpcg_lis);
+ }
+
+ clks->lpcg_apb = devm_clk_get(dev, "lpcg_apb");
+ if (IS_ERR(clks->lpcg_apb)) {
+ dev_warn(dev, "failed to get lpcg apb clk\n");
+ return PTR_ERR(clks->lpcg_apb);
+ }
+
+ clks->lpcg_apb_csr = devm_clk_get(dev, "lpcg_apb_csr");
+ if (IS_ERR(clks->lpcg_apb_csr)) {
+ dev_warn(dev, "failed to get apb csr clk\n");
+ return PTR_ERR(clks->lpcg_apb_csr);
+ }
+
+ clks->lpcg_apb_ctrl = devm_clk_get(dev, "lpcg_apb_ctrl");
+ if (IS_ERR(clks->lpcg_apb_ctrl)) {
+ dev_warn(dev, "failed to get lpcg apb ctrl clk\n");
+ return PTR_ERR(clks->lpcg_apb_ctrl);
+ }
+
+ clks->clk_i2s_bypass = devm_clk_get(dev, "clk_i2s_bypass");
+ if (IS_ERR(clks->clk_i2s_bypass)) {
+ dev_err(dev, "failed to get i2s bypass clk\n");
+ return PTR_ERR(clks->clk_i2s_bypass);
+ }
+
+ clks->lpcg_i2s = devm_clk_get(dev, "lpcg_i2s");
+ if (IS_ERR(clks->lpcg_i2s)) {
+ dev_err(dev, "failed to get lpcg i2s clk\n");
+ return PTR_ERR(clks->lpcg_i2s);
+ }
+ return true;
+}
+
+static int imx8qm_pixel_clk_enable(struct imx_mhdp_device *imx_mhdp)
+{
+ struct imx_hdp_clks *clks = &imx_mhdp->clks;
+ struct device *dev = imx_mhdp->mhdp.dev;
+ int ret;
+
+ ret = clk_prepare_enable(clks->av_pll);
+ if (ret < 0) {
+ dev_err(dev, "%s, pre av pll error\n", __func__);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(clks->clk_pxl);
+ if (ret < 0) {
+ dev_err(dev, "%s, pre clk pxl error\n", __func__);
+ return ret;
+ }
+ ret = clk_prepare_enable(clks->clk_pxl_mux);
+ if (ret < 0) {
+ dev_err(dev, "%s, pre clk pxl mux error\n", __func__);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(clks->clk_pxl_link);
+ if (ret < 0) {
+ dev_err(dev, "%s, pre clk pxl link error\n", __func__);
+ return ret;
+ }
+ ret = clk_prepare_enable(clks->lpcg_vif);
+ if (ret < 0) {
+ dev_err(dev, "%s, pre clk vif error\n", __func__);
+ return ret;
+ }
+ ret = clk_prepare_enable(clks->lpcg_pxl);
+ if (ret < 0) {
+ dev_err(dev, "%s, pre lpcg pxl error\n", __func__);
+ return ret;
+ }
+ ret = clk_prepare_enable(clks->lpcg_hdp);
+ if (ret < 0) {
+ dev_err(dev, "%s, pre lpcg hdp error\n", __func__);
+ return ret;
+ }
+ return ret;
+}
+
+static void imx8qm_pixel_clk_disable(struct imx_mhdp_device *imx_mhdp)
+{
+ struct imx_hdp_clks *clks = &imx_mhdp->clks;
+
+ clk_disable_unprepare(clks->lpcg_pxl);
+ clk_disable_unprepare(clks->lpcg_hdp);
+ clk_disable_unprepare(clks->lpcg_vif);
+ clk_disable_unprepare(clks->clk_pxl);
+ clk_disable_unprepare(clks->clk_pxl_link);
+ clk_disable_unprepare(clks->clk_pxl_mux);
+ clk_disable_unprepare(clks->av_pll);
+}
+
+static void imx8qm_pixel_clk_set_rate(struct imx_mhdp_device *imx_mhdp, u32 pclock)
+{
+ bool dual_mode = imx8qm_video_dual_mode(&imx_mhdp->mhdp);
+ struct imx_hdp_clks *clks = &imx_mhdp->clks;
+
+ /* pixel clock for HDMI */
+ clk_set_rate(clks->av_pll, pclock);
+
+ if (dual_mode == true) {
+ clk_set_rate(clks->clk_pxl, pclock/2);
+ clk_set_rate(clks->clk_pxl_link, pclock/2);
+ } else {
+ clk_set_rate(clks->clk_pxl_link, pclock);
+ clk_set_rate(clks->clk_pxl, pclock);
+ }
+ clk_set_rate(clks->clk_pxl_mux, pclock);
+}
+
+static int imx8qm_ipg_clk_enable(struct imx_mhdp_device *imx_mhdp)
+{
+ int ret;
+ struct imx_hdp_clks *clks = &imx_mhdp->clks;
+ struct device *dev = imx_mhdp->mhdp.dev;
+
+ ret = clk_prepare_enable(clks->dig_pll);
+ if (ret < 0) {
+ dev_err(dev, "%s, pre dig pll error\n", __func__);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(clks->clk_ipg);
+ if (ret < 0) {
+ dev_err(dev, "%s, pre clk_ipg error\n", __func__);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(clks->clk_core);
+ if (ret < 0) {
+ dev_err(dev, "%s, pre clk core error\n", __func__);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(clks->lpcg_apb);
+ if (ret < 0) {
+ dev_err(dev, "%s, pre clk apb error\n", __func__);
+ return ret;
+ }
+ ret = clk_prepare_enable(clks->lpcg_lis);
+ if (ret < 0) {
+ dev_err(dev, "%s, pre clk lis error\n", __func__);
+ return ret;
+ }
+ ret = clk_prepare_enable(clks->lpcg_msi);
+ if (ret < 0) {
+ dev_err(dev, "%s, pre clk msierror\n", __func__);
+ return ret;
+ }
+ ret = clk_prepare_enable(clks->lpcg_apb_csr);
+ if (ret < 0) {
+ dev_err(dev, "%s, pre clk apb csr error\n", __func__);
+ return ret;
+ }
+ ret = clk_prepare_enable(clks->lpcg_apb_ctrl);
+ if (ret < 0) {
+ dev_err(dev, "%s, pre clk apb ctrl error\n", __func__);
+ return ret;
+ }
+ ret = clk_prepare_enable(clks->lpcg_i2s);
+ if (ret < 0) {
+ dev_err(dev, "%s, pre clk i2s error\n", __func__);
+ return ret;
+ }
+ ret = clk_prepare_enable(clks->clk_i2s_bypass);
+ if (ret < 0) {
+ dev_err(dev, "%s, pre clk i2s bypass error\n", __func__);
+ return ret;
+ }
+ return ret;
+}
+
+static void imx8qm_ipg_clk_set_rate(struct imx_mhdp_device *imx_mhdp)
+{
+ struct imx_hdp_clks *clks = &imx_mhdp->clks;
+
+ /* ipg/core clock */
+ clk_set_rate(clks->dig_pll, PLL_800MHZ);
+ clk_set_rate(clks->clk_core, PLL_800MHZ/4);
+ clk_set_rate(clks->clk_ipg, PLL_800MHZ/8);
+}
+
+static void imx8qm_detach_pm_domains(struct imx_mhdp_device *imx_mhdp)
+{
+ if (imx_mhdp->pd_pll1_link && !IS_ERR(imx_mhdp->pd_pll1_link))
+ device_link_del(imx_mhdp->pd_pll1_link);
+ if (imx_mhdp->pd_pll1_dev && !IS_ERR(imx_mhdp->pd_pll1_dev))
+ dev_pm_domain_detach(imx_mhdp->pd_pll1_dev, true);
+
+ if (imx_mhdp->pd_pll0_link && !IS_ERR(imx_mhdp->pd_pll0_link))
+ device_link_del(imx_mhdp->pd_pll0_link);
+ if (imx_mhdp->pd_pll0_dev && !IS_ERR(imx_mhdp->pd_pll0_dev))
+ dev_pm_domain_detach(imx_mhdp->pd_pll0_dev, true);
+
+ if (imx_mhdp->pd_mhdp_link && !IS_ERR(imx_mhdp->pd_mhdp_link))
+ device_link_del(imx_mhdp->pd_mhdp_link);
+ if (imx_mhdp->pd_mhdp_dev && !IS_ERR(imx_mhdp->pd_mhdp_dev))
+ dev_pm_domain_detach(imx_mhdp->pd_mhdp_dev, true);
+
+ imx_mhdp->pd_mhdp_dev = NULL;
+ imx_mhdp->pd_mhdp_link = NULL;
+ imx_mhdp->pd_pll0_dev = NULL;
+ imx_mhdp->pd_pll0_link = NULL;
+ imx_mhdp->pd_pll1_dev = NULL;
+ imx_mhdp->pd_pll1_link = NULL;
+}
+
+static int imx8qm_attach_pm_domains(struct imx_mhdp_device *imx_mhdp)
+{
+ struct device *dev = imx_mhdp->mhdp.dev;
+ u32 flags = DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME | DL_FLAG_RPM_ACTIVE;
+ int ret = 0;
+
+ imx_mhdp->pd_mhdp_dev = dev_pm_domain_attach_by_name(dev, "hdmi");
+ if (IS_ERR(imx_mhdp->pd_mhdp_dev)) {
+ ret = PTR_ERR(imx_mhdp->pd_mhdp_dev);
+ dev_err(dev, "Failed to attach dc pd dev: %d\n", ret);
+ goto fail;
+ }
+ imx_mhdp->pd_mhdp_link = device_link_add(dev, imx_mhdp->pd_mhdp_dev, flags);
+ if (IS_ERR(imx_mhdp->pd_mhdp_link)) {
+ ret = PTR_ERR(imx_mhdp->pd_mhdp_link);
+ dev_err(dev, "Failed to add device link to dc pd dev: %d\n",
+ ret);
+ goto fail;
+ }
+
+ imx_mhdp->pd_pll0_dev = dev_pm_domain_attach_by_name(dev, "pll0");
+ if (IS_ERR(imx_mhdp->pd_pll0_dev)) {
+ ret = PTR_ERR(imx_mhdp->pd_pll0_dev);
+ dev_err(dev, "Failed to attach pll0 pd dev: %d\n", ret);
+ goto fail;
+ }
+ imx_mhdp->pd_pll0_link = device_link_add(dev, imx_mhdp->pd_pll0_dev, flags);
+ if (IS_ERR(imx_mhdp->pd_pll0_link)) {
+ ret = PTR_ERR(imx_mhdp->pd_pll0_link);
+ dev_err(dev, "Failed to add device link to pll0 pd dev: %d\n",
+ ret);
+ goto fail;
+ }
+
+ imx_mhdp->pd_pll1_dev = dev_pm_domain_attach_by_name(dev, "pll1");
+ if (IS_ERR(imx_mhdp->pd_pll1_dev)) {
+ ret = PTR_ERR(imx_mhdp->pd_pll1_dev);
+ dev_err(dev, "Failed to attach pll0 pd dev: %d\n", ret);
+ goto fail;
+ }
+ imx_mhdp->pd_pll1_link = device_link_add(dev, imx_mhdp->pd_pll1_dev, flags);
+ if (IS_ERR(imx_mhdp->pd_pll1_link)) {
+ ret = PTR_ERR(imx_mhdp->pd_pll1_link);
+ dev_err(dev, "Failed to add device link to pll1 pd dev: %d\n",
+ ret);
+ goto fail;
+ }
+fail:
+ imx8qm_detach_pm_domains(imx_mhdp);
+ return ret;
+}
+
+int cdns_mhdp_power_on_imx8qm(struct cdns_mhdp_device *mhdp)
+{
+ struct imx_mhdp_device *imx_mhdp =
+ container_of(mhdp, struct imx_mhdp_device, mhdp);
+ /* Power on PM Domains */
+
+ imx8qm_attach_pm_domains(imx_mhdp);
+
+ /* clock init and rate set */
+ imx8qm_clocks_init(imx_mhdp);
+
+ imx8qm_ipg_clk_set_rate(imx_mhdp);
+
+ /* Init pixel clock with 148.5MHz before FW init */
+ imx8qm_pixel_clk_set_rate(imx_mhdp, 148500000);
+
+ imx8qm_ipg_clk_enable(imx_mhdp);
+
+ imx8qm_clk_mux(imx_mhdp->mhdp.plat_data->is_dp);
+
+ imx8qm_pixel_clk_enable(imx_mhdp);
+
+ imx8qm_phy_reset(1);
+
+ return 0;
+}
+
+void cdns_mhdp_plat_deinit_imx8qm(struct cdns_mhdp_device *mhdp)
+{
+ struct imx_mhdp_device *imx_mhdp =
+ container_of(mhdp, struct imx_mhdp_device, mhdp);
+ bool dual_mode = imx8qm_video_dual_mode(&imx_mhdp->mhdp);
+
+ imx8qm_pixel_link_sync_disable(dual_mode);
+ imx8qm_pixel_link_invalid(dual_mode);
+}
+
+void cdns_mhdp_plat_init_imx8qm(struct cdns_mhdp_device *mhdp)
+{
+ struct imx_mhdp_device *imx_mhdp =
+ container_of(mhdp, struct imx_mhdp_device, mhdp);
+ bool dual_mode = imx8qm_video_dual_mode(&imx_mhdp->mhdp);
+
+ imx8qm_pixel_link_valid(dual_mode);
+ imx8qm_pixel_link_sync_enable(dual_mode);
+}
+
+void cdns_mhdp_pclk_rate_imx8qm(struct cdns_mhdp_device *mhdp)
+{
+ struct imx_mhdp_device *imx_mhdp =
+ container_of(mhdp, struct imx_mhdp_device, mhdp);
+
+ mutex_lock(&mhdp->iolock);
+
+ /* set pixel clock before video mode setup */
+ imx8qm_pixel_clk_disable(imx_mhdp);
+
+ imx8qm_pixel_clk_set_rate(imx_mhdp, imx_mhdp->mhdp.mode.clock * 1000);
+
+ imx8qm_pixel_clk_enable(imx_mhdp);
+
+ mutex_unlock(&mhdp->iolock);
+
+ /* Config pixel link mux */
+ imx8qm_pixel_link_mux(imx_mhdp);
+}
+
+int cdns_mhdp_firmware_write_section(struct imx_mhdp_device *imx_mhdp,
+ const u8 *data, int size, int addr)
+{
+ int i;
+
+ for (i = 0; i < size; i += 4) {
+ u32 val = (unsigned int)data[i] << 0 |
+ (unsigned int)data[i + 1] << 8 |
+ (unsigned int)data[i + 2] << 16 |
+ (unsigned int)data[i + 3] << 24;
+ cdns_mhdp_bus_write(val, &imx_mhdp->mhdp, addr + i);
+ }
+
+ return 0;
+}
+
+static void cdns_mhdp_firmware_load_cont(const struct firmware *fw, void *context)
+{
+ struct imx_mhdp_device *imx_mhdp = context;
+
+ imx_mhdp->fw = fw;
+}
+
+static int cdns_mhdp_firmware_load(struct imx_mhdp_device *imx_mhdp)
+{
+ const u8 *iram;
+ const u8 *dram;
+ u32 rate;
+ int ret;
+
+ /* configure HDMI/DP core clock */
+ rate = clk_get_rate(imx_mhdp->clks.clk_core);
+ if (imx_mhdp->mhdp.is_ls1028a)
+ rate = rate / 4;
+
+ cdns_mhdp_set_fw_clk(&imx_mhdp->mhdp, rate);
+
+ /* skip fw loading if none is specified */
+ if (!imx_mhdp->firmware_name)
+ goto out;
+
+ if (!imx_mhdp->fw) {
+ ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_NOUEVENT,
+ imx_mhdp->firmware_name,
+ imx_mhdp->mhdp.dev, GFP_KERNEL,
+ imx_mhdp,
+ cdns_mhdp_firmware_load_cont);
+ if (ret < 0) {
+ DRM_ERROR("failed to load firmware\n");
+ return -ENOENT;
+ }
+ } else {
+ iram = imx_mhdp->fw->data + FW_IRAM_OFFSET;
+ dram = iram + FW_IRAM_SIZE;
+
+ cdns_mhdp_firmware_write_section(imx_mhdp, iram, FW_IRAM_SIZE, ADDR_IMEM);
+ cdns_mhdp_firmware_write_section(imx_mhdp, dram, FW_DRAM_SIZE, ADDR_DMEM);
+ }
+
+out:
+ /* un-reset ucpu */
+ cdns_mhdp_bus_write(0, &imx_mhdp->mhdp, APB_CTRL);
+ DRM_INFO("Started firmware!\n");
+
+ return 0;
+}
+
+int cdns_mhdp_firmware_init_imx8qm(struct cdns_mhdp_device *mhdp)
+{
+ struct imx_mhdp_device *imx_mhdp =
+ container_of(mhdp, struct imx_mhdp_device, mhdp);
+ int ret;
+
+ /* load firmware */
+ ret = cdns_mhdp_firmware_load(imx_mhdp);
+ if (ret)
+ return ret;
+
+ ret = cdns_mhdp_check_alive(&imx_mhdp->mhdp);
+ if (ret == false) {
+ DRM_ERROR("NO HDMI FW running\n");
+ return -ENXIO;
+ }
+
+ /* turn on IP activity */
+ cdns_mhdp_set_firmware_active(&imx_mhdp->mhdp, 1);
+
+ DRM_INFO("HDP FW Version - ver %d verlib %d\n",
+ cdns_mhdp_bus_read(mhdp, VER_L) + (cdns_mhdp_bus_read(mhdp, VER_H) << 8),
+ cdns_mhdp_bus_read(mhdp, VER_LIB_H_ADDR) + (cdns_mhdp_bus_read(mhdp, VER_LIB_H_ADDR) << 8));
+
+ return 0;
+}
+
+int cdns_mhdp_suspend_imx8qm(struct cdns_mhdp_device *mhdp)
+{
+ struct imx_mhdp_device *imx_mhdp =
+ container_of(mhdp, struct imx_mhdp_device, mhdp);
+
+ imx8qm_pixel_clk_disable(imx_mhdp);
+
+ return 0;
+}
+
+int cdns_mhdp_resume_imx8qm(struct cdns_mhdp_device *mhdp)
+{
+ struct imx_mhdp_device *imx_mhdp =
+ container_of(mhdp, struct imx_mhdp_device, mhdp);
+
+ imx8qm_pixel_clk_enable(imx_mhdp);
+
+ return cdns_mhdp_firmware_init_imx8qm(mhdp);
+}
diff --git a/drivers/gpu/drm/imx/mhdp/cdns-mhdp-imxdrv.c b/drivers/gpu/drm/imx/mhdp/cdns-mhdp-imxdrv.c
new file mode 100644
index 000000000000..b3118fc7d629
--- /dev/null
+++ b/drivers/gpu/drm/imx/mhdp/cdns-mhdp-imxdrv.c
@@ -0,0 +1,276 @@
+/*
+ * Copyright 2019-2020 NXP
+ *
+ * this program is free software; you can redistribute it and/or modify
+ * it under the terms of the gnu general public license version 2 as
+ * published by the free software foundation.
+ */
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/component.h>
+#include <drm/drm_of.h>
+#include <drm/drm_vblank.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_encoder_slave.h>
+
+#include "cdns-mhdp-imx.h"
+#include "cdns-mhdp-phy.h"
+#include "../imx-drm.h"
+
+static void cdns_mhdp_imx_encoder_disable(struct drm_encoder *encoder)
+{
+ struct drm_bridge *bridge = drm_bridge_chain_get_first_bridge(encoder);
+ struct cdns_mhdp_device *mhdp = bridge->driver_private;
+
+ if (mhdp->is_dp)
+ cdns_dp_phy_shutdown(mhdp);
+ else
+ cdns_hdmi_phy_shutdown(mhdp);
+
+ cdns_mhdp_plat_call(mhdp, plat_init);
+}
+
+static void cdns_mhdp_imx_encoder_enable(struct drm_encoder *encoder)
+{
+ struct drm_bridge *bridge = drm_bridge_chain_get_first_bridge(encoder);
+ struct cdns_mhdp_device *mhdp = bridge->driver_private;
+
+ cdns_mhdp_plat_call(mhdp, plat_init);
+ if (mhdp->is_dp)
+ cdns_dp_phy_power_up(mhdp);
+ else
+ cdns_hdmi_phy_power_up(mhdp);
+}
+
+static int cdns_mhdp_imx_encoder_atomic_check(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc_state);
+ struct drm_bridge *bridge = drm_bridge_chain_get_first_bridge(encoder);
+ struct cdns_mhdp_device *mhdp = bridge->driver_private;
+
+ if (mhdp->plat_data->video_format != 0)
+ imx_crtc_state->bus_format = mhdp->plat_data->video_format;
+
+ if (mhdp->force_mode_set) {
+ crtc_state->mode_changed = true;
+ /* reset force mode set flag */
+ mhdp->force_mode_set = false;
+ }
+
+ return 0;
+}
+
+static const struct drm_encoder_helper_funcs cdns_mhdp_imx_encoder_helper_funcs = {
+ .enable = cdns_mhdp_imx_encoder_enable,
+ .disable = cdns_mhdp_imx_encoder_disable,
+ .atomic_check = cdns_mhdp_imx_encoder_atomic_check,
+};
+
+static const struct drm_encoder_funcs cdns_mhdp_imx_encoder_funcs = {
+ .destroy = drm_encoder_cleanup,
+};
+
+static struct cdns_plat_data imx8mq_hdmi_drv_data = {
+ .plat_name = "imx8mq-hdmi",
+ .bind = cdns_hdmi_bind,
+ .unbind = cdns_hdmi_unbind,
+ .phy_set = cdns_hdmi_phy_set_imx8mq,
+ .phy_video_valid = cdns_hdmi_phy_video_valid_imx8mq,
+ .bus_type = BUS_TYPE_NORMAL_APB,
+};
+
+static struct cdns_plat_data imx8mq_dp_drv_data = {
+ .plat_name = "imx8mq-dp",
+ .bind = cdns_dp_bind,
+ .unbind = cdns_dp_unbind,
+ .phy_set = cdns_dp_phy_set_imx8mq,
+ .bus_type = BUS_TYPE_NORMAL_APB,
+};
+
+static struct cdns_plat_data imx8qm_hdmi_drv_data = {
+ .plat_name = "imx8qm-hdmi",
+ .bind = cdns_hdmi_bind,
+ .unbind = cdns_hdmi_unbind,
+ .phy_set = cdns_hdmi_phy_set_imx8qm,
+ .phy_video_valid = cdns_hdmi_phy_video_valid_imx8qm,
+ .power_on = cdns_mhdp_power_on_imx8qm,
+ .firmware_init = cdns_mhdp_firmware_init_imx8qm,
+ .resume = cdns_mhdp_resume_imx8qm,
+ .suspend = cdns_mhdp_suspend_imx8qm,
+ .pclk_rate = cdns_mhdp_pclk_rate_imx8qm,
+ .plat_init = cdns_mhdp_plat_init_imx8qm,
+ .plat_deinit = cdns_mhdp_plat_deinit_imx8qm,
+ .bus_type = BUS_TYPE_LOW4K_APB,
+ .video_format = MEDIA_BUS_FMT_RGB101010_1X30,
+};
+
+static struct cdns_plat_data imx8qm_dp_drv_data = {
+ .plat_name = "imx8qm-dp",
+ .bind = cdns_dp_bind,
+ .unbind = cdns_dp_unbind,
+ .phy_set = cdns_dp_phy_set_imx8qm,
+ .power_on = cdns_mhdp_power_on_imx8qm,
+ .firmware_init = cdns_mhdp_firmware_init_imx8qm,
+ .resume = cdns_mhdp_resume_imx8qm,
+ .suspend = cdns_mhdp_suspend_imx8qm,
+ .pclk_rate = cdns_mhdp_pclk_rate_imx8qm,
+ .plat_init = cdns_mhdp_plat_init_imx8qm,
+ .plat_deinit = cdns_mhdp_plat_deinit_imx8qm,
+ .bus_type = BUS_TYPE_LOW4K_APB,
+ .video_format = MEDIA_BUS_FMT_RGB101010_1X30,
+ .is_dp = true,
+};
+
+static struct cdns_plat_data ls1028a_dp_drv_data = {
+ .bind = cdns_dp_bind,
+ .unbind = cdns_dp_unbind,
+ .phy_set = cdns_dp_phy_set_imx8mq,
+ .power_on = cdns_mhdp_power_on_ls1028a,
+ .firmware_init = cdns_mhdp_firmware_init_imx8qm,
+ .pclk_rate = cdns_mhdp_pclk_rate_ls1028a,
+ .bus_type = BUS_TYPE_NORMAL_APB,
+};
+
+static const struct of_device_id cdns_mhdp_imx_dt_ids[] = {
+ { .compatible = "cdn,imx8mq-hdmi",
+ .data = &imx8mq_hdmi_drv_data
+ },
+ { .compatible = "cdn,imx8mq-dp",
+ .data = &imx8mq_dp_drv_data
+ },
+ { .compatible = "cdn,imx8qm-hdmi",
+ .data = &imx8qm_hdmi_drv_data
+ },
+ { .compatible = "cdn,imx8qm-dp",
+ .data = &imx8qm_dp_drv_data
+ },
+ { .compatible = "cdn,ls1028a-dp",
+ .data = &ls1028a_dp_drv_data
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, cdns_mhdp_imx_dt_ids);
+
+static int cdns_mhdp_imx_bind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ const struct cdns_plat_data *plat_data;
+ const struct of_device_id *match;
+ struct drm_device *drm = data;
+ struct drm_encoder *encoder;
+ struct imx_mhdp_device *imx_mhdp;
+ int ret;
+
+ if (!pdev->dev.of_node)
+ return -ENODEV;
+
+ imx_mhdp = devm_kzalloc(&pdev->dev, sizeof(*imx_mhdp), GFP_KERNEL);
+ if (!imx_mhdp)
+ return -ENOMEM;
+
+ match = of_match_node(cdns_mhdp_imx_dt_ids, pdev->dev.of_node);
+ if (!match)
+ return -EFAULT;
+
+ plat_data = match->data;
+ encoder = &imx_mhdp->encoder;
+
+ encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node);
+
+ ret = of_property_read_string(pdev->dev.of_node, "firmware-name",
+ &imx_mhdp->firmware_name);
+ /*
+ * If we failed to find the CRTC(s) which this encoder is
+ * supposed to be connected to, it's because the CRTC has
+ * not been registered yet. Defer probing, and hope that
+ * the required CRTC is added later.
+ */
+ if (encoder->possible_crtcs == 0)
+ return -EPROBE_DEFER;
+
+ drm_encoder_helper_add(encoder, &cdns_mhdp_imx_encoder_helper_funcs);
+ drm_encoder_init(drm, encoder, &cdns_mhdp_imx_encoder_funcs,
+ DRM_MODE_ENCODER_TMDS, NULL);
+
+
+ imx_mhdp->mhdp.plat_data = plat_data;
+ imx_mhdp->mhdp.dev = dev;
+ imx_mhdp->mhdp.drm_dev = drm;
+ imx_mhdp->mhdp.bus_type = plat_data->bus_type;
+ ret = plat_data->bind(pdev, encoder, &imx_mhdp->mhdp);
+ /*
+ * If cdns_mhdp_bind() fails we'll never call cdns_mhdp_unbind(),
+ * which would have called the encoder cleanup. Do it manually.
+ */
+ if (ret < 0)
+ drm_encoder_cleanup(encoder);
+
+ return ret;
+}
+
+static void cdns_mhdp_imx_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct imx_mhdp_device *imx_mhdp = dev_get_drvdata(dev);
+
+ imx_mhdp->mhdp.plat_data->unbind(dev);
+}
+
+static const struct component_ops cdns_mhdp_imx_ops = {
+ .bind = cdns_mhdp_imx_bind,
+ .unbind = cdns_mhdp_imx_unbind,
+};
+
+static int cdns_mhdp_imx_suspend(struct device *dev)
+{
+ struct imx_mhdp_device *imx_mhdp = dev_get_drvdata(dev);
+
+ cdns_mhdp_plat_call(&imx_mhdp->mhdp, suspend);
+
+ return 0;
+}
+
+static int cdns_mhdp_imx_resume(struct device *dev)
+{
+ struct imx_mhdp_device *imx_mhdp = dev_get_drvdata(dev);
+
+ cdns_mhdp_plat_call(&imx_mhdp->mhdp, resume);
+ cdns_mhdp_plat_call(&imx_mhdp->mhdp, phy_set);
+
+ return 0;
+}
+
+static int cdns_mhdp_imx_probe(struct platform_device *pdev)
+{
+ return component_add(&pdev->dev, &cdns_mhdp_imx_ops);
+}
+
+static int cdns_mhdp_imx_remove(struct platform_device *pdev)
+{
+ component_del(&pdev->dev, &cdns_mhdp_imx_ops);
+
+ return 0;
+}
+
+static const struct dev_pm_ops cdns_mhdp_imx_pm_ops = {
+ SET_LATE_SYSTEM_SLEEP_PM_OPS(cdns_mhdp_imx_suspend, cdns_mhdp_imx_resume)
+};
+
+static struct platform_driver cdns_mhdp_imx_platform_driver = {
+ .probe = cdns_mhdp_imx_probe,
+ .remove = cdns_mhdp_imx_remove,
+ .driver = {
+ .name = "cdns-mhdp-imx",
+ .of_match_table = cdns_mhdp_imx_dt_ids,
+ .pm = &cdns_mhdp_imx_pm_ops,
+ },
+};
+
+module_platform_driver(cdns_mhdp_imx_platform_driver);
+
+MODULE_AUTHOR("Sandor YU <sandor.yu@nxp.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:cdnhdmi-imx");
diff --git a/drivers/gpu/drm/imx/mhdp/cdns-mhdp-ls1028a.c b/drivers/gpu/drm/imx/mhdp/cdns-mhdp-ls1028a.c
new file mode 100644
index 000000000000..4cc71301f5fe
--- /dev/null
+++ b/drivers/gpu/drm/imx/mhdp/cdns-mhdp-ls1028a.c
@@ -0,0 +1,110 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2019 NXP
+ *
+ */
+#include <linux/clk.h>
+#include <drm/drm_vblank.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+
+#include "cdns-mhdp-imx.h"
+
+static const struct of_device_id scfg_device_ids[] = {
+ { .compatible = "fsl,ls1028a-scfg", },
+ {}
+};
+
+static void ls1028a_phy_reset(u8 reset)
+{
+ struct device_node *scfg_node;
+ void __iomem *scfg_base = NULL;
+
+ scfg_node = of_find_matching_node(NULL, scfg_device_ids);
+ if (scfg_node)
+ scfg_base = of_iomap(scfg_node, 0);
+
+ iowrite32(reset, scfg_base + 0x230);
+}
+
+int ls1028a_clocks_init(struct imx_mhdp_device *imx_mhdp)
+{
+ struct device *dev = imx_mhdp->mhdp.dev;
+ struct imx_hdp_clks *clks = &imx_mhdp->clks;
+
+ clks->clk_core = devm_clk_get(dev, "clk_core");
+ if (IS_ERR(clks->clk_core)) {
+ dev_warn(dev, "failed to get hdp core clk\n");
+ return PTR_ERR(clks->clk_core);
+ }
+
+ clks->clk_pxl = devm_clk_get(dev, "clk_pxl");
+ if (IS_ERR(clks->clk_pxl)) {
+ dev_warn(dev, "failed to get pxl clk\n");
+ return PTR_ERR(clks->clk_pxl);
+ }
+
+ return true;
+}
+
+static int ls1028a_pixel_clk_enable(struct imx_mhdp_device *imx_mhdp)
+{
+ struct imx_hdp_clks *clks = &imx_mhdp->clks;
+ struct device *dev = imx_mhdp->mhdp.dev;
+ int ret;
+
+ ret = clk_prepare_enable(clks->clk_pxl);
+ if (ret < 0) {
+ dev_err(dev, "%s, pre clk pxl error\n", __func__);
+ return ret;
+ }
+
+ return ret;
+}
+
+static void ls1028a_pixel_clk_disable(struct imx_mhdp_device *imx_mhdp)
+{
+ struct imx_hdp_clks *clks = &imx_mhdp->clks;
+
+ clk_disable_unprepare(clks->clk_pxl);
+}
+
+static void ls1028a_pixel_clk_set_rate(struct imx_mhdp_device *imx_mhdp,
+ u32 pclock)
+{
+ struct imx_hdp_clks *clks = &imx_mhdp->clks;
+
+ clk_set_rate(clks->clk_pxl, pclock);
+}
+
+int cdns_mhdp_power_on_ls1028a(struct cdns_mhdp_device *mhdp)
+{
+ struct imx_mhdp_device *imx_mhdp = container_of
+ (mhdp, struct imx_mhdp_device, mhdp);
+
+ /* clock init and rate set */
+ ls1028a_clocks_init(imx_mhdp);
+
+ ls1028a_pixel_clk_enable(imx_mhdp);
+
+ /* Init pixel clock with 148.5MHz before FW init */
+ ls1028a_pixel_clk_set_rate(imx_mhdp, 148500000);
+
+ ls1028a_phy_reset(1);
+
+ return 0;
+}
+
+void cdns_mhdp_pclk_rate_ls1028a(struct cdns_mhdp_device *mhdp)
+{
+ struct imx_mhdp_device *imx_mhdp = container_of
+ (mhdp, struct imx_mhdp_device, mhdp);
+
+ /* set pixel clock before video mode setup */
+ ls1028a_pixel_clk_disable(imx_mhdp);
+
+ ls1028a_pixel_clk_set_rate(imx_mhdp, imx_mhdp->mhdp.mode.clock * 1000);
+
+ ls1028a_pixel_clk_enable(imx_mhdp);
+}
diff --git a/drivers/gpu/drm/imx/mhdp/cdns-mhdp-phy.h b/drivers/gpu/drm/imx/mhdp/cdns-mhdp-phy.h
new file mode 100644
index 000000000000..4f52dae4d0a1
--- /dev/null
+++ b/drivers/gpu/drm/imx/mhdp/cdns-mhdp-phy.h
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2019-2021 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef _CDN_DP_PHY_H
+#define _CDN_DP_PHY_H
+
+#include <drm/bridge/cdns-mhdp.h>
+
+#define CMN_SSM_BIAS_TMR 0x0022
+#define CMN_PLLSM0_PLLEN_TMR 0x0029
+#define CMN_PLLSM0_PLLPRE_TMR 0x002A
+#define CMN_PLLSM0_PLLVREF_TMR 0x002B
+#define CMN_PLLSM0_PLLLOCK_TMR 0x002C
+#define CMN_PLLSM0_USER_DEF_CTRL 0x002F
+#define CMN_PSM_CLK_CTRL 0x0061
+#define CMN_CDIAG_REFCLK_CTRL 0x0062
+#define CMN_PLL0_VCOCAL_START 0x0081
+#define CMN_PLL0_VCOCAL_INIT_TMR 0x0084
+#define CMN_PLL0_VCOCAL_ITER_TMR 0x0085
+#define CMN_PLL0_INTDIV 0x0094
+#define CMN_PLL0_FRACDIV 0x0095
+#define CMN_PLL0_HIGH_THR 0x0096
+#define CMN_PLL0_DSM_DIAG 0x0097
+#define CMN_PLL0_SS_CTRL1 0x0098
+#define CMN_PLL0_SS_CTRL2 0x0099
+#define CMN_ICAL_INIT_TMR 0x00C4
+#define CMN_ICAL_ITER_TMR 0x00C5
+#define CMN_RXCAL_INIT_TMR 0x00D4
+#define CMN_RXCAL_ITER_TMR 0x00D5
+#define CMN_TXPUCAL_CTRL 0x00E0
+#define CMN_TXPUCAL_INIT_TMR 0x00E4
+#define CMN_TXPUCAL_ITER_TMR 0x00E5
+#define CMN_TXPDCAL_CTRL 0x00F0
+#define CMN_TXPDCAL_INIT_TMR 0x00F4
+#define CMN_TXPDCAL_ITER_TMR 0x00F5
+#define CMN_ICAL_ADJ_INIT_TMR 0x0102
+#define CMN_ICAL_ADJ_ITER_TMR 0x0103
+#define CMN_RX_ADJ_INIT_TMR 0x0106
+#define CMN_RX_ADJ_ITER_TMR 0x0107
+#define CMN_TXPU_ADJ_CTRL 0x0108
+#define CMN_TXPU_ADJ_INIT_TMR 0x010A
+#define CMN_TXPU_ADJ_ITER_TMR 0x010B
+#define CMN_TXPD_ADJ_CTRL 0x010c
+#define CMN_TXPD_ADJ_INIT_TMR 0x010E
+#define CMN_TXPD_ADJ_ITER_TMR 0x010F
+#define CMN_DIAG_PLL0_FBH_OVRD 0x01C0
+#define CMN_DIAG_PLL0_FBL_OVRD 0x01C1
+#define CMN_DIAG_PLL0_OVRD 0x01C2
+#define CMN_DIAG_PLL0_TEST_MODE 0x01C4
+#define CMN_DIAG_PLL0_V2I_TUNE 0x01C5
+#define CMN_DIAG_PLL0_CP_TUNE 0x01C6
+#define CMN_DIAG_PLL0_LF_PROG 0x01C7
+#define CMN_DIAG_PLL0_PTATIS_TUNE1 0x01C8
+#define CMN_DIAG_PLL0_PTATIS_TUNE2 0x01C9
+#define CMN_DIAG_PLL0_INCLK_CTRL 0x01CA
+#define CMN_DIAG_PLL0_PXL_DIVH 0x01CB
+#define CMN_DIAG_PLL0_PXL_DIVL 0x01CC
+#define CMN_DIAG_HSCLK_SEL 0x01E0
+#define CMN_DIAG_PER_CAL_ADJ 0x01EC
+#define CMN_DIAG_CAL_CTRL 0x01ED
+#define CMN_DIAG_ACYA 0x01FF
+#define XCVR_PSM_RCTRL 0x4001
+#define XCVR_PSM_CAL_TMR 0x4002
+#define XCVR_PSM_A0IN_TMR 0x4003
+#define TX_TXCC_CAL_SCLR_MULT_0 0x4047
+#define TX_TXCC_CPOST_MULT_00_0 0x404C
+#define TX_TXCC_MGNFS_MULT_000_0 0x4050
+#define XCVR_DIAG_PLLDRC_CTRL 0x40E0
+#define XCVR_DIAG_PLLDRC_CTRL 0x40E0
+#define XCVR_DIAG_HSCLK_SEL 0x40E1
+#define XCVR_DIAG_BIDI_CTRL 0x40E8
+#define XCVR_DIAG_LANE_FCM_EN_MGN_TMR 0x40F2
+#define XCVR_DIAG_LANE_FCM_EN_MGN 0x40F2
+#define TX_PSC_A0 0x4100
+#define TX_PSC_A1 0x4101
+#define TX_PSC_A2 0x4102
+#define TX_PSC_A3 0x4103
+#define TX_RCVDET_CTRL 0x4120
+#define TX_RCVDET_EN_TMR 0x4122
+#define TX_RCVDET_EN_TMR 0x4122
+#define TX_RCVDET_ST_TMR 0x4123
+#define TX_RCVDET_ST_TMR 0x4123
+#define TX_BIST_CTRL 0x4140
+#define TX_BIST_UDDWR 0x4141
+#define TX_DIAG_TX_CTRL 0x41E0
+#define TX_DIAG_TX_DRV 0x41E1
+#define TX_DIAG_BGREF_PREDRV_DELAY 0x41E7
+#define TX_DIAG_BGREF_PREDRV_DELAY 0x41E7
+#define XCVR_PSM_RCTRL_1 0x4201
+#define TX_TXCC_CAL_SCLR_MULT_1 0x4247
+#define TX_TXCC_CPOST_MULT_00_1 0x424C
+#define TX_TXCC_MGNFS_MULT_000_1 0x4250
+#define XCVR_DIAG_PLLDRC_CTRL_1 0x42E0
+#define XCVR_DIAG_HSCLK_SEL_1 0x42E1
+#define XCVR_DIAG_LANE_FCM_EN_MGN_TMR_1 0x42F2
+#define TX_RCVDET_EN_TMR_1 0x4322
+#define TX_RCVDET_ST_TMR_1 0x4323
+#define TX_DIAG_ACYA_0 0x41FF
+#define TX_DIAG_ACYA_1 0x43FF
+#define TX_DIAG_ACYA_2 0x45FF
+#define TX_DIAG_ACYA_3 0x47FF
+#define TX_ANA_CTRL_REG_1 0x5020
+#define TX_ANA_CTRL_REG_2 0x5021
+#define TXDA_COEFF_CALC 0x5022
+#define TX_DIG_CTRL_REG_1 0x5023
+#define TX_DIG_CTRL_REG_2 0x5024
+#define TXDA_CYA_AUXDA_CYA 0x5025
+#define TX_ANA_CTRL_REG_3 0x5026
+#define TX_ANA_CTRL_REG_4 0x5027
+#define TX_ANA_CTRL_REG_5 0x5029
+#define RX_PSC_A0 0x8000
+#define RX_PSC_CAL 0x8006
+#define PMA_LANE_CFG 0xC000
+#define PIPE_CMN_CTRL1 0xC001
+#define PIPE_CMN_CTRL2 0xC002
+#define PIPE_COM_LOCK_CFG1 0xC003
+#define PIPE_COM_LOCK_CFG2 0xC004
+#define PIPE_RCV_DET_INH 0xC005
+#define PHY_HDP_MODE_CTRL 0xC008
+#define PHY_HDP_CLK_CTL 0xC009
+#define STS 0xC00F
+#define PHY_ISO_CMN_CTRL 0xC010
+#define PHY_ISO_CMN_CTRL 0xC010
+#define PHY_HDP_TX_CTL_L0 0xC408
+#define PHY_DP_TX_CTL 0xC408
+#define PHY_HDP_TX_CTL_L1 0xC448
+#define PHY_HDP_TX_CTL_L2 0xC488
+#define PHY_HDP_TX_CTL_L3 0xC4C8
+#define PHY_PMA_CMN_CTRL1 0xC800
+#define PMA_CMN_CTRL1 0xC800
+#define PHY_PMA_ISO_CMN_CTRL 0xC810
+#define PHY_PMA_ISO_PLL_CTRL1 0xC812
+#define PHY_PMA_ISOLATION_CTRL 0xC81F
+#define PHY_ISOLATION_CTRL 0xC81F
+#define PHY_PMA_ISO_XCVR_CTRL 0xCC11
+#define PHY_PMA_ISO_LINK_MODE 0xCC12
+#define PHY_PMA_ISO_PWRST_CTRL 0xCC13
+#define PHY_PMA_ISO_TX_DATA_LO 0xCC14
+#define PHY_PMA_ISO_TX_DATA_HI 0xCC15
+#define PHY_PMA_ISO_RX_DATA_LO 0xCC16
+#define PHY_PMA_ISO_RX_DATA_HI 0xCC17
+
+int cdns_dp_phy_set_imx8mq(struct cdns_mhdp_device *hdp);
+int cdns_dp_phy_set_imx8qm(struct cdns_mhdp_device *hdp);
+int cdns_dp_phy_shutdown(struct cdns_mhdp_device *mhdp);
+int cdns_dp_phy_power_up(struct cdns_mhdp_device *mhdp);
+bool cdns_hdmi_phy_video_valid_imx8mq(struct cdns_mhdp_device *hdp);
+bool cdns_hdmi_phy_video_valid_imx8qm(struct cdns_mhdp_device *hdp);
+int cdns_hdmi_phy_set_imx8mq(struct cdns_mhdp_device *hdp);
+int cdns_hdmi_phy_set_imx8qm(struct cdns_mhdp_device *hdp);
+int cdns_hdmi_phy_shutdown(struct cdns_mhdp_device *mhdp);
+int cdns_hdmi_phy_power_up(struct cdns_mhdp_device *mhdp);
+#endif /* _CDNS_MHDP_PHY_H */
diff --git a/drivers/gpu/drm/imx/sec_mipi_dphy_ln14lpp.h b/drivers/gpu/drm/imx/sec_mipi_dphy_ln14lpp.h
new file mode 100644
index 000000000000..b302ed064e25
--- /dev/null
+++ b/drivers/gpu/drm/imx/sec_mipi_dphy_ln14lpp.h
@@ -0,0 +1,227 @@
+/*
+ * Copyright 2018 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __SEC_DSIM_DPHY_LN14LPP_H__
+#define __SEC_DSIM_DPHY_LN14LPP_H__
+
+#include <drm/bridge/sec_mipi_dsim.h>
+
+/* descending order based on 'bit_clk' value */
+static const struct sec_mipi_dsim_dphy_timing dphy_timing_ln14lpp_v1p2[] = {
+ { DSIM_DPHY_TIMING(2100, 19, 91, 22, 19, 20, 35, 22, 15, 26), },
+ { DSIM_DPHY_TIMING(2090, 19, 91, 22, 19, 19, 35, 22, 15, 26), },
+ { DSIM_DPHY_TIMING(2080, 19, 91, 21, 18, 19, 35, 22, 15, 26), },
+ { DSIM_DPHY_TIMING(2070, 18, 90, 21, 18, 19, 35, 22, 15, 25), },
+ { DSIM_DPHY_TIMING(2060, 18, 90, 21, 18, 19, 34, 22, 15, 25), },
+ { DSIM_DPHY_TIMING(2050, 18, 89, 21, 18, 19, 34, 22, 15, 25), },
+ { DSIM_DPHY_TIMING(2040, 18, 89, 21, 18, 19, 34, 21, 15, 25), },
+ { DSIM_DPHY_TIMING(2030, 18, 88, 21, 18, 19, 34, 21, 15, 25), },
+ { DSIM_DPHY_TIMING(2020, 18, 88, 21, 18, 19, 34, 21, 15, 25), },
+ { DSIM_DPHY_TIMING(2010, 18, 87, 21, 18, 19, 34, 21, 15, 25), },
+ { DSIM_DPHY_TIMING(2000, 18, 87, 21, 18, 19, 33, 21, 15, 25), },
+ { DSIM_DPHY_TIMING(1990, 18, 87, 21, 18, 18, 33, 21, 14, 24), },
+ { DSIM_DPHY_TIMING(1980, 18, 86, 21, 18, 18, 33, 21, 14, 24), },
+ { DSIM_DPHY_TIMING(1970, 17, 86, 21, 17, 18, 33, 21, 14, 24), },
+ { DSIM_DPHY_TIMING(1960, 17, 85, 21, 17, 18, 33, 21, 14, 24), },
+ { DSIM_DPHY_TIMING(1950, 17, 85, 21, 17, 18, 32, 21, 14, 24), },
+ { DSIM_DPHY_TIMING(1940, 17, 84, 20, 17, 18, 32, 21, 14, 24), },
+ { DSIM_DPHY_TIMING(1930, 17, 84, 20, 17, 18, 32, 20, 14, 24), },
+ { DSIM_DPHY_TIMING(1920, 17, 84, 20, 17, 18, 32, 20, 14, 24), },
+ { DSIM_DPHY_TIMING(1910, 17, 83, 20, 17, 18, 32, 20, 14, 23), },
+ { DSIM_DPHY_TIMING(1900, 17, 83, 20, 17, 18, 32, 20, 14, 23), },
+ { DSIM_DPHY_TIMING(1890, 17, 82, 20, 17, 18, 31, 20, 14, 23), },
+ { DSIM_DPHY_TIMING(1880, 17, 82, 20, 17, 17, 31, 20, 14, 23), },
+ { DSIM_DPHY_TIMING(1870, 17, 81, 20, 17, 17, 31, 20, 14, 23), },
+ { DSIM_DPHY_TIMING(1860, 16, 81, 20, 17, 17, 31, 20, 13, 23), },
+ { DSIM_DPHY_TIMING(1850, 16, 80, 20, 16, 17, 31, 20, 13, 23), },
+ { DSIM_DPHY_TIMING(1840, 16, 80, 20, 16, 17, 30, 20, 13, 23), },
+ { DSIM_DPHY_TIMING(1830, 16, 80, 20, 16, 17, 30, 20, 13, 22), },
+ { DSIM_DPHY_TIMING(1820, 16, 79, 20, 16, 17, 30, 19, 13, 22), },
+ { DSIM_DPHY_TIMING(1810, 16, 79, 19, 16, 17, 30, 19, 13, 22), },
+ { DSIM_DPHY_TIMING(1800, 16, 78, 19, 16, 17, 30, 19, 13, 22), },
+ { DSIM_DPHY_TIMING(1790, 16, 78, 19, 16, 17, 30, 19, 13, 22), },
+ { DSIM_DPHY_TIMING(1780, 16, 77, 19, 16, 16, 29, 19, 13, 22), },
+ { DSIM_DPHY_TIMING(1770, 16, 77, 19, 16, 16, 29, 19, 13, 22), },
+ { DSIM_DPHY_TIMING(1760, 16, 77, 19, 16, 16, 29, 19, 13, 22), },
+ { DSIM_DPHY_TIMING(1750, 15, 76, 19, 16, 16, 29, 19, 13, 21), },
+ { DSIM_DPHY_TIMING(1740, 15, 76, 19, 15, 16, 29, 19, 13, 21), },
+ { DSIM_DPHY_TIMING(1730, 15, 75, 19, 15, 16, 28, 19, 12, 21), },
+ { DSIM_DPHY_TIMING(1720, 15, 75, 19, 15, 16, 28, 19, 12, 21), },
+ { DSIM_DPHY_TIMING(1710, 15, 74, 19, 15, 16, 28, 18, 12, 21), },
+ { DSIM_DPHY_TIMING(1700, 15, 74, 19, 15, 16, 28, 18, 12, 21), },
+ { DSIM_DPHY_TIMING(1690, 15, 73, 19, 15, 16, 28, 18, 12, 21), },
+ { DSIM_DPHY_TIMING(1680, 15, 73, 18, 15, 16, 28, 18, 12, 21), },
+ { DSIM_DPHY_TIMING(1670, 15, 73, 18, 15, 15, 27, 18, 12, 20), },
+ { DSIM_DPHY_TIMING(1660, 15, 72, 18, 15, 15, 27, 18, 12, 20), },
+ { DSIM_DPHY_TIMING(1650, 14, 72, 18, 15, 15, 27, 18, 12, 20), },
+ { DSIM_DPHY_TIMING(1640, 14, 71, 18, 15, 15, 27, 18, 12, 20), },
+ { DSIM_DPHY_TIMING(1630, 14, 71, 18, 15, 15, 27, 18, 12, 20), },
+ { DSIM_DPHY_TIMING(1620, 14, 70, 18, 14, 15, 26, 18, 12, 20), },
+ { DSIM_DPHY_TIMING(1610, 14, 70, 18, 14, 15, 26, 17, 12, 20), },
+ { DSIM_DPHY_TIMING(1600, 14, 70, 18, 14, 15, 26, 17, 12, 20), },
+ { DSIM_DPHY_TIMING(1590, 14, 69, 18, 14, 15, 26, 17, 11, 19), },
+ { DSIM_DPHY_TIMING(1580, 14, 69, 18, 14, 15, 26, 17, 11, 19), },
+ { DSIM_DPHY_TIMING(1570, 14, 68, 18, 14, 15, 26, 17, 11, 19), },
+ { DSIM_DPHY_TIMING(1560, 14, 68, 18, 14, 14, 25, 17, 11, 19), },
+ { DSIM_DPHY_TIMING(1550, 14, 67, 18, 14, 14, 25, 17, 11, 19), },
+ { DSIM_DPHY_TIMING(1540, 13, 67, 17, 14, 14, 25, 17, 11, 19), },
+ { DSIM_DPHY_TIMING(1530, 13, 66, 17, 14, 14, 25, 17, 11, 19), },
+ { DSIM_DPHY_TIMING(1520, 13, 66, 17, 14, 14, 25, 17, 11, 19), },
+ { DSIM_DPHY_TIMING(1510, 13, 66, 17, 13, 14, 24, 17, 11, 18), },
+ { DSIM_DPHY_TIMING(1500, 13, 65, 17, 13, 14, 24, 16, 11, 18), },
+ { DSIM_DPHY_TIMING(1490, 13, 65, 17, 13, 14, 24, 16, 11, 18), },
+ { DSIM_DPHY_TIMING(1480, 13, 64, 17, 13, 14, 24, 16, 11, 18), },
+ { DSIM_DPHY_TIMING(1470, 13, 64, 17, 13, 14, 24, 16, 11, 18), },
+ { DSIM_DPHY_TIMING(1460, 13, 63, 17, 13, 13, 24, 16, 10, 18), },
+ { DSIM_DPHY_TIMING(1450, 13, 63, 17, 13, 13, 23, 16, 10, 18), },
+ { DSIM_DPHY_TIMING(1440, 13, 63, 17, 13, 13, 23, 16, 10, 18), },
+ { DSIM_DPHY_TIMING(1430, 12, 62, 17, 13, 13, 23, 16, 10, 17), },
+ { DSIM_DPHY_TIMING(1420, 12, 62, 17, 13, 13, 23, 16, 10, 17), },
+ { DSIM_DPHY_TIMING(1410, 12, 61, 16, 13, 13, 23, 16, 10, 17), },
+ { DSIM_DPHY_TIMING(1400, 12, 61, 16, 13, 13, 23, 16, 10, 17), },
+ { DSIM_DPHY_TIMING(1390, 12, 60, 16, 12, 13, 22, 15, 10, 17), },
+ { DSIM_DPHY_TIMING(1380, 12, 60, 16, 12, 13, 22, 15, 10, 17), },
+ { DSIM_DPHY_TIMING(1370, 12, 59, 16, 12, 13, 22, 15, 10, 17), },
+ { DSIM_DPHY_TIMING(1360, 12, 59, 16, 12, 13, 22, 15, 10, 17), },
+ { DSIM_DPHY_TIMING(1350, 12, 59, 16, 12, 12, 22, 15, 10, 16), },
+ { DSIM_DPHY_TIMING(1340, 12, 58, 16, 12, 12, 21, 15, 10, 16), },
+ { DSIM_DPHY_TIMING(1330, 11, 58, 16, 12, 12, 21, 15, 9, 16), },
+ { DSIM_DPHY_TIMING(1320, 11, 57, 16, 12, 12, 21, 15, 9, 16), },
+ { DSIM_DPHY_TIMING(1310, 11, 57, 16, 12, 12, 21, 15, 9, 16), },
+ { DSIM_DPHY_TIMING(1300, 11, 56, 16, 12, 12, 21, 15, 9, 16), },
+ { DSIM_DPHY_TIMING(1290, 11, 56, 16, 12, 12, 21, 15, 9, 16), },
+ { DSIM_DPHY_TIMING(1280, 11, 56, 15, 11, 12, 20, 14, 9, 16), },
+ { DSIM_DPHY_TIMING(1270, 11, 55, 15, 11, 12, 20, 14, 9, 15), },
+ { DSIM_DPHY_TIMING(1260, 11, 55, 15, 11, 12, 20, 14, 9, 15), },
+ { DSIM_DPHY_TIMING(1250, 11, 54, 15, 11, 11, 20, 14, 9, 15), },
+ { DSIM_DPHY_TIMING(1240, 11, 54, 15, 11, 11, 20, 14, 9, 15), },
+ { DSIM_DPHY_TIMING(1230, 11, 53, 15, 11, 11, 19, 14, 9, 15), },
+ { DSIM_DPHY_TIMING(1220, 10, 53, 15, 11, 11, 19, 14, 9, 15), },
+ { DSIM_DPHY_TIMING(1210, 10, 52, 15, 11, 11, 19, 14, 9, 15), },
+ { DSIM_DPHY_TIMING(1200, 10, 52, 15, 11, 11, 19, 14, 9, 15), },
+ { DSIM_DPHY_TIMING(1190, 10, 52, 15, 11, 11, 19, 14, 8, 14), },
+ { DSIM_DPHY_TIMING(1180, 10, 51, 15, 11, 11, 19, 13, 8, 14), },
+ { DSIM_DPHY_TIMING(1170, 10, 51, 15, 10, 11, 18, 13, 8, 14), },
+ { DSIM_DPHY_TIMING(1160, 10, 50, 15, 10, 11, 18, 13, 8, 14), },
+ { DSIM_DPHY_TIMING(1150, 10, 50, 15, 10, 11, 18, 13, 8, 14), },
+ { DSIM_DPHY_TIMING(1140, 10, 49, 14, 10, 10, 18, 13, 8, 14), },
+ { DSIM_DPHY_TIMING(1130, 10, 49, 14, 10, 10, 18, 13, 8, 14), },
+ { DSIM_DPHY_TIMING(1120, 10, 49, 14, 10, 10, 17, 13, 8, 14), },
+ { DSIM_DPHY_TIMING(1110, 9, 48, 14, 10, 10, 17, 13, 8, 13), },
+ { DSIM_DPHY_TIMING(1100, 9, 48, 14, 10, 10, 17, 13, 8, 13), },
+ { DSIM_DPHY_TIMING(1090, 9, 47, 14, 10, 10, 17, 13, 8, 13), },
+ { DSIM_DPHY_TIMING(1080, 9, 47, 14, 10, 10, 17, 13, 8, 13), },
+ { DSIM_DPHY_TIMING(1070, 9, 46, 14, 10, 10, 17, 12, 8, 13), },
+ { DSIM_DPHY_TIMING(1060, 9, 46, 14, 10, 10, 16, 12, 7, 13), },
+ { DSIM_DPHY_TIMING(1050, 9, 45, 14, 9, 10, 16, 12, 7, 13), },
+ { DSIM_DPHY_TIMING(1040, 9, 45, 14, 9, 10, 16, 12, 7, 13), },
+ { DSIM_DPHY_TIMING(1030, 9, 45, 14, 9, 9, 16, 12, 7, 12), },
+ { DSIM_DPHY_TIMING(1020, 9, 44, 14, 9, 9, 16, 12, 7, 12), },
+ { DSIM_DPHY_TIMING(1010, 8, 44, 13, 9, 9, 15, 12, 7, 12), },
+ { DSIM_DPHY_TIMING(1000, 8, 43, 13, 9, 9, 15, 12, 7, 12), },
+ { DSIM_DPHY_TIMING( 990, 8, 43, 13, 9, 9, 15, 12, 7, 12), },
+ { DSIM_DPHY_TIMING( 980, 8, 42, 13, 9, 9, 15, 12, 7, 12), },
+ { DSIM_DPHY_TIMING( 970, 8, 42, 13, 9, 9, 15, 12, 7, 12), },
+ { DSIM_DPHY_TIMING( 960, 8, 42, 13, 9, 9, 15, 11, 7, 12), },
+ { DSIM_DPHY_TIMING( 950, 8, 41, 13, 9, 9, 14, 11, 7, 11), },
+ { DSIM_DPHY_TIMING( 940, 8, 41, 13, 8, 9, 14, 11, 7, 11), },
+ { DSIM_DPHY_TIMING( 930, 8, 40, 13, 8, 8, 14, 11, 6, 11), },
+ { DSIM_DPHY_TIMING( 920, 8, 40, 13, 8, 8, 14, 11, 6, 11), },
+ { DSIM_DPHY_TIMING( 910, 8, 39, 13, 8, 8, 14, 11, 6, 11), },
+ { DSIM_DPHY_TIMING( 900, 7, 39, 13, 8, 8, 13, 11, 6, 11), },
+ { DSIM_DPHY_TIMING( 890, 7, 38, 13, 8, 8, 13, 11, 6, 11), },
+ { DSIM_DPHY_TIMING( 880, 7, 38, 12, 8, 8, 13, 11, 6, 11), },
+ { DSIM_DPHY_TIMING( 870, 7, 38, 12, 8, 8, 13, 11, 6, 10), },
+ { DSIM_DPHY_TIMING( 860, 7, 37, 12, 8, 8, 13, 11, 6, 10), },
+ { DSIM_DPHY_TIMING( 850, 7, 37, 12, 8, 8, 13, 10, 6, 10), },
+ { DSIM_DPHY_TIMING( 840, 7, 36, 12, 8, 8, 12, 10, 6, 10), },
+ { DSIM_DPHY_TIMING( 830, 7, 36, 12, 8, 8, 12, 10, 6, 10), },
+ { DSIM_DPHY_TIMING( 820, 7, 35, 12, 7, 7, 12, 10, 6, 10), },
+ { DSIM_DPHY_TIMING( 810, 7, 35, 12, 7, 7, 12, 10, 6, 10), },
+ { DSIM_DPHY_TIMING( 800, 7, 35, 12, 7, 7, 12, 10, 6, 10), },
+ { DSIM_DPHY_TIMING( 790, 6, 34, 12, 7, 7, 11, 10, 5, 9), },
+ { DSIM_DPHY_TIMING( 780, 6, 34, 12, 7, 7, 11, 10, 5, 9), },
+ { DSIM_DPHY_TIMING( 770, 6, 33, 12, 7, 7, 11, 10, 5, 9), },
+ { DSIM_DPHY_TIMING( 760, 6, 33, 12, 7, 7, 11, 10, 5, 9), },
+ { DSIM_DPHY_TIMING( 750, 6, 32, 12, 7, 7, 11, 9, 5, 9), },
+ { DSIM_DPHY_TIMING( 740, 6, 32, 11, 7, 7, 11, 9, 5, 9), },
+ { DSIM_DPHY_TIMING( 730, 6, 31, 11, 7, 7, 10, 9, 5, 9), },
+ { DSIM_DPHY_TIMING( 720, 6, 31, 11, 7, 6, 10, 9, 5, 9), },
+ { DSIM_DPHY_TIMING( 710, 6, 31, 11, 6, 6, 10, 9, 5, 8), },
+ { DSIM_DPHY_TIMING( 700, 6, 30, 11, 6, 6, 10, 9, 5, 8), },
+ { DSIM_DPHY_TIMING( 690, 5, 30, 11, 6, 6, 10, 9, 5, 8), },
+ { DSIM_DPHY_TIMING( 680, 5, 29, 11, 6, 6, 9, 9, 5, 8), },
+ { DSIM_DPHY_TIMING( 670, 5, 29, 11, 6, 6, 9, 9, 5, 8), },
+ { DSIM_DPHY_TIMING( 660, 5, 28, 11, 6, 6, 9, 9, 4, 8), },
+ { DSIM_DPHY_TIMING( 650, 5, 28, 11, 6, 6, 9, 9, 4, 8), },
+ { DSIM_DPHY_TIMING( 640, 5, 28, 11, 6, 6, 9, 8, 4, 8), },
+ { DSIM_DPHY_TIMING( 630, 5, 27, 11, 6, 6, 9, 8, 4, 7), },
+ { DSIM_DPHY_TIMING( 620, 5, 27, 11, 6, 6, 8, 8, 4, 7), },
+ { DSIM_DPHY_TIMING( 610, 5, 26, 10, 6, 5, 8, 8, 4, 7), },
+ { DSIM_DPHY_TIMING( 600, 5, 26, 10, 6, 5, 8, 8, 4, 7), },
+ { DSIM_DPHY_TIMING( 590, 5, 25, 10, 5, 5, 8, 8, 4, 7), },
+ { DSIM_DPHY_TIMING( 580, 4, 25, 10, 5, 5, 8, 8, 4, 7), },
+ { DSIM_DPHY_TIMING( 570, 4, 24, 10, 5, 5, 7, 8, 4, 7), },
+ { DSIM_DPHY_TIMING( 560, 4, 24, 10, 5, 5, 7, 8, 4, 7), },
+ { DSIM_DPHY_TIMING( 550, 4, 24, 10, 5, 5, 7, 8, 4, 6), },
+ { DSIM_DPHY_TIMING( 540, 4, 23, 10, 5, 5, 7, 8, 4, 6), },
+ { DSIM_DPHY_TIMING( 530, 4, 23, 10, 5, 5, 7, 7, 3, 6), },
+ { DSIM_DPHY_TIMING( 520, 4, 22, 10, 5, 5, 7, 7, 3, 6), },
+ { DSIM_DPHY_TIMING( 510, 4, 22, 10, 5, 5, 6, 7, 3, 6), },
+ { DSIM_DPHY_TIMING( 500, 4, 21, 10, 5, 4, 6, 7, 3, 6), },
+ { DSIM_DPHY_TIMING( 490, 4, 21, 10, 5, 4, 6, 7, 3, 6), },
+ { DSIM_DPHY_TIMING( 480, 4, 21, 9, 4, 4, 6, 7, 3, 6), },
+ { DSIM_DPHY_TIMING( 470, 3, 20, 9, 4, 4, 6, 7, 3, 5), },
+ { DSIM_DPHY_TIMING( 460, 3, 20, 9, 4, 4, 5, 7, 3, 5), },
+ { DSIM_DPHY_TIMING( 450, 3, 19, 9, 4, 4, 5, 7, 3, 5), },
+ { DSIM_DPHY_TIMING( 440, 3, 19, 9, 4, 4, 5, 7, 3, 5), },
+ { DSIM_DPHY_TIMING( 430, 3, 18, 9, 4, 4, 5, 7, 3, 5), },
+ { DSIM_DPHY_TIMING( 420, 3, 18, 9, 4, 4, 5, 6, 3, 5), },
+ { DSIM_DPHY_TIMING( 410, 3, 17, 9, 4, 4, 5, 6, 3, 5), },
+ { DSIM_DPHY_TIMING( 400, 3, 17, 9, 4, 3, 4, 6, 3, 5), },
+ { DSIM_DPHY_TIMING( 390, 3, 17, 9, 4, 3, 4, 6, 2, 4), },
+ { DSIM_DPHY_TIMING( 380, 3, 16, 9, 4, 3, 4, 6, 2, 4), },
+ { DSIM_DPHY_TIMING( 370, 2, 16, 9, 3, 3, 4, 6, 2, 4), },
+ { DSIM_DPHY_TIMING( 360, 2, 15, 9, 3, 3, 4, 6, 2, 4), },
+ { DSIM_DPHY_TIMING( 350, 2, 15, 9, 3, 3, 3, 6, 2, 4), },
+ { DSIM_DPHY_TIMING( 340, 2, 14, 8, 3, 3, 3, 6, 2, 4), },
+ { DSIM_DPHY_TIMING( 330, 2, 14, 8, 3, 3, 3, 6, 2, 4), },
+ { DSIM_DPHY_TIMING( 320, 2, 14, 8, 3, 3, 3, 5, 2, 4), },
+ { DSIM_DPHY_TIMING( 310, 2, 13, 8, 3, 3, 3, 5, 2, 3), },
+ { DSIM_DPHY_TIMING( 300, 2, 13, 8, 3, 3, 3, 5, 2, 3), },
+ { DSIM_DPHY_TIMING( 290, 2, 12, 8, 3, 2, 2, 5, 2, 3), },
+ { DSIM_DPHY_TIMING( 280, 2, 12, 8, 3, 2, 2, 5, 2, 3), },
+ { DSIM_DPHY_TIMING( 270, 2, 11, 8, 3, 2, 2, 5, 2, 3), },
+ { DSIM_DPHY_TIMING( 260, 1, 11, 8, 3, 2, 2, 5, 1, 3), },
+ { DSIM_DPHY_TIMING( 250, 1, 10, 8, 2, 2, 2, 5, 1, 3), },
+ { DSIM_DPHY_TIMING( 240, 1, 9, 8, 2, 2, 1, 5, 1, 3), },
+ { DSIM_DPHY_TIMING( 230, 1, 8, 8, 2, 2, 1, 5, 1, 2), },
+ { DSIM_DPHY_TIMING( 220, 1, 8, 8, 2, 2, 1, 5, 1, 2), },
+ { DSIM_DPHY_TIMING( 210, 1, 7, 7, 2, 2, 1, 4, 1, 2), },
+ { DSIM_DPHY_TIMING( 200, 1, 7, 7, 2, 2, 1, 4, 1, 2), },
+ { DSIM_DPHY_TIMING( 190, 1, 7, 7, 2, 1, 1, 4, 1, 2), },
+ { DSIM_DPHY_TIMING( 180, 1, 6, 7, 2, 1, 0, 4, 1, 2), },
+ { DSIM_DPHY_TIMING( 170, 1, 6, 7, 2, 1, 0, 4, 1, 2), },
+ { DSIM_DPHY_TIMING( 160, 1, 6, 7, 2, 1, 0, 4, 1, 2), },
+ { DSIM_DPHY_TIMING( 150, 0, 5, 7, 2, 1, 0, 4, 1, 1), },
+ { DSIM_DPHY_TIMING( 140, 0, 5, 7, 1, 1, 0, 4, 1, 1), },
+ { DSIM_DPHY_TIMING( 130, 0, 4, 7, 1, 1, 0, 4, 0, 1), },
+ { DSIM_DPHY_TIMING( 120, 0, 4, 7, 1, 1, 0, 4, 0, 1), },
+ { DSIM_DPHY_TIMING( 110, 0, 3, 7, 1, 0, 0, 4, 0, 1), },
+ { DSIM_DPHY_TIMING( 100, 0, 3, 7, 1, 0, 0, 3, 0, 1), },
+ { DSIM_DPHY_TIMING( 90, 0, 2, 7, 1, 0, 0, 3, 0, 1), },
+ { DSIM_DPHY_TIMING( 80, 0, 2, 6, 1, 0, 0, 3, 0, 1), },
+};
+
+#endif
diff --git a/drivers/gpu/drm/imx/sec_mipi_dsim-imx.c b/drivers/gpu/drm/imx/sec_mipi_dsim-imx.c
new file mode 100644
index 000000000000..4542b1ddc219
--- /dev/null
+++ b/drivers/gpu/drm/imx/sec_mipi_dsim-imx.c
@@ -0,0 +1,583 @@
+/*
+ * Samsung MIPI DSI Host Controller on IMX
+ *
+ * Copyright 2018-2022 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/busfreq-imx.h>
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/mfd/syscon.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/reset.h>
+#include <drm/bridge/sec_mipi_dsim.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_modeset_helper_vtables.h>
+#include <drm/drm_simple_kms_helper.h>
+
+#include "imx-drm.h"
+#include "sec_mipi_dphy_ln14lpp.h"
+#include "sec_mipi_pll_1432x.h"
+
+#define DRIVER_NAME "imx_sec_dsim_drv"
+
+/* fixed phy ref clk rate */
+#define PHY_REF_CLK 12000
+
+struct imx_sec_dsim_device {
+ struct device *dev;
+ void __iomem *base;
+ int irq;
+ struct clk *clk_cfg;
+ struct clk *clk_pllref;
+ struct drm_encoder encoder;
+
+ struct reset_control *soft_resetn;
+ struct reset_control *clk_enable;
+ struct reset_control *mipi_reset;
+
+ atomic_t rpm_suspended;
+
+ bool enabled;
+};
+
+#define enc_to_dsim(enc) container_of(enc, struct imx_sec_dsim_device, encoder)
+
+static struct imx_sec_dsim_device *dsim_dev;
+
+#if CONFIG_PM
+static int imx_sec_dsim_runtime_suspend(struct device *dev);
+static int imx_sec_dsim_runtime_resume(struct device *dev);
+#else
+static int imx_sec_dsim_runtime_suspend(struct device *dev)
+{
+ return 0;
+}
+static int imx_sec_dsim_runtime_resume(struct device *dev)
+{
+ return 0;
+}
+#endif
+
+static int sec_dsim_rstc_reset(struct reset_control *rstc, bool assert)
+{
+ int ret;
+
+ if (!rstc)
+ return 0;
+
+ ret = assert ? reset_control_assert(rstc) :
+ reset_control_deassert(rstc);
+
+ return ret;
+}
+
+static struct drm_crtc *
+imx_sec_dsim_encoder_get_new_crtc(struct drm_encoder *encoder,
+ struct drm_atomic_state *state)
+{
+ struct drm_connector *connector;
+ struct drm_connector_state *conn_state;
+
+ connector = drm_atomic_get_new_connector_for_encoder(state, encoder);
+ if (!connector)
+ return NULL;
+
+ conn_state = drm_atomic_get_new_connector_state(state, connector);
+ if (!conn_state)
+ return NULL;
+
+ return conn_state->crtc;
+}
+
+static void
+imx_sec_dsim_encoder_atomic_enable(struct drm_encoder *encoder,
+ struct drm_atomic_state *state)
+{
+ int ret;
+ struct imx_sec_dsim_device *dsim_dev = enc_to_dsim(encoder);
+ struct drm_crtc *crtc;
+ struct drm_crtc_state *old_crtc_state;
+
+ crtc = imx_sec_dsim_encoder_get_new_crtc(encoder, state);
+ if (!crtc) {
+ dev_err(dsim_dev->dev, "encoder is enabling without CRTC\n");
+ return;
+ }
+
+ old_crtc_state = drm_atomic_get_old_crtc_state(state, crtc);
+ /* Coming back from self refresh, nothing to do. */
+ if (old_crtc_state && old_crtc_state->self_refresh_active &&
+ dsim_dev->enabled)
+ return;
+
+ if (dsim_dev->enabled)
+ return;
+
+ pm_runtime_get_sync(dsim_dev->dev);
+
+ ret = sec_dsim_rstc_reset(dsim_dev->mipi_reset, false);
+ if (ret)
+ dev_err(dsim_dev->dev, "deassert mipi_reset failed\n");
+
+ dsim_dev->enabled = true;
+}
+
+static void
+imx_sec_dsim_encoder_atomic_disable(struct drm_encoder *encoder,
+ struct drm_atomic_state *state)
+{
+ int ret;
+ struct imx_sec_dsim_device *dsim_dev = enc_to_dsim(encoder);
+ struct drm_crtc *crtc;
+ struct drm_crtc_state *new_crtc_state;
+
+ crtc = imx_sec_dsim_encoder_get_new_crtc(encoder, state);
+ /* No CRTC means we're doing a full shutdown. */
+ if (!crtc)
+ goto disable;
+
+ new_crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
+ /* Don't do disablement operation if we're entering PSR. */
+ if (!new_crtc_state || new_crtc_state->self_refresh_active)
+ return;
+
+disable:
+ if (!dsim_dev->enabled)
+ return;
+
+ ret = sec_dsim_rstc_reset(dsim_dev->mipi_reset, true);
+ if (ret)
+ dev_err(dsim_dev->dev, "deassert mipi_reset failed\n");
+
+ pm_runtime_put_sync(dsim_dev->dev);
+
+ dsim_dev->enabled = false;
+}
+
+static int imx_sec_dsim_encoder_atomic_check(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ int ret;
+ struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode;
+ struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc_state);
+ struct drm_bridge *bridge = drm_bridge_chain_get_first_bridge(encoder);
+ struct drm_bridge_state *bridge_state;
+ struct drm_bus_cfg *input_bus_cfg;
+
+ /* check pll out */
+ ret = sec_mipi_dsim_check_pll_out(bridge->driver_private,
+ adjusted_mode);
+ if (ret)
+ return ret;
+
+ bridge_state = drm_atomic_get_new_bridge_state(crtc_state->state,
+ bridge);
+
+ if (WARN_ON(!bridge_state))
+ return -ENODEV;
+
+ input_bus_cfg = &bridge_state->input_bus_cfg;
+
+ imx_crtc_state->bus_format = input_bus_cfg->format;
+ imx_crtc_state->bus_flags = input_bus_cfg->flags;
+
+ return 0;
+}
+
+static const struct drm_encoder_helper_funcs imx_sec_dsim_encoder_helper_funcs = {
+ .atomic_enable = imx_sec_dsim_encoder_atomic_enable,
+ .atomic_disable = imx_sec_dsim_encoder_atomic_disable,
+ .atomic_check = imx_sec_dsim_encoder_atomic_check,
+};
+
+static int sec_dsim_determine_pll_ref_rate(u32 *rate, u32 min, u32 max)
+{
+ int ret;
+ struct device *dev = dsim_dev->dev;
+ u32 req_rate = PHY_REF_CLK;
+ unsigned long get_rate;
+
+ ret = of_property_read_u32(dev->of_node, "pref-rate", &req_rate);
+ if (!ret) {
+ if (req_rate != clamp(req_rate, min, max)) {
+ dev_warn(dev, "invalid requested PLL ref clock rate : %u\n", req_rate);
+ req_rate = PHY_REF_CLK;
+ dev_warn(dev, "use default clock rate : %u\n", req_rate);
+ }
+ }
+
+set_rate:
+ ret = clk_set_rate(dsim_dev->clk_pllref, ((unsigned long)req_rate) * 1000);
+ if (ret)
+ return ret;
+
+ get_rate = clk_get_rate(dsim_dev->clk_pllref);
+ if (!get_rate)
+ return -EINVAL;
+
+ /* PLL ref clock rate should be set precisely */
+ if (get_rate != req_rate * 1000) {
+ /* default clock rate should can be set precisely */
+ if (WARN_ON(unlikely(req_rate == PHY_REF_CLK)))
+ return -EINVAL;
+
+ dev_warn(dev, "request rate %u cannot be satisfied\n", req_rate);
+ req_rate = PHY_REF_CLK;
+ dev_warn(dev, "use default clock rate : %u\n", req_rate);
+
+ goto set_rate;
+ }
+
+ *rate = req_rate;
+
+ return 0;
+}
+
+static const struct sec_mipi_dsim_plat_data imx8mm_mipi_dsim_plat_data = {
+ .version = 0x1060200,
+ .max_data_lanes = 4,
+ .max_data_rate = 1500000000ULL,
+ .dphy_pll = &pll_1432x,
+ .dphy_timing = dphy_timing_ln14lpp_v1p2,
+ .num_dphy_timing = ARRAY_SIZE(dphy_timing_ln14lpp_v1p2),
+ .dphy_timing_cmp = dphy_timing_default_cmp,
+ .mode_valid = NULL,
+ .determine_pll_ref_rate = sec_dsim_determine_pll_ref_rate,
+};
+
+static const struct of_device_id imx_sec_dsim_dt_ids[] = {
+ {
+ .compatible = "fsl,imx8mm-mipi-dsim",
+ .data = &imx8mm_mipi_dsim_plat_data,
+ },
+ {
+ .compatible = "fsl,imx8mn-mipi-dsim",
+ .data = &imx8mm_mipi_dsim_plat_data,
+ },
+ {
+ .compatible = "fsl,imx8mp-mipi-dsim",
+ .data = &imx8mm_mipi_dsim_plat_data,
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_sec_dsim_dt_ids);
+
+static int sec_dsim_of_parse_resets(struct imx_sec_dsim_device *dsim)
+{
+ int ret;
+ struct device *dev = dsim->dev;
+ struct device_node *np = dev->of_node;
+ struct device_node *parent, *child;
+ struct of_phandle_args args;
+ struct reset_control *rstc;
+ const char *compat;
+ uint32_t len, rstc_num = 0;
+
+ /* TODO: bypass resets for imx8mp platform */
+ compat = of_get_property(np, "compatible", NULL);
+ if (unlikely(!compat))
+ return -ENODEV;
+
+ len = strlen(compat);
+ if (!of_compat_cmp(compat, "fsl,imx8mp-mipi-dsim", len))
+ return 0;
+
+ ret = of_parse_phandle_with_args(np, "resets", "#reset-cells",
+ 0, &args);
+ if (ret)
+ return ret;
+
+ parent = args.np;
+ for_each_child_of_node(parent, child) {
+ compat = of_get_property(child, "compatible", NULL);
+ if (!compat)
+ continue;
+
+ rstc = of_reset_control_array_get(child, false, false, true);
+ if (IS_ERR(rstc))
+ continue;
+
+ len = strlen(compat);
+ if (!of_compat_cmp("dsi,soft-resetn", compat, len)) {
+ dsim->soft_resetn = rstc;
+ rstc_num++;
+ } else if (!of_compat_cmp("dsi,clk-enable", compat, len)) {
+ dsim->clk_enable = rstc;
+ rstc_num++;
+ } else if (!of_compat_cmp("dsi,mipi-reset", compat, len)) {
+ dsim->mipi_reset = rstc;
+ rstc_num++;
+ } else
+ dev_warn(dev, "invalid dsim reset node: %s\n", compat);
+ }
+
+ if (!rstc_num) {
+ dev_err(dev, "no invalid reset control exists\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void sec_dsim_of_put_resets(struct imx_sec_dsim_device *dsim)
+{
+ if (dsim->soft_resetn)
+ reset_control_put(dsim->soft_resetn);
+
+ if (dsim->clk_enable)
+ reset_control_put(dsim->clk_enable);
+
+ if (dsim->mipi_reset)
+ reset_control_put(dsim->mipi_reset);
+}
+
+static int imx_sec_dsim_bind(struct device *dev, struct device *master,
+ void *data)
+{
+ int ret;
+ struct drm_device *drm_dev = data;
+ struct device_node *np = dev->of_node;
+ const struct of_device_id *of_id = of_match_device(imx_sec_dsim_dt_ids,
+ dev);
+ const struct sec_mipi_dsim_plat_data *pdata;
+ struct drm_encoder *encoder;
+
+ dev_dbg(dev, "%s: dsim bind begin\n", __func__);
+
+ if (!of_id)
+ return -ENODEV;
+ pdata = of_id->data;
+
+ encoder = &dsim_dev->encoder;
+ ret = imx_drm_encoder_parse_of(drm_dev, encoder, np);
+ if (ret)
+ return ret;
+
+ drm_encoder_helper_add(encoder, &imx_sec_dsim_encoder_helper_funcs);
+
+ ret = drm_simple_encoder_init(drm_dev, encoder, DRM_MODE_ENCODER_DSI);
+ if (ret)
+ return ret;
+
+ /* bind sec dsim bridge */
+ ret = sec_mipi_dsim_bind(dev, master, data, encoder,
+ dsim_dev->base, dsim_dev->irq, pdata);
+ if (ret) {
+ dev_err(dev, "failed to bind sec dsim bridge: %d\n", ret);
+ drm_encoder_cleanup(encoder);
+
+ /* If no panel or bridge connected, just return 0
+ * to make component core to believe it is bound
+ * successfully to allow other components can be
+ * bound continuously, since in component core,
+ * it follows 'one fails, all fail'. It is useful
+ * when there exists multiple heads display.
+ */
+ if (ret == -ENODEV)
+ return 0;
+
+ return ret;
+ }
+
+ dev_dbg(dev, "%s: dsim bind end\n", __func__);
+
+ return 0;
+}
+
+static void imx_sec_dsim_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ if (!dsim_dev->encoder.dev)
+ return;
+
+ drm_encoder_cleanup(&dsim_dev->encoder);
+
+ sec_mipi_dsim_unbind(dev, master, data);
+}
+
+static const struct component_ops imx_sec_dsim_ops = {
+ .bind = imx_sec_dsim_bind,
+ .unbind = imx_sec_dsim_unbind,
+};
+
+static int imx_sec_dsim_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct device *dev = &pdev->dev;
+
+ dev_dbg(dev, "%s: dsim probe begin\n", __func__);
+
+ dsim_dev = devm_kzalloc(dev, sizeof(*dsim_dev), GFP_KERNEL);
+ if (!dsim_dev) {
+ dev_err(dev, "Unable to allocate 'dsim_dev'\n");
+ return -ENOMEM;
+ }
+ dsim_dev->dev = dev;
+
+ dsim_dev->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(dsim_dev->base))
+ return PTR_ERR(dsim_dev->base);
+
+ dsim_dev->irq = platform_get_irq(pdev, 0);
+ if (dsim_dev->irq < 0)
+ return -ENODEV;
+
+ dsim_dev->clk_cfg = devm_clk_get(dev, "cfg");
+ if (IS_ERR(dsim_dev->clk_cfg))
+ return PTR_ERR(dsim_dev->clk_cfg);
+
+ dsim_dev->clk_pllref = devm_clk_get(dev, "pll-ref");
+ if (IS_ERR(dsim_dev->clk_pllref))
+ return PTR_ERR(dsim_dev->clk_pllref);
+
+ ret = sec_dsim_of_parse_resets(dsim_dev);
+ if (ret)
+ return ret;
+
+ atomic_set(&dsim_dev->rpm_suspended, 1);
+
+ pm_runtime_enable(dev);
+
+ ret = component_add(dev, &imx_sec_dsim_ops);
+ if (ret) {
+ pm_runtime_disable(dev);
+ sec_dsim_of_put_resets(dsim_dev);
+ dev_err_probe(dev, ret, "Failed to add component\n");
+ }
+
+ return ret;
+}
+
+static int imx_sec_dsim_remove(struct platform_device *pdev)
+{
+ component_del(&pdev->dev, &imx_sec_dsim_ops);
+ pm_runtime_disable(&pdev->dev);
+ sec_dsim_of_put_resets(dsim_dev);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int imx_sec_dsim_suspend(struct device *dev)
+{
+ return imx_sec_dsim_runtime_suspend(dev);
+}
+
+static int imx_sec_dsim_resume(struct device *dev)
+{
+ return imx_sec_dsim_runtime_resume(dev);
+}
+#endif
+
+#ifdef CONFIG_PM
+static int imx_sec_dsim_runtime_suspend(struct device *dev)
+{
+ /* check sec dsim is bound or not */
+ if (unlikely(!dsim_dev->encoder.dev))
+ return 0;
+
+ if (atomic_inc_return(&dsim_dev->rpm_suspended) > 1)
+ return 0;
+
+ sec_mipi_dsim_suspend(dev);
+
+ clk_disable_unprepare(dsim_dev->clk_cfg);
+ clk_disable_unprepare(dsim_dev->clk_pllref);
+
+ release_bus_freq(BUS_FREQ_HIGH);
+
+ return 0;
+}
+
+static int imx_sec_dsim_runtime_resume(struct device *dev)
+{
+ int ret;
+
+ /* check sec dsim is bound or not */
+ if (unlikely(!dsim_dev->encoder.dev))
+ return 0;
+
+ if (unlikely(!atomic_read(&dsim_dev->rpm_suspended))) {
+ dev_warn(dsim_dev->dev,
+ "Unbalanced %s!\n", __func__);
+ return 0;
+ }
+
+ if (!atomic_dec_and_test(&dsim_dev->rpm_suspended))
+ return 0;
+
+ request_bus_freq(BUS_FREQ_HIGH);
+
+ ret = clk_prepare_enable(dsim_dev->clk_pllref);
+ if (WARN_ON(unlikely(ret)))
+ return ret;
+
+ ret = clk_prepare_enable(dsim_dev->clk_cfg);
+ if (WARN_ON(unlikely(ret)))
+ return ret;
+
+ ret = sec_dsim_rstc_reset(dsim_dev->soft_resetn, false);
+ if (ret) {
+ dev_err(dev, "deassert soft_resetn failed\n");
+ return ret;
+ }
+
+ ret = sec_dsim_rstc_reset(dsim_dev->clk_enable, true);
+ if (ret) {
+ dev_err(dev, "assert clk_enable failed\n");
+ return ret;
+ }
+
+ ret = sec_dsim_rstc_reset(dsim_dev->mipi_reset, false);
+ if (ret) {
+ dev_err(dev, "deassert mipi_reset failed\n");
+ return ret;
+ }
+
+ sec_mipi_dsim_resume(dev);
+
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops imx_sec_dsim_pm_ops = {
+ SET_LATE_SYSTEM_SLEEP_PM_OPS(imx_sec_dsim_suspend,
+ imx_sec_dsim_resume)
+ SET_RUNTIME_PM_OPS(imx_sec_dsim_runtime_suspend,
+ imx_sec_dsim_runtime_resume,
+ NULL)
+};
+
+struct platform_driver imx_sec_dsim_driver = {
+ .probe = imx_sec_dsim_probe,
+ .remove = imx_sec_dsim_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = imx_sec_dsim_dt_ids,
+ .pm = &imx_sec_dsim_pm_ops,
+ },
+};
+
+module_platform_driver(imx_sec_dsim_driver);
+
+MODULE_DESCRIPTION("NXP i.MX MIPI DSI Host Controller driver");
+MODULE_AUTHOR("Fancy Fang <chen.fang@nxp.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/imx/sec_mipi_pll_1432x.h b/drivers/gpu/drm/imx/sec_mipi_pll_1432x.h
new file mode 100644
index 000000000000..c387b2facd59
--- /dev/null
+++ b/drivers/gpu/drm/imx/sec_mipi_pll_1432x.h
@@ -0,0 +1,49 @@
+/*
+ * Samsung MIPI DSIM PLL_1432X
+ *
+ * Copyright 2019 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __SEC_DSIM_PLL_1432X_H__
+#define __SEC_DSIM_PLL_1432X_H__
+
+#include <drm/bridge/sec_mipi_dsim.h>
+/*
+ * DSIM PLL_1432X setting guide from spec:
+ *
+ * Fout(bitclk) = ((m + k / 65536) * Fin) / (p * 2^s), and
+ * p = P[5:0], m = M[9:0], s = S[2:0], k = K[15:0];
+ *
+ * Fpref = Fin / p
+ * Fin: [6MHz ~ 300MHz], Fpref: [2MHz ~ 30MHz]
+ *
+ * Fvco = ((m + k / 65536) * Fin) / p
+ * Fvco: [1050MHz ~ 2100MHz]
+ *
+ * 1 <= P[5:0] <= 63, 64 <= M[9:0] <= 1023,
+ * 0 <= S[2:0] <= 5, -32768 <= K[15:0] <= 32767
+ *
+ */
+
+const struct sec_mipi_dsim_pll pll_1432x = {
+ .p = { .min = 1, .max = 63, },
+ .m = { .min = 64, .max = 1023, },
+ .s = { .min = 0, .max = 5, },
+ .k = { .min = 0, .max = 32768, }, /* abs(k) */
+ .fin = { .min = 6000, .max = 300000, }, /* in KHz */
+ .fpref = { .min = 2000, .max = 30000, }, /* in KHz */
+ .fvco = { .min = 1050000, .max = 2100000, }, /* in KHz */
+};
+
+#endif
+
diff --git a/drivers/gpu/drm/mxsfb/mxsfb_drv.c b/drivers/gpu/drm/mxsfb/mxsfb_drv.c
index 86d78634a979..ced16169035b 100644
--- a/drivers/gpu/drm/mxsfb/mxsfb_drv.c
+++ b/drivers/gpu/drm/mxsfb/mxsfb_drv.c
@@ -263,6 +263,9 @@ static int mxsfb_load(struct drm_device *drm,
goto err_vblank;
}
+ of_property_read_u32(drm->dev->of_node, "max-memory-bandwidth",
+ &mxsfb->max_bw);
+
drm->mode_config.min_width = MXSFB_MIN_XRES;
drm->mode_config.min_height = MXSFB_MIN_YRES;
drm->mode_config.max_width = MXSFB_MAX_XRES;
diff --git a/drivers/gpu/drm/mxsfb/mxsfb_drv.h b/drivers/gpu/drm/mxsfb/mxsfb_drv.h
index ddb5b0417a82..effeb1c7c764 100644
--- a/drivers/gpu/drm/mxsfb/mxsfb_drv.h
+++ b/drivers/gpu/drm/mxsfb/mxsfb_drv.h
@@ -44,6 +44,7 @@ struct mxsfb_drm_private {
struct drm_encoder encoder;
struct drm_connector *connector;
struct drm_bridge *bridge;
+ u32 max_bw;
};
static inline struct mxsfb_drm_private *
diff --git a/drivers/gpu/drm/mxsfb/mxsfb_kms.c b/drivers/gpu/drm/mxsfb/mxsfb_kms.c
index 988bc4fbd78d..328857c4b556 100644
--- a/drivers/gpu/drm/mxsfb/mxsfb_kms.c
+++ b/drivers/gpu/drm/mxsfb/mxsfb_kms.c
@@ -8,6 +8,7 @@
* Copyright (C) 2008 Embedded Alley Solutions, Inc All Rights Reserved.
*/
+#include <linux/busfreq-imx.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/iopoll.h>
@@ -53,9 +54,19 @@ static void mxsfb_set_formats(struct mxsfb_drm_private *mxsfb,
struct drm_device *drm = mxsfb->drm;
const u32 format = mxsfb->crtc.primary->state->fb->format->format;
u32 ctrl, ctrl1;
-
- DRM_DEV_DEBUG_DRIVER(drm->dev, "Using bus_format: 0x%08X\n",
- bus_format);
+ bool bgr_format = true;
+
+ /* Do some clean-up that we might have from a previous mode */
+ ctrl = CTRL_SHIFT_DIR(1);
+ ctrl |= CTRL_SHIFT_NUM(0xff);
+ ctrl |= CTRL_SET_WORD_LENGTH(0xff);
+ ctrl |= CTRL_DF16;
+ ctrl |= CTRL_SET_BUS_WIDTH(0xff);
+ writel(ctrl, mxsfb->base + LCDC_CTRL + REG_CLR);
+ if (mxsfb->devdata->has_ctrl2)
+ writel(CTRL2_ODD_LINE_PATTERN(CTRL2_LINE_PATTERN_CLR) |
+ CTRL2_EVEN_LINE_PATTERN(CTRL2_LINE_PATTERN_CLR),
+ mxsfb->base + LCDC_V4_CTRL2 + REG_CLR);
ctrl = CTRL_BYPASS_COUNT | CTRL_MASTER;
@@ -63,20 +74,70 @@ static void mxsfb_set_formats(struct mxsfb_drm_private *mxsfb,
ctrl1 = readl(mxsfb->base + LCDC_CTRL1);
ctrl1 &= CTRL1_CUR_FRAME_DONE_IRQ_EN | CTRL1_CUR_FRAME_DONE_IRQ;
+ DRM_DEV_DEBUG_DRIVER(drm->dev, "Setting up %p4cc mode\n", &format);
+
switch (format) {
- case DRM_FORMAT_RGB565:
- dev_dbg(drm->dev, "Setting up RGB565 mode\n");
- ctrl |= CTRL_WORD_LENGTH_16;
+ case DRM_FORMAT_BGR565: /* BG16 */
+ if (!mxsfb->devdata->has_ctrl2)
+ goto err;
+ writel(CTRL2_ODD_LINE_PATTERN(CTRL2_LINE_PATTERN_BGR) |
+ CTRL2_EVEN_LINE_PATTERN(CTRL2_LINE_PATTERN_BGR),
+ mxsfb->base + LCDC_V4_CTRL2 + REG_SET);
+ fallthrough;
+ case DRM_FORMAT_RGB565: /* RG16 */
+ ctrl |= CTRL_SET_WORD_LENGTH(0);
+ ctrl &= ~CTRL_DF16;
ctrl1 |= CTRL1_SET_BYTE_PACKAGING(0xf);
break;
- case DRM_FORMAT_XRGB8888:
- dev_dbg(drm->dev, "Setting up XRGB8888 mode\n");
- ctrl |= CTRL_WORD_LENGTH_24;
+ case DRM_FORMAT_XBGR1555: /* XB15 */
+ fallthrough;
+ case DRM_FORMAT_ABGR1555: /* AB15 */
+ if (!mxsfb->devdata->has_ctrl2)
+ goto err;
+ writel(CTRL2_ODD_LINE_PATTERN(CTRL2_LINE_PATTERN_BGR) |
+ CTRL2_EVEN_LINE_PATTERN(CTRL2_LINE_PATTERN_BGR),
+ mxsfb->base + LCDC_V4_CTRL2 + REG_SET);
+ fallthrough;
+ case DRM_FORMAT_XRGB1555: /* XR15 */
+ fallthrough;
+ case DRM_FORMAT_ARGB1555: /* AR15 */
+ ctrl |= CTRL_SET_WORD_LENGTH(0);
+ ctrl |= CTRL_DF16;
+ ctrl1 |= CTRL1_SET_BYTE_PACKAGING(0xf);
+ break;
+ case DRM_FORMAT_RGBX8888: /* RX24 */
+ fallthrough;
+ case DRM_FORMAT_RGBA8888: /* RA24 */
+ /* RGBX - > 0RGB */
+ ctrl |= CTRL_SHIFT_DIR(1);
+ ctrl |= CTRL_SHIFT_NUM(8);
+ bgr_format = false;
+ fallthrough;
+ case DRM_FORMAT_XBGR8888: /* XB24 */
+ fallthrough;
+ case DRM_FORMAT_ABGR8888: /* AB24 */
+ if (bgr_format) {
+ if (!mxsfb->devdata->has_ctrl2)
+ goto err;
+ writel(CTRL2_ODD_LINE_PATTERN(CTRL2_LINE_PATTERN_BGR) |
+ CTRL2_EVEN_LINE_PATTERN(CTRL2_LINE_PATTERN_BGR),
+ mxsfb->base + LCDC_V4_CTRL2 + REG_SET);
+ }
+ fallthrough;
+ case DRM_FORMAT_XRGB8888: /* XR24 */
+ fallthrough;
+ case DRM_FORMAT_ARGB8888: /* AR24 */
+ ctrl |= CTRL_SET_WORD_LENGTH(3);
/* Do not use packed pixels = one pixel per word instead. */
ctrl1 |= CTRL1_SET_BYTE_PACKAGING(0x7);
break;
+ default:
+ goto err;
}
+ DRM_DEV_DEBUG_DRIVER(drm->dev, "Using bus_format: 0x%08X\n",
+ bus_format);
+
switch (bus_format) {
case MEDIA_BUS_FMT_RGB565_1X16:
ctrl |= CTRL_BUS_WIDTH_16;
@@ -93,7 +154,11 @@ static void mxsfb_set_formats(struct mxsfb_drm_private *mxsfb,
}
writel(ctrl1, mxsfb->base + LCDC_CTRL1);
- writel(ctrl, mxsfb->base + LCDC_CTRL);
+ writel(ctrl, mxsfb->base + LCDC_CTRL + REG_SET);
+
+ return;
+err:
+ dev_err(drm->dev, "Unhandled pixel format: %p4cc\n", &format);
}
static void mxsfb_enable_controller(struct mxsfb_drm_private *mxsfb)
@@ -112,6 +177,9 @@ static void mxsfb_enable_controller(struct mxsfb_drm_private *mxsfb)
writel(reg, mxsfb->base + LCDC_V4_CTRL2);
}
+ /* De-assert LCD Reset bit */
+ writel(CTRL_LCD_RESET, mxsfb->base + LCDC_CTRL1 + REG_SET);
+
/* If it was disabled, re-enable the mode again */
writel(CTRL_DOTCLK_MODE, mxsfb->base + LCDC_CTRL + REG_SET);
@@ -156,6 +224,11 @@ static void mxsfb_disable_controller(struct mxsfb_drm_private *mxsfb)
{
u32 reg;
+ writel(CTRL_RUN, mxsfb->base + LCDC_CTRL + REG_CLR);
+
+ /* Assert LCD Reset bit */
+ writel(CTRL_LCD_RESET, mxsfb->base + LCDC_CTRL1 + REG_CLR);
+
/*
* Even if we disable the controller here, it will still continue
* until its FIFOs are running out of data
@@ -307,6 +380,28 @@ static void mxsfb_crtc_mode_set_nofb(struct mxsfb_drm_private *mxsfb,
mxsfb->base + LCDC_VDCTRL4);
}
+static enum drm_mode_status mxsfb_crtc_mode_valid(struct drm_crtc *crtc,
+ const struct drm_display_mode *mode)
+{
+ struct mxsfb_drm_private *mxsfb = to_mxsfb_drm_private(crtc->dev);
+ u32 bpp;
+ u64 bw;
+
+ if (!crtc->primary->state->fb)
+ bpp = 32;
+ else
+ bpp = crtc->primary->state->fb->format->cpp[0] * 8;
+
+ bw = (u64)mode->clock * 1000;
+ bw = bw * mode->hdisplay * mode->vdisplay * (bpp / 8);
+ bw = div_u64(bw, mode->htotal * mode->vtotal);
+
+ if (mxsfb->max_bw && bw > mxsfb->max_bw)
+ return MODE_BAD;
+
+ return MODE_OK;
+}
+
static int mxsfb_crtc_atomic_check(struct drm_crtc *crtc,
struct drm_atomic_state *state)
{
@@ -351,6 +446,7 @@ static void mxsfb_crtc_atomic_enable(struct drm_crtc *crtc,
u32 bus_format = 0;
dma_addr_t paddr;
+ request_bus_freq(BUS_FREQ_HIGH);
pm_runtime_get_sync(drm->dev);
mxsfb_enable_axi_clk(mxsfb);
@@ -415,6 +511,7 @@ static void mxsfb_crtc_atomic_disable(struct drm_crtc *crtc,
mxsfb_disable_axi_clk(mxsfb);
pm_runtime_put_sync(drm->dev);
+ release_bus_freq(BUS_FREQ_HIGH);
}
static int mxsfb_crtc_enable_vblank(struct drm_crtc *crtc)
@@ -438,6 +535,7 @@ static void mxsfb_crtc_disable_vblank(struct drm_crtc *crtc)
}
static const struct drm_crtc_helper_funcs mxsfb_crtc_helper_funcs = {
+ .mode_valid = mxsfb_crtc_mode_valid,
.atomic_check = mxsfb_crtc_atomic_check,
.atomic_flush = mxsfb_crtc_atomic_flush,
.atomic_enable = mxsfb_crtc_atomic_enable,
@@ -474,25 +572,120 @@ static int mxsfb_plane_atomic_check(struct drm_plane *plane,
plane);
struct mxsfb_drm_private *mxsfb = to_mxsfb_drm_private(plane->dev);
struct drm_crtc_state *crtc_state;
+ struct drm_framebuffer *fb = plane_state->fb;
+ struct drm_plane_state *old_state = plane->state;
+ struct drm_framebuffer *old_fb = old_state->fb;
crtc_state = drm_atomic_get_new_crtc_state(state,
&mxsfb->crtc);
+ if (plane->type == DRM_PLANE_TYPE_PRIMARY && old_fb && fb &&
+ old_fb->pitches[0] != fb->pitches[0])
+ crtc_state->mode_changed = true;
+
return drm_atomic_helper_check_plane_state(plane_state, crtc_state,
DRM_PLANE_HELPER_NO_SCALING,
DRM_PLANE_HELPER_NO_SCALING,
false, true);
}
+static void mxsfb_set_fb_hcrop(struct mxsfb_drm_private *mxsfb, u32 src_w, u32 fb_w)
+{
+ u32 mask_cnt;
+ u32 vdctrl3, vdctrl4, transfer_count;
+
+ if (src_w == fb_w) {
+ writel(0x0, mxsfb->base + HW_EPDC_PIGEON_12_0);
+ writel(0x0, mxsfb->base + HW_EPDC_PIGEON_12_1);
+
+ return;
+ }
+
+ transfer_count = readl(mxsfb->base + LCDC_V4_TRANSFER_COUNT);
+ transfer_count &= ~TRANSFER_COUNT_SET_HCOUNT(0xffff);
+ transfer_count |= TRANSFER_COUNT_SET_HCOUNT(fb_w);
+ writel(transfer_count, mxsfb->base + LCDC_V4_TRANSFER_COUNT);
+
+ vdctrl4 = readl(mxsfb->base + LCDC_VDCTRL4);
+ vdctrl4 &= ~SET_DOTCLK_H_VALID_DATA_CNT(0x3ffff);
+ vdctrl4 |= SET_DOTCLK_H_VALID_DATA_CNT(fb_w);
+ writel(vdctrl4, mxsfb->base + LCDC_VDCTRL4);
+
+ /* configure related pigeon registers */
+ vdctrl3 = readl(mxsfb->base + LCDC_VDCTRL3);
+ mask_cnt = GET_HOR_WAIT_CNT(vdctrl3) - 5;
+
+ writel(PIGEON_12_0_SET_STATE_MASK(0x24) |
+ PIGEON_12_0_SET_MASK_CNT(mask_cnt) |
+ PIGEON_12_0_SET_MASK_CNT_SEL(0x6) |
+ PIGEON_12_0_POL_ACTIVE_LOW |
+ PIGEON_12_0_EN,
+ mxsfb->base + HW_EPDC_PIGEON_12_0);
+
+ writel(PIGEON_12_1_SET_CLR_CNT(src_w) |
+ PIGEON_12_1_SET_SET_CNT(0x0),
+ mxsfb->base + HW_EPDC_PIGEON_12_1);
+
+ writel(0, mxsfb->base + HW_EPDC_PIGEON_12_2);
+}
+
static void mxsfb_plane_primary_atomic_update(struct drm_plane *plane,
struct drm_atomic_state *state)
{
struct mxsfb_drm_private *mxsfb = to_mxsfb_drm_private(plane->dev);
+ struct drm_plane_state *new_pstate = drm_atomic_get_new_plane_state(state,
+ plane);
+ struct drm_plane_state *old_pstate = drm_atomic_get_old_plane_state(state,
+ plane);
+ struct drm_framebuffer *fb = new_pstate->fb;
+ struct drm_framebuffer *old_fb = old_pstate->fb;
dma_addr_t paddr;
+ u32 src_off, src_w, stride, cpp = 0;
paddr = mxsfb_get_fb_paddr(plane);
- if (paddr)
- writel(paddr, mxsfb->base + mxsfb->devdata->next_buf);
+ if (!paddr)
+ return;
+
+ /* We will use the 'has_ctrl2' to assume IP version larger than 4 */
+ if (mxsfb->devdata->has_ctrl2) {
+ cpp = fb->format->cpp[0];
+ src_off = (new_pstate->src_y >> 16) * fb->pitches[0] +
+ (new_pstate->src_x >> 16) * cpp;
+ paddr += fb->offsets[0] + src_off;
+ }
+
+ writel(paddr, mxsfb->base + mxsfb->devdata->next_buf);
+
+ if (mxsfb->devdata->has_ctrl2 &&
+ unlikely(drm_atomic_crtc_needs_modeset(new_pstate->crtc->state))) {
+ stride = DIV_ROUND_UP(fb->pitches[0], cpp);
+ src_w = new_pstate->src_w >> 16;
+ mxsfb_set_fb_hcrop(mxsfb, src_w, stride);
+ }
+
+ /* Update format if changed */
+ if (old_fb && old_fb->format->format != fb->format->format) {
+ u32 bus_format = 0;
+ struct drm_bridge_state *bridge_state;
+
+ /* If there is a bridge attached to the LCDIF, use its bus format */
+ if (mxsfb->bridge) {
+ bridge_state =
+ drm_atomic_get_new_bridge_state(state,
+ mxsfb->bridge);
+ bus_format = bridge_state->input_bus_cfg.format;
+ }
+
+ /* If there is no bridge, use bus format from connector */
+ if (!bus_format && mxsfb->connector->display_info.num_bus_formats)
+ bus_format = mxsfb->connector->display_info.bus_formats[0];
+
+ /* If all else fails, default to RGB888_1X24 */
+ if (!bus_format)
+ bus_format = MEDIA_BUS_FMT_RGB888_1X24;
+
+ mxsfb_set_formats(mxsfb, bus_format);
+ }
}
static void mxsfb_plane_overlay_atomic_update(struct drm_plane *plane,
@@ -585,11 +778,26 @@ static const struct drm_plane_funcs mxsfb_plane_funcs = {
.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
};
-static const uint32_t mxsfb_primary_plane_formats[] = {
+static const uint32_t mxsfb_primary_plane_formats_v3[] = {
DRM_FORMAT_RGB565,
DRM_FORMAT_XRGB8888,
};
+static const uint32_t mxsfb_primary_plane_formats_v4[] = {
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_BGR565,
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_XBGR8888,
+ DRM_FORMAT_ABGR8888,
+ DRM_FORMAT_RGBX8888,
+ DRM_FORMAT_RGBA8888,
+ DRM_FORMAT_ARGB1555,
+ DRM_FORMAT_XRGB1555,
+ DRM_FORMAT_ABGR1555,
+ DRM_FORMAT_XBGR1555,
+};
+
static const uint32_t mxsfb_overlay_plane_formats[] = {
DRM_FORMAT_XRGB4444,
DRM_FORMAT_ARGB4444,
@@ -613,14 +821,23 @@ int mxsfb_kms_init(struct mxsfb_drm_private *mxsfb)
{
struct drm_encoder *encoder = &mxsfb->encoder;
struct drm_crtc *crtc = &mxsfb->crtc;
+ uint32_t const *plane_formats;
+ unsigned int format_count;
int ret;
+ if (!mxsfb->devdata->has_ctrl2) {
+ plane_formats = &mxsfb_primary_plane_formats_v3[0];
+ format_count = ARRAY_SIZE(mxsfb_primary_plane_formats_v3);
+ } else {
+ plane_formats = &mxsfb_primary_plane_formats_v4[0];
+ format_count = ARRAY_SIZE(mxsfb_primary_plane_formats_v4);
+ }
+
drm_plane_helper_add(&mxsfb->planes.primary,
&mxsfb_plane_primary_helper_funcs);
ret = drm_universal_plane_init(mxsfb->drm, &mxsfb->planes.primary, 1,
&mxsfb_plane_funcs,
- mxsfb_primary_plane_formats,
- ARRAY_SIZE(mxsfb_primary_plane_formats),
+ plane_formats, format_count,
mxsfb_modifiers, DRM_PLANE_TYPE_PRIMARY,
NULL);
if (ret)
diff --git a/drivers/gpu/drm/mxsfb/mxsfb_regs.h b/drivers/gpu/drm/mxsfb/mxsfb_regs.h
index 694fea13e893..cc762fc3dcab 100644
--- a/drivers/gpu/drm/mxsfb/mxsfb_regs.h
+++ b/drivers/gpu/drm/mxsfb/mxsfb_regs.h
@@ -21,11 +21,19 @@
#define LCDC_V4_NEXT_BUF 0x50
#define LCDC_V3_CUR_BUF 0x30
#define LCDC_V3_NEXT_BUF 0x40
+#define LCDC_TIMING 0x60
#define LCDC_VDCTRL0 0x70
#define LCDC_VDCTRL1 0x80
#define LCDC_VDCTRL2 0x90
#define LCDC_VDCTRL3 0xa0
#define LCDC_VDCTRL4 0xb0
+#define LCDC_DVICTRL0 0xc0
+#define LCDC_DVICTRL1 0xd0
+#define LCDC_DVICTRL2 0xe0
+#define LCDC_DVICTRL3 0xf0
+#define LCDC_DVICTRL4 0x100
+#define LCDC_V4_DATA 0x180
+#define LCDC_V3_DATA 0x1b0
#define LCDC_V4_DEBUG0 0x1d0
#define LCDC_V3_DEBUG0 0x1f0
#define LCDC_AS_CTRL 0x210
@@ -34,31 +42,55 @@
#define LCDC_AS_CLRKEYLOW 0x240
#define LCDC_AS_CLRKEYHIGH 0x250
+#define REG_PUT(x, h, l) (((x) << (l)) & GENMASK(h, l))
+#define REG_GET(x, h, l) (((x) & GENMASK(h, l)) >> (l))
+
#define CTRL_SFTRST BIT(31)
#define CTRL_CLKGATE BIT(30)
+#define CTRL_SHIFT_DIR(x) REG_PUT((x), 26, 26)
+#define CTRL_SHIFT_NUM(x) REG_PUT((x), 25, 21)
#define CTRL_BYPASS_COUNT BIT(19)
#define CTRL_VSYNC_MODE BIT(18)
#define CTRL_DOTCLK_MODE BIT(17)
#define CTRL_DATA_SELECT BIT(16)
-#define CTRL_BUS_WIDTH_16 (0 << 10)
-#define CTRL_BUS_WIDTH_8 (1 << 10)
-#define CTRL_BUS_WIDTH_18 (2 << 10)
-#define CTRL_BUS_WIDTH_24 (3 << 10)
-#define CTRL_BUS_WIDTH_MASK (0x3 << 10)
-#define CTRL_WORD_LENGTH_16 (0 << 8)
-#define CTRL_WORD_LENGTH_8 (1 << 8)
-#define CTRL_WORD_LENGTH_18 (2 << 8)
-#define CTRL_WORD_LENGTH_24 (3 << 8)
+#define CTRL_INPUT_SWIZZLE(x) REG_PUT((x), 15, 14)
+#define CTRL_CSC_SWIZZLE(x) REG_PUT((x), 13, 12)
+#define CTRL_SET_BUS_WIDTH(x) REG_PUT((x), 11, 10)
+#define CTRL_BUS_WIDTH_MASK REG_PUT((0x3), 11, 10)
+#define CTRL_SET_WORD_LENGTH(x) REG_PUT((x), 9, 8)
#define CTRL_MASTER BIT(5)
#define CTRL_DF16 BIT(3)
#define CTRL_DF18 BIT(2)
#define CTRL_DF24 BIT(1)
#define CTRL_RUN BIT(0)
+#define CTRL_BUS_WIDTH_8 CTRL_SET_BUS_WIDTH(1)
+#define CTRL_BUS_WIDTH_16 CTRL_SET_BUS_WIDTH(0)
+#define CTRL_BUS_WIDTH_18 CTRL_SET_BUS_WIDTH(2)
+#define CTRL_BUS_WIDTH_24 CTRL_SET_BUS_WIDTH(3)
+#define CTRL_WORD_LENGTH_8 CTRL_SET_WORD_LENGTH(1)
+#define CTRL_WORD_LENGTH_16 CTRL_SET_WORD_LENGTH(0)
+#define CTRL_WORD_LENGTH_18 CTRL_SET_WORD_LENGTH(2)
+#define CTRL_WORD_LENGTH_24 CTRL_SET_WORD_LENGTH(3)
+
#define CTRL1_RECOVER_ON_UNDERFLOW BIT(24)
#define CTRL1_FIFO_CLEAR BIT(21)
-#define CTRL1_SET_BYTE_PACKAGING(x) (((x) & 0xf) << 16)
-#define CTRL1_GET_BYTE_PACKAGING(x) (((x) >> 16) & 0xf)
+
+/*
+ * BYTE_PACKAGING
+ *
+ * This bitfield is used to show which data bytes in a 32-bit word area valid.
+ * Default value 0xf indicates that all bytes are valid. For 8-bit transfers,
+ * any combination in this bitfield will mean valid data is present in the
+ * corresponding bytes. In the 16-bit mode, a 16-bit half-word is valid only if
+ * adjacent bits [1:0] or [3:2] or both are 1. A value of 0x0 will mean that
+ * none of the bytes are valid and should not be used. For example, set the bit
+ * field value to 0x7 if the display data is arranged in the 24-bit unpacked
+ * format (A-R-G-B where A value does not have be transmitted).
+ */
+#define CTRL1_SET_BYTE_PACKAGING(x) REG_PUT((x), 19, 16)
+#define CTRL1_GET_BYTE_PACKAGING(x) REG_GET((x), 19, 16)
+
#define CTRL1_CUR_FRAME_DONE_IRQ_EN BIT(13)
#define CTRL1_CUR_FRAME_DONE_IRQ BIT(9)
@@ -69,10 +101,27 @@
#define CTRL2_SET_OUTSTANDING_REQS_16 (0x4 << 21)
#define CTRL2_SET_OUTSTANDING_REQS_MASK (0x7 << 21)
-#define TRANSFER_COUNT_SET_VCOUNT(x) (((x) & 0xffff) << 16)
-#define TRANSFER_COUNT_GET_VCOUNT(x) (((x) >> 16) & 0xffff)
-#define TRANSFER_COUNT_SET_HCOUNT(x) ((x) & 0xffff)
-#define TRANSFER_COUNT_GET_HCOUNT(x) ((x) & 0xffff)
+#define SWIZZLE_LE 0 /* Little-Endian or No swap */
+#define SWIZZLE_BE 1 /* Big-Endian or swap all */
+#define SWIZZLE_HWD 2 /* Swap half-words */
+#define SWIZZLE_HWD_BYTE 3 /* Swap bytes within each half-word */
+
+#define CTRL2_ODD_LINE_PATTERN(x) REG_PUT((x), 18, 16)
+#define CTRL2_EVEN_LINE_PATTERN(x) REG_PUT((x), 14, 12)
+#define CTRL2_LINE_PATTERN_RGB 0
+#define CTRL2_LINE_PATTERN_RBG 1
+#define CTRL2_LINE_PATTERN_GBR 2
+#define CTRL2_LINE_PATTERN_GRB 3
+#define CTRL2_LINE_PATTERN_BRG 4
+#define CTRL2_LINE_PATTERN_BGR 5
+#define CTRL2_LINE_PATTERN_CLR 7
+
+#define CTRL_LCD_RESET BIT(0)
+
+#define TRANSFER_COUNT_SET_VCOUNT(x) REG_PUT((x), 31, 16)
+#define TRANSFER_COUNT_GET_VCOUNT(x) REG_GET((x), 31, 16)
+#define TRANSFER_COUNT_SET_HCOUNT(x) REG_PUT((x), 15, 0)
+#define TRANSFER_COUNT_GET_HCOUNT(x) REG_GET((x), 15, 0)
#define VDCTRL0_ENABLE_PRESENT BIT(28)
#define VDCTRL0_VSYNC_ACT_HIGH BIT(27)
@@ -83,23 +132,23 @@
#define VDCTRL0_VSYNC_PULSE_WIDTH_UNIT BIT(20)
#define VDCTRL0_HALF_LINE BIT(19)
#define VDCTRL0_HALF_LINE_MODE BIT(18)
-#define VDCTRL0_SET_VSYNC_PULSE_WIDTH(x) ((x) & 0x3ffff)
-#define VDCTRL0_GET_VSYNC_PULSE_WIDTH(x) ((x) & 0x3ffff)
+#define VDCTRL0_SET_VSYNC_PULSE_WIDTH(x) REG_PUT((x), 17, 0)
+#define VDCTRL0_GET_VSYNC_PULSE_WIDTH(x) REG_GET((x), 17, 0)
-#define VDCTRL2_SET_HSYNC_PERIOD(x) ((x) & 0x3ffff)
-#define VDCTRL2_GET_HSYNC_PERIOD(x) ((x) & 0x3ffff)
+#define VDCTRL2_SET_HSYNC_PERIOD(x) REG_PUT((x), 15, 0)
+#define VDCTRL2_GET_HSYNC_PERIOD(x) REG_GET((x), 15, 0)
#define VDCTRL3_MUX_SYNC_SIGNALS BIT(29)
#define VDCTRL3_VSYNC_ONLY BIT(28)
-#define SET_HOR_WAIT_CNT(x) (((x) & 0xfff) << 16)
-#define GET_HOR_WAIT_CNT(x) (((x) >> 16) & 0xfff)
-#define SET_VERT_WAIT_CNT(x) ((x) & 0xffff)
-#define GET_VERT_WAIT_CNT(x) ((x) & 0xffff)
+#define SET_HOR_WAIT_CNT(x) REG_PUT((x), 27, 16)
+#define GET_HOR_WAIT_CNT(x) REG_GET((x), 27, 16)
+#define SET_VERT_WAIT_CNT(x) REG_PUT((x), 15, 0)
+#define GET_VERT_WAIT_CNT(x) REG_GET((x), 15, 0)
-#define VDCTRL4_SET_DOTCLK_DLY(x) (((x) & 0x7) << 29) /* v4 only */
-#define VDCTRL4_GET_DOTCLK_DLY(x) (((x) >> 29) & 0x7) /* v4 only */
+#define VDCTRL4_SET_DOTCLK_DLY(x) REG_PUT((x), 31, 29) /* v4 only */
+#define VDCTRL4_GET_DOTCLK_DLY(x) REG_GET((x), 31, 29) /* v4 only */
#define VDCTRL4_SYNC_SIGNALS_ON BIT(18)
-#define SET_DOTCLK_H_VALID_DATA_CNT(x) ((x) & 0x3ffff)
+#define SET_DOTCLK_H_VALID_DATA_CNT(x) REG_PUT((x), 17, 0)
#define DEBUG0_HSYNC BIT(26)
#define DEBUG0_VSYNC BIT(25)
@@ -121,6 +170,22 @@
#define AS_CTRL_ALPHA_CTRL_EMBEDDED (0 << 1)
#define AS_CTRL_AS_ENABLE BIT(0)
+/* pigeon registers for crop */
+#define HW_EPDC_PIGEON_12_0 0xb00
+#define HW_EPDC_PIGEON_12_1 0xb10
+#define HW_EPDC_PIGEON_12_2 0xb20
+
+#define PIGEON_12_0_SET_STATE_MASK(x) REG_PUT((x), 31, 24)
+#define PIGEON_12_0_SET_MASK_CNT(x) REG_PUT((x), 23, 12)
+#define PIGEON_12_0_SET_MASK_CNT_SEL(x) REG_PUT((x), 11, 8)
+#define PIGEON_12_0_SET_OFFSET(x) REG_PUT((x), 7, 4)
+#define PIGEON_12_0_SET_INC_SEL(x) REG_PUT((x), 3, 2)
+#define PIGEON_12_0_POL_ACTIVE_LOW BIT(1)
+#define PIGEON_12_0_EN BIT(0)
+
+#define PIGEON_12_1_SET_CLR_CNT(x) REG_PUT((x), 31, 16)
+#define PIGEON_12_1_SET_SET_CNT(x) REG_PUT((x), 15, 0)
+
#define MXSFB_MIN_XRES 120
#define MXSFB_MIN_YRES 120
#define MXSFB_MAX_XRES 0xffff
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
index 479ffdb64486..bb2eb6808a48 100644
--- a/drivers/gpu/drm/panel/Kconfig
+++ b/drivers/gpu/drm/panel/Kconfig
@@ -305,6 +305,15 @@ config DRM_PANEL_OLIMEX_LCD_OLINUXINO
Say Y here if you want to enable support for Olimex Ltd.
LCD-OLinuXino panel.
+config DRM_PANEL_ONTAT_KD50G21_40NT_A1
+ tristate "ON Tat Industrial Company KD50G21-40NT-A1 panel"
+ depends on OF
+ depends on BACKLIGHT_CLASS_DEVICE
+ select VIDEOMODE_HELPERS
+ help
+ Say Y here if you want to enable support for the ON Tat Industrial
+ Company KD50G21-40NT-A1 for 800x480 LCD panel
+
config DRM_PANEL_ORISETECH_OTM8009A
tristate "Orise Technology otm8009a 480x800 dsi 2dl panel"
depends on OF
@@ -359,6 +368,17 @@ config DRM_PANEL_RAYDIUM_RM68200
Say Y here if you want to enable support for Raydium RM68200
720x1280 DSI video mode panel.
+config DRM_PANEL_ROCKTECK_HIMAX8394F
+ tristate "Rocktech Himax8394f 720x1280 DSI video mode panel"
+ depends on OF
+ depends on DRM_MIPI_DSI
+ depends on BACKLIGHT_CLASS_DEVICE
+ help
+ Say Y here if you want to enable support for Rocktech Himax8394f
+ TFT-LCD modules. The panel has a 720x1280 resolution and uses
+ 24 bit RGB per pixel. It provides a MIPI DSI interface to the host
+ and has a built-in LED backlight.
+
config DRM_PANEL_RONBO_RB070D30
tristate "Ronbo Electronics RB070D30 panel"
depends on OF
@@ -621,4 +641,13 @@ config DRM_PANEL_XINPENG_XPP055C272
Say Y here if you want to enable support for the Xinpeng
XPP055C272 controller for 720x1280 LCD panels with MIPI/RGB/SPI
system interfaces.
+config DRM_PANEL_WKS_101WX001
+ tristate "WKS 101WX001 parallel LCD"
+ depends on OF
+ depends on BACKLIGHT_CLASS_DEVICE
+ select VIDEOMODE_HELPERS
+ help
+ Say Y here if you want to enable support for the WKS
+ 101WX001 controller for 1280x800 LCD panel
+
endmenu
diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
index c8132050bcec..d15a67e92319 100644
--- a/drivers/gpu/drm/panel/Makefile
+++ b/drivers/gpu/drm/panel/Makefile
@@ -28,12 +28,14 @@ obj-$(CONFIG_DRM_PANEL_NOVATEK_NT36672A) += panel-novatek-nt36672a.o
obj-$(CONFIG_DRM_PANEL_NOVATEK_NT39016) += panel-novatek-nt39016.o
obj-$(CONFIG_DRM_PANEL_MANTIX_MLAF057WE51) += panel-mantix-mlaf057we51.o
obj-$(CONFIG_DRM_PANEL_OLIMEX_LCD_OLINUXINO) += panel-olimex-lcd-olinuxino.o
+obj-$(CONFIG_DRM_PANEL_ONTAT_KD50G21_40NT_A1) += panel-ontat-kd50g21-40nt-a1.o
obj-$(CONFIG_DRM_PANEL_ORISETECH_OTM8009A) += panel-orisetech-otm8009a.o
obj-$(CONFIG_DRM_PANEL_OSD_OSD101T2587_53TS) += panel-osd-osd101t2587-53ts.o
obj-$(CONFIG_DRM_PANEL_PANASONIC_VVX10F034N00) += panel-panasonic-vvx10f034n00.o
obj-$(CONFIG_DRM_PANEL_RASPBERRYPI_TOUCHSCREEN) += panel-raspberrypi-touchscreen.o
obj-$(CONFIG_DRM_PANEL_RAYDIUM_RM67191) += panel-raydium-rm67191.o
obj-$(CONFIG_DRM_PANEL_RAYDIUM_RM68200) += panel-raydium-rm68200.o
+obj-$(CONFIG_DRM_PANEL_ROCKTECK_HIMAX8394F) += panel-rocktech-hx8394f.o
obj-$(CONFIG_DRM_PANEL_RONBO_RB070D30) += panel-ronbo-rb070d30.o
obj-$(CONFIG_DRM_PANEL_SAMSUNG_ATNA33XC20) += panel-samsung-atna33xc20.o
obj-$(CONFIG_DRM_PANEL_SAMSUNG_DB7430) += panel-samsung-db7430.o
@@ -64,3 +66,4 @@ obj-$(CONFIG_DRM_PANEL_TRULY_NT35597_WQXGA) += panel-truly-nt35597.o
obj-$(CONFIG_DRM_PANEL_VISIONOX_RM69299) += panel-visionox-rm69299.o
obj-$(CONFIG_DRM_PANEL_WIDECHIPS_WS2401) += panel-widechips-ws2401.o
obj-$(CONFIG_DRM_PANEL_XINPENG_XPP055C272) += panel-xinpeng-xpp055c272.o
+obj-$(CONFIG_DRM_PANEL_WKS_101WX001) += panel-wks-101wx001.o
diff --git a/drivers/gpu/drm/panel/panel-ontat-kd50g21-40nt-a1.c b/drivers/gpu/drm/panel/panel-ontat-kd50g21-40nt-a1.c
new file mode 100644
index 000000000000..3af4ca5dd4a2
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-ontat-kd50g21-40nt-a1.c
@@ -0,0 +1,370 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2022 NXP
+ *
+ * Based on drivers/gpu/drm/panel/panel-seiko-43wvf1g.c
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+
+#include <video/display_timing.h>
+#include <video/videomode.h>
+
+#include <drm/drm_crtc.h>
+#include <drm/drm_device.h>
+#include <drm/drm_panel.h>
+
+struct ontat_kd50g21_40nt_a1_panel_desc {
+ const struct drm_display_mode *modes;
+ unsigned int num_modes;
+ const struct display_timing *timings;
+ unsigned int num_timings;
+
+ unsigned int bpc;
+
+ /**
+ * @width: width (in millimeters) of the panel's active display area
+ * @height: height (in millimeters) of the panel's active display area
+ */
+ struct {
+ unsigned int width;
+ unsigned int height;
+ } size;
+
+ u32 bus_format;
+ u32 bus_flags;
+};
+
+struct ontat_kd50g21_40nt_a1_panel {
+ struct drm_panel base;
+ bool enabled;
+ bool prepared;
+ ktime_t prepared_time;
+ ktime_t unprepared_time;
+ const struct ontat_kd50g21_40nt_a1_panel_desc *desc;
+ struct regulator *supply;
+ struct gpio_desc *enable_gpio;
+};
+
+static inline struct ontat_kd50g21_40nt_a1_panel *
+to_ontat_kd50g21_40nt_a1_panel(struct drm_panel *panel)
+{
+ return container_of(panel, struct ontat_kd50g21_40nt_a1_panel, base);
+}
+
+static int
+ontat_kd50g21_40nt_a1_panel_get_fixed_modes(struct ontat_kd50g21_40nt_a1_panel *panel,
+ struct drm_connector *connector)
+{
+ struct drm_display_mode *mode;
+ unsigned int i, num = 0;
+
+ if (!panel->desc)
+ return 0;
+
+ for (i = 0; i < panel->desc->num_timings; i++) {
+ const struct display_timing *dt = &panel->desc->timings[i];
+ struct videomode vm;
+
+ videomode_from_timing(dt, &vm);
+ mode = drm_mode_create(connector->dev);
+ if (!mode) {
+ dev_err(panel->base.dev, "failed to add mode %ux%u\n",
+ dt->hactive.typ, dt->vactive.typ);
+ continue;
+ }
+
+ drm_display_mode_from_videomode(&vm, mode);
+
+ mode->type |= DRM_MODE_TYPE_DRIVER;
+
+ if (panel->desc->num_timings == 1)
+ mode->type |= DRM_MODE_TYPE_PREFERRED;
+
+ drm_mode_probed_add(connector, mode);
+ num++;
+ }
+
+ for (i = 0; i < panel->desc->num_modes; i++) {
+ const struct drm_display_mode *m = &panel->desc->modes[i];
+
+ mode = drm_mode_duplicate(connector->dev, m);
+ if (!mode) {
+ dev_err(panel->base.dev, "failed to add mode %ux%u@%u\n",
+ m->hdisplay, m->vdisplay,
+ drm_mode_vrefresh(m));
+ continue;
+ }
+
+ mode->type |= DRM_MODE_TYPE_DRIVER;
+
+ if (panel->desc->num_modes == 1)
+ mode->type |= DRM_MODE_TYPE_PREFERRED;
+
+ drm_mode_set_name(mode);
+
+ drm_mode_probed_add(connector, mode);
+ num++;
+ }
+
+ connector->display_info.bpc = panel->desc->bpc;
+ connector->display_info.width_mm = panel->desc->size.width;
+ connector->display_info.height_mm = panel->desc->size.height;
+ if (panel->desc->bus_format)
+ drm_display_info_set_bus_formats(&connector->display_info,
+ &panel->desc->bus_format, 1);
+ connector->display_info.bus_flags = panel->desc->bus_flags;
+
+ return num;
+}
+
+static void ontat_kd50g21_40nt_a1_panel_wait(ktime_t start_ktime,
+ unsigned int min_ms)
+{
+ ktime_t now_ktime, min_ktime;
+
+ if (!min_ms)
+ return;
+
+ min_ktime = ktime_add(start_ktime, ms_to_ktime(min_ms));
+ now_ktime = ktime_get();
+
+ if (ktime_before(now_ktime, min_ktime))
+ msleep(ktime_to_ms(ktime_sub(min_ktime, now_ktime)) + 1);
+}
+
+static int ontat_kd50g21_40nt_a1_panel_disable(struct drm_panel *panel)
+{
+ struct ontat_kd50g21_40nt_a1_panel *p = to_ontat_kd50g21_40nt_a1_panel(panel);
+
+ if (!p->enabled)
+ return 0;
+
+ msleep(114);
+
+ p->enabled = false;
+
+ return 0;
+}
+
+static int ontat_kd50g21_40nt_a1_panel_unprepare(struct drm_panel *panel)
+{
+ struct ontat_kd50g21_40nt_a1_panel *p = to_ontat_kd50g21_40nt_a1_panel(panel);
+
+ if (!p->prepared)
+ return 0;
+
+ gpiod_set_value_cansleep(p->enable_gpio, 0);
+ regulator_disable(p->supply);
+ p->unprepared_time = ktime_get();
+
+ p->prepared = false;
+
+ return 0;
+}
+
+static int ontat_kd50g21_40nt_a1_panel_prepare(struct drm_panel *panel)
+{
+ struct ontat_kd50g21_40nt_a1_panel *p = to_ontat_kd50g21_40nt_a1_panel(panel);
+ int err;
+
+ if (p->prepared)
+ return 0;
+
+ ontat_kd50g21_40nt_a1_panel_wait(p->unprepared_time, 17);
+
+ err = regulator_enable(p->supply);
+ if (err < 0) {
+ dev_err(panel->dev, "failed to enable power supply: %d\n", err);
+ return err;
+ }
+
+ gpiod_set_value_cansleep(p->enable_gpio, 1);
+
+ msleep(36);
+
+ p->prepared_time = ktime_get();
+
+ p->prepared = true;
+
+ return 0;
+}
+
+static int ontat_kd50g21_40nt_a1_panel_enable(struct drm_panel *panel)
+{
+ struct ontat_kd50g21_40nt_a1_panel *p = to_ontat_kd50g21_40nt_a1_panel(panel);
+
+ if (p->enabled)
+ return 0;
+
+ msleep(163);
+
+ ontat_kd50g21_40nt_a1_panel_wait(p->prepared_time, 0);
+
+ p->enabled = true;
+
+ return 0;
+}
+
+static int
+ontat_kd50g21_40nt_a1_panel_get_modes(struct drm_panel *panel,
+ struct drm_connector *connector)
+{
+ struct ontat_kd50g21_40nt_a1_panel *p = to_ontat_kd50g21_40nt_a1_panel(panel);
+
+ /* add hard-coded panel modes */
+ return ontat_kd50g21_40nt_a1_panel_get_fixed_modes(p, connector);
+}
+
+static int
+ontat_kd50g21_40nt_a1_panel_get_timings(struct drm_panel *panel,
+ unsigned int num_timings,
+ struct display_timing *timings)
+{
+ struct ontat_kd50g21_40nt_a1_panel *p = to_ontat_kd50g21_40nt_a1_panel(panel);
+ unsigned int i;
+
+ if (p->desc->num_timings < num_timings)
+ num_timings = p->desc->num_timings;
+
+ if (timings)
+ for (i = 0; i < num_timings; i++)
+ timings[i] = p->desc->timings[i];
+
+ return p->desc->num_timings;
+}
+
+static const struct drm_panel_funcs ontat_kd50g21_40nt_a1_panel_funcs = {
+ .disable = ontat_kd50g21_40nt_a1_panel_disable,
+ .unprepare = ontat_kd50g21_40nt_a1_panel_unprepare,
+ .prepare = ontat_kd50g21_40nt_a1_panel_prepare,
+ .enable = ontat_kd50g21_40nt_a1_panel_enable,
+ .get_modes = ontat_kd50g21_40nt_a1_panel_get_modes,
+ .get_timings = ontat_kd50g21_40nt_a1_panel_get_timings,
+};
+
+static int
+ontat_kd50g21_40nt_a1_panel_probe(struct device *dev,
+ const struct ontat_kd50g21_40nt_a1_panel_desc *desc)
+{
+ struct ontat_kd50g21_40nt_a1_panel *panel;
+ int err;
+
+ panel = devm_kzalloc(dev, sizeof(*panel), GFP_KERNEL);
+ if (!panel)
+ return -ENOMEM;
+
+ panel->enabled = false;
+ panel->prepared = false;
+ panel->desc = desc;
+
+ panel->supply = devm_regulator_get(dev, "power");
+ if (IS_ERR(panel->supply))
+ return PTR_ERR(panel->supply);
+
+ panel->enable_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_LOW);
+ if (IS_ERR(panel->enable_gpio)) {
+ err = PTR_ERR(panel->enable_gpio);
+ if (err != -EPROBE_DEFER)
+ dev_err(dev, "failed to request GPIO: %d\n", err);
+ return err;
+ }
+
+ dev_set_drvdata(dev, panel);
+
+ drm_panel_init(&panel->base, dev, &ontat_kd50g21_40nt_a1_panel_funcs,
+ DRM_MODE_CONNECTOR_DPI);
+
+ err = drm_panel_of_backlight(&panel->base);
+ if (err)
+ return err;
+
+ drm_panel_add(&panel->base);
+
+ return 0;
+}
+
+static int ontat_kd50g21_40nt_a1_panel_remove(struct platform_device *pdev)
+{
+ struct ontat_kd50g21_40nt_a1_panel *panel = platform_get_drvdata(pdev);
+
+ drm_panel_remove(&panel->base);
+ drm_panel_disable(&panel->base);
+ drm_panel_unprepare(&panel->base);
+
+ return 0;
+}
+
+static void ontat_kd50g21_40nt_a1_panel_shutdown(struct platform_device *pdev)
+{
+ struct ontat_kd50g21_40nt_a1_panel *panel = platform_get_drvdata(pdev);
+
+ drm_panel_disable(&panel->base);
+ drm_panel_unprepare(&panel->base);
+}
+
+static const struct display_timing ontat_kd50g21_40nt_a1_timing = {
+ .pixelclock = { 30000000, 30000000, 30000000 },
+ .hactive = { 800, 800, 800 },
+ .hfront_porch = { 40, 40, 40 },
+ .hback_porch = { 40, 40, 40 },
+ .hsync_len = { 48, 48, 48 },
+ .vactive = { 480, 480, 480 },
+ .vfront_porch = { 13, 13, 13 },
+ .vback_porch = { 29, 29, 29 },
+ .vsync_len = { 3, 3, 3 },
+ .flags = DISPLAY_FLAGS_VSYNC_LOW | DISPLAY_FLAGS_HSYNC_LOW,
+};
+
+static const struct ontat_kd50g21_40nt_a1_panel_desc ontat_kd50g21_40nt_a1 = {
+ .timings = &ontat_kd50g21_40nt_a1_timing,
+ .num_timings = 1,
+ .bpc = 8,
+ .size = {
+ .width = 108,
+ .height = 65,
+ },
+ .bus_format = MEDIA_BUS_FMT_RGB888_1X24,
+ .bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE,
+};
+
+static const struct of_device_id platform_of_match[] = {
+ {
+ .compatible = "ontat,kd50g21-40nt-a1",
+ .data = &ontat_kd50g21_40nt_a1,
+ }, {
+ /* sentinel */
+ }
+};
+MODULE_DEVICE_TABLE(of, platform_of_match);
+
+static int ontat_kd50g21_40nt_a1_panel_platform_probe(struct platform_device *pdev)
+{
+ const struct of_device_id *id;
+
+ id = of_match_node(platform_of_match, pdev->dev.of_node);
+ if (!id)
+ return -ENODEV;
+
+ return ontat_kd50g21_40nt_a1_panel_probe(&pdev->dev, id->data);
+}
+
+static struct platform_driver ontat_kd50g21_40nt_a1_panel_platform_driver = {
+ .driver = {
+ .name = "ontat_kd50g21_40nt_a1_panel",
+ .of_match_table = platform_of_match,
+ },
+ .probe = ontat_kd50g21_40nt_a1_panel_platform_probe,
+ .remove = ontat_kd50g21_40nt_a1_panel_remove,
+ .shutdown = ontat_kd50g21_40nt_a1_panel_shutdown,
+};
+module_platform_driver(ontat_kd50g21_40nt_a1_panel_platform_driver);
+
+MODULE_AUTHOR("NXP Semiconductor");
+MODULE_DESCRIPTION("ON Tat Industrial Company KD50G21-40NT-A1 panel driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/panel/panel-raydium-rm67191.c b/drivers/gpu/drm/panel/panel-raydium-rm67191.c
index 572547d1aa83..871b5f1ad12b 100644
--- a/drivers/gpu/drm/panel/panel-raydium-rm67191.c
+++ b/drivers/gpu/drm/panel/panel-raydium-rm67191.c
@@ -10,6 +10,7 @@
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/of.h>
+#include <linux/of_platform.h>
#include <linux/regulator/consumer.h>
#include <video/mipi_display.h>
@@ -38,150 +39,78 @@ struct cmd_set_entry {
* There is no description in the Reference Manual about these commands.
* We received them from vendor, so just use them as is.
*/
-static const struct cmd_set_entry manufacturer_cmd_set[] = {
- {0xFE, 0x0B},
- {0x28, 0x40},
- {0x29, 0x4F},
- {0xFE, 0x0E},
- {0x4B, 0x00},
- {0x4C, 0x0F},
- {0x4D, 0x20},
- {0x4E, 0x40},
- {0x4F, 0x60},
- {0x50, 0xA0},
- {0x51, 0xC0},
- {0x52, 0xE0},
- {0x53, 0xFF},
- {0xFE, 0x0D},
- {0x18, 0x08},
- {0x42, 0x00},
- {0x08, 0x41},
- {0x46, 0x02},
- {0x72, 0x09},
- {0xFE, 0x0A},
- {0x24, 0x17},
- {0x04, 0x07},
- {0x1A, 0x0C},
- {0x0F, 0x44},
- {0xFE, 0x04},
- {0x00, 0x0C},
- {0x05, 0x08},
- {0x06, 0x08},
- {0x08, 0x08},
- {0x09, 0x08},
- {0x0A, 0xE6},
- {0x0B, 0x8C},
- {0x1A, 0x12},
- {0x1E, 0xE0},
- {0x29, 0x93},
- {0x2A, 0x93},
- {0x2F, 0x02},
- {0x31, 0x02},
- {0x33, 0x05},
- {0x37, 0x2D},
- {0x38, 0x2D},
- {0x3A, 0x1E},
- {0x3B, 0x1E},
- {0x3D, 0x27},
- {0x3F, 0x80},
- {0x40, 0x40},
- {0x41, 0xE0},
- {0x4F, 0x2F},
- {0x50, 0x1E},
- {0xFE, 0x06},
- {0x00, 0xCC},
- {0x05, 0x05},
- {0x07, 0xA2},
- {0x08, 0xCC},
- {0x0D, 0x03},
- {0x0F, 0xA2},
- {0x32, 0xCC},
- {0x37, 0x05},
- {0x39, 0x83},
- {0x3A, 0xCC},
- {0x41, 0x04},
- {0x43, 0x83},
- {0x44, 0xCC},
- {0x49, 0x05},
- {0x4B, 0xA2},
- {0x4C, 0xCC},
- {0x51, 0x03},
- {0x53, 0xA2},
- {0x75, 0xCC},
- {0x7A, 0x03},
- {0x7C, 0x83},
- {0x7D, 0xCC},
- {0x82, 0x02},
- {0x84, 0x83},
- {0x85, 0xEC},
- {0x86, 0x0F},
- {0x87, 0xFF},
- {0x88, 0x00},
- {0x8A, 0x02},
- {0x8C, 0xA2},
- {0x8D, 0xEA},
- {0x8E, 0x01},
- {0x8F, 0xE8},
- {0xFE, 0x06},
- {0x90, 0x0A},
- {0x92, 0x06},
- {0x93, 0xA0},
- {0x94, 0xA8},
- {0x95, 0xEC},
- {0x96, 0x0F},
- {0x97, 0xFF},
- {0x98, 0x00},
- {0x9A, 0x02},
- {0x9C, 0xA2},
- {0xAC, 0x04},
- {0xFE, 0x06},
- {0xB1, 0x12},
- {0xB2, 0x17},
- {0xB3, 0x17},
- {0xB4, 0x17},
- {0xB5, 0x17},
- {0xB6, 0x11},
- {0xB7, 0x08},
- {0xB8, 0x09},
- {0xB9, 0x06},
- {0xBA, 0x07},
- {0xBB, 0x17},
- {0xBC, 0x17},
- {0xBD, 0x17},
- {0xBE, 0x17},
- {0xBF, 0x17},
- {0xC0, 0x17},
- {0xC1, 0x17},
- {0xC2, 0x17},
- {0xC3, 0x17},
- {0xC4, 0x0F},
- {0xC5, 0x0E},
- {0xC6, 0x00},
- {0xC7, 0x01},
- {0xC8, 0x10},
- {0xFE, 0x06},
- {0x95, 0xEC},
- {0x8D, 0xEE},
- {0x44, 0xEC},
- {0x4C, 0xEC},
- {0x32, 0xEC},
- {0x3A, 0xEC},
- {0x7D, 0xEC},
- {0x75, 0xEC},
- {0x00, 0xEC},
- {0x08, 0xEC},
- {0x85, 0xEC},
- {0xA6, 0x21},
- {0xA7, 0x05},
- {0xA9, 0x06},
- {0x82, 0x06},
- {0x41, 0x06},
- {0x7A, 0x07},
- {0x37, 0x07},
- {0x05, 0x06},
- {0x49, 0x06},
- {0x0D, 0x04},
- {0x51, 0x04},
+static const struct cmd_set_entry mcs_rm67191[] = {
+ {0xFE, 0x0B}, {0x28, 0x40}, {0x29, 0x4F}, {0xFE, 0x0E},
+ {0x4B, 0x00}, {0x4C, 0x0F}, {0x4D, 0x20}, {0x4E, 0x40},
+ {0x4F, 0x60}, {0x50, 0xA0}, {0x51, 0xC0}, {0x52, 0xE0},
+ {0x53, 0xFF}, {0xFE, 0x0D}, {0x18, 0x08}, {0x42, 0x00},
+ {0x08, 0x41}, {0x46, 0x02}, {0x72, 0x09}, {0xFE, 0x0A},
+ {0x24, 0x17}, {0x04, 0x07}, {0x1A, 0x0C}, {0x0F, 0x44},
+ {0xFE, 0x04}, {0x00, 0x0C}, {0x05, 0x08}, {0x06, 0x08},
+ {0x08, 0x08}, {0x09, 0x08}, {0x0A, 0xE6}, {0x0B, 0x8C},
+ {0x1A, 0x12}, {0x1E, 0xE0}, {0x29, 0x93}, {0x2A, 0x93},
+ {0x2F, 0x02}, {0x31, 0x02}, {0x33, 0x05}, {0x37, 0x2D},
+ {0x38, 0x2D}, {0x3A, 0x1E}, {0x3B, 0x1E}, {0x3D, 0x27},
+ {0x3F, 0x80}, {0x40, 0x40}, {0x41, 0xE0}, {0x4F, 0x2F},
+ {0x50, 0x1E}, {0xFE, 0x06}, {0x00, 0xCC}, {0x05, 0x05},
+ {0x07, 0xA2}, {0x08, 0xCC}, {0x0D, 0x03}, {0x0F, 0xA2},
+ {0x32, 0xCC}, {0x37, 0x05}, {0x39, 0x83}, {0x3A, 0xCC},
+ {0x41, 0x04}, {0x43, 0x83}, {0x44, 0xCC}, {0x49, 0x05},
+ {0x4B, 0xA2}, {0x4C, 0xCC}, {0x51, 0x03}, {0x53, 0xA2},
+ {0x75, 0xCC}, {0x7A, 0x03}, {0x7C, 0x83}, {0x7D, 0xCC},
+ {0x82, 0x02}, {0x84, 0x83}, {0x85, 0xEC}, {0x86, 0x0F},
+ {0x87, 0xFF}, {0x88, 0x00}, {0x8A, 0x02}, {0x8C, 0xA2},
+ {0x8D, 0xEA}, {0x8E, 0x01}, {0x8F, 0xE8}, {0xFE, 0x06},
+ {0x90, 0x0A}, {0x92, 0x06}, {0x93, 0xA0}, {0x94, 0xA8},
+ {0x95, 0xEC}, {0x96, 0x0F}, {0x97, 0xFF}, {0x98, 0x00},
+ {0x9A, 0x02}, {0x9C, 0xA2}, {0xAC, 0x04}, {0xFE, 0x06},
+ {0xB1, 0x12}, {0xB2, 0x17}, {0xB3, 0x17}, {0xB4, 0x17},
+ {0xB5, 0x17}, {0xB6, 0x11}, {0xB7, 0x08}, {0xB8, 0x09},
+ {0xB9, 0x06}, {0xBA, 0x07}, {0xBB, 0x17}, {0xBC, 0x17},
+ {0xBD, 0x17}, {0xBE, 0x17}, {0xBF, 0x17}, {0xC0, 0x17},
+ {0xC1, 0x17}, {0xC2, 0x17}, {0xC3, 0x17}, {0xC4, 0x0F},
+ {0xC5, 0x0E}, {0xC6, 0x00}, {0xC7, 0x01}, {0xC8, 0x10},
+ {0xFE, 0x06}, {0x95, 0xEC}, {0x8D, 0xEE}, {0x44, 0xEC},
+ {0x4C, 0xEC}, {0x32, 0xEC}, {0x3A, 0xEC}, {0x7D, 0xEC},
+ {0x75, 0xEC}, {0x00, 0xEC}, {0x08, 0xEC}, {0x85, 0xEC},
+ {0xA6, 0x21}, {0xA7, 0x05}, {0xA9, 0x06}, {0x82, 0x06},
+ {0x41, 0x06}, {0x7A, 0x07}, {0x37, 0x07}, {0x05, 0x06},
+ {0x49, 0x06}, {0x0D, 0x04}, {0x51, 0x04},
+};
+
+static const struct cmd_set_entry mcs_rm67199[] = {
+ {0xFE, 0xA0}, {0x2B, 0x18}, {0xFE, 0x70}, {0x7D, 0x05},
+ {0x5D, 0x0A}, {0x5A, 0x79}, {0x5C, 0x00}, {0x52, 0x00},
+ {0xFE, 0xD0}, {0x40, 0x02}, {0x13, 0x40}, {0xFE, 0x40},
+ {0x05, 0x08}, {0x06, 0x08}, {0x08, 0x08}, {0x09, 0x08},
+ {0x0A, 0xCA}, {0x0B, 0x88}, {0x20, 0x93}, {0x21, 0x93},
+ {0x24, 0x02}, {0x26, 0x02}, {0x28, 0x05}, {0x2A, 0x05},
+ {0x74, 0x2F}, {0x75, 0x1E}, {0xAD, 0x00}, {0xFE, 0x60},
+ {0x00, 0xCC}, {0x01, 0x00}, {0x02, 0x04}, {0x03, 0x00},
+ {0x04, 0x00}, {0x05, 0x07}, {0x06, 0x00}, {0x07, 0x88},
+ {0x08, 0x00}, {0x09, 0xCC}, {0x0A, 0x00}, {0x0B, 0x04},
+ {0x0C, 0x00}, {0x0D, 0x00}, {0x0E, 0x05}, {0x0F, 0x00},
+ {0x10, 0x88}, {0x11, 0x00}, {0x12, 0xCC}, {0x13, 0x0F},
+ {0x14, 0xFF}, {0x15, 0x04}, {0x16, 0x00}, {0x17, 0x06},
+ {0x18, 0x00}, {0x19, 0x96}, {0x1A, 0x00}, {0x24, 0xCC},
+ {0x25, 0x00}, {0x26, 0x02}, {0x27, 0x00}, {0x28, 0x00},
+ {0x29, 0x06}, {0x2A, 0x06}, {0x2B, 0x82}, {0x2D, 0x00},
+ {0x2F, 0xCC}, {0x30, 0x00}, {0x31, 0x02}, {0x32, 0x00},
+ {0x33, 0x00}, {0x34, 0x07}, {0x35, 0x06}, {0x36, 0x82},
+ {0x37, 0x00}, {0x38, 0xCC}, {0x39, 0x00}, {0x3A, 0x02},
+ {0x3B, 0x00}, {0x3D, 0x00}, {0x3F, 0x07}, {0x40, 0x00},
+ {0x41, 0x88}, {0x42, 0x00}, {0x43, 0xCC}, {0x44, 0x00},
+ {0x45, 0x02}, {0x46, 0x00}, {0x47, 0x00}, {0x48, 0x06},
+ {0x49, 0x02}, {0x4A, 0x8A}, {0x4B, 0x00}, {0x5F, 0xCA},
+ {0x60, 0x01}, {0x61, 0xE8}, {0x62, 0x09}, {0x63, 0x00},
+ {0x64, 0x07}, {0x65, 0x00}, {0x66, 0x30}, {0x67, 0x80},
+ {0x9B, 0x03}, {0xA9, 0x07}, {0xAA, 0x06}, {0xAB, 0x02},
+ {0xAC, 0x10}, {0xAD, 0x11}, {0xAE, 0x05}, {0xAF, 0x04},
+ {0xB0, 0x10}, {0xB1, 0x10}, {0xB2, 0x10}, {0xB3, 0x10},
+ {0xB4, 0x10}, {0xB5, 0x10}, {0xB6, 0x10}, {0xB7, 0x10},
+ {0xB8, 0x10}, {0xB9, 0x10}, {0xBA, 0x04}, {0xBB, 0x05},
+ {0xBC, 0x00}, {0xBD, 0x01}, {0xBE, 0x0A}, {0xBF, 0x10},
+ {0xC0, 0x11}, {0xFE, 0xA0}, {0x22, 0x00},
};
static const u32 rad_bus_formats[] = {
@@ -205,10 +134,16 @@ struct rad_panel {
bool prepared;
bool enabled;
+
+ const struct rad_platform_data *pdata;
+};
+
+struct rad_platform_data {
+ int (*enable)(struct rad_panel *panel);
};
static const struct drm_display_mode default_mode = {
- .clock = 132000,
+ .clock = 121000,
.hdisplay = 1080,
.hsync_start = 1080 + 20,
.hsync_end = 1080 + 20 + 2,
@@ -228,14 +163,15 @@ static inline struct rad_panel *to_rad_panel(struct drm_panel *panel)
return container_of(panel, struct rad_panel, panel);
}
-static int rad_panel_push_cmd_list(struct mipi_dsi_device *dsi)
+static int rad_panel_push_cmd_list(struct mipi_dsi_device *dsi,
+ struct cmd_set_entry const *cmd_set,
+ size_t count)
{
size_t i;
- size_t count = ARRAY_SIZE(manufacturer_cmd_set);
int ret = 0;
for (i = 0; i < count; i++) {
- const struct cmd_set_entry *entry = &manufacturer_cmd_set[i];
+ const struct cmd_set_entry *entry = cmd_set++;
u8 buffer[2] = { entry->cmd, entry->param };
ret = mipi_dsi_generic_write(dsi, &buffer, sizeof(buffer));
@@ -273,11 +209,16 @@ static int rad_panel_prepare(struct drm_panel *panel)
if (ret)
return ret;
+ /* At lest 10ms needed between power-on and reset-out as RM specifies */
+ usleep_range(10000, 12000);
+
if (rad->reset) {
- gpiod_set_value_cansleep(rad->reset, 1);
- usleep_range(3000, 5000);
gpiod_set_value_cansleep(rad->reset, 0);
- usleep_range(18000, 20000);
+ /*
+ * 50ms delay after reset-out, as per manufacturer initalization
+ * sequence.
+ */
+ msleep(50);
}
rad->prepared = true;
@@ -313,20 +254,22 @@ static int rad_panel_unprepare(struct drm_panel *panel)
return 0;
}
-static int rad_panel_enable(struct drm_panel *panel)
+static int rm67191_enable(struct rad_panel *panel)
{
- struct rad_panel *rad = to_rad_panel(panel);
- struct mipi_dsi_device *dsi = rad->dsi;
+ struct mipi_dsi_device *dsi = panel->dsi;
struct device *dev = &dsi->dev;
+ u8 dsi_mode;
int color_format = color_format_from_dsi_format(dsi->format);
int ret;
- if (rad->enabled)
+ if (panel->enabled)
return 0;
dsi->mode_flags |= MIPI_DSI_MODE_LPM;
- ret = rad_panel_push_cmd_list(dsi);
+ ret = rad_panel_push_cmd_list(dsi,
+ &mcs_rm67191[0],
+ ARRAY_SIZE(mcs_rm67191));
if (ret < 0) {
dev_err(dev, "Failed to send MCS (%d)\n", ret);
goto fail;
@@ -347,7 +290,8 @@ static int rad_panel_enable(struct drm_panel *panel)
usleep_range(15000, 17000);
/* Set DSI mode */
- ret = mipi_dsi_generic_write(dsi, (u8[]){ 0xC2, 0x0B }, 2);
+ dsi_mode = (dsi->mode_flags & MIPI_DSI_MODE_VIDEO) ? 0x0B : 0x00;
+ ret = mipi_dsi_generic_write(dsi, (u8[]){ 0xC2, dsi_mode }, 2);
if (ret < 0) {
dev_err(dev, "Failed to set DSI mode (%d)\n", ret);
goto fail;
@@ -386,18 +330,116 @@ static int rad_panel_enable(struct drm_panel *panel)
goto fail;
}
- backlight_enable(rad->backlight);
+ backlight_enable(panel->backlight);
+
+ panel->enabled = true;
+
+ return 0;
+
+fail:
+ gpiod_set_value_cansleep(panel->reset, 1);
+
+ return ret;
+}
+
+static int rm67199_enable(struct rad_panel *panel)
+{
+ struct mipi_dsi_device *dsi = panel->dsi;
+ struct device *dev = &dsi->dev;
+ u8 dsi_mode;
+ int color_format = color_format_from_dsi_format(dsi->format);
+ int ret;
+
+ if (panel->enabled)
+ return 0;
+
+ dsi->mode_flags |= MIPI_DSI_MODE_LPM;
+
+ ret = rad_panel_push_cmd_list(dsi,
+ &mcs_rm67199[0],
+ ARRAY_SIZE(mcs_rm67199));
+ if (ret < 0) {
+ dev_err(dev, "Failed to send MCS (%d)\n", ret);
+ goto fail;
+ }
+
+ /* Select User Command Set table (CMD1) */
+ ret = mipi_dsi_generic_write(dsi, (u8[]){ WRMAUCCTR, 0x00 }, 2);
+ if (ret < 0)
+ goto fail;
+
+ /* Set DSI mode */
+ dsi_mode = (dsi->mode_flags & MIPI_DSI_MODE_VIDEO) ? 0x0B : 0x00;
+ ret = mipi_dsi_generic_write(dsi, (u8[]){ 0xC2, dsi_mode }, 2);
+ if (ret < 0) {
+ dev_err(dev, "Failed to set DSI mode (%d)\n", ret);
+ goto fail;
+ }
+ /* Set tear ON */
+ ret = mipi_dsi_dcs_set_tear_on(dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
+ if (ret < 0) {
+ dev_err(dev, "Failed to set tear ON (%d)\n", ret);
+ goto fail;
+ }
+ /* Set tear scanline */
+ ret = mipi_dsi_dcs_set_tear_scanline(dsi, 0x00);
+ if (ret < 0) {
+ dev_err(dev, "Failed to set tear scanline (%d)\n", ret);
+ goto fail;
+ }
+ /* Set pixel format */
+ ret = mipi_dsi_dcs_set_pixel_format(dsi, color_format);
+ dev_dbg(dev, "Interface color format set to 0x%x\n",
+ color_format);
+ if (ret < 0) {
+ dev_err(dev, "Failed to set pixel format (%d)\n", ret);
+ goto fail;
+ }
+ /* Exit sleep mode */
+ ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
+ if (ret < 0) {
+ dev_err(dev, "Failed to exit sleep mode (%d)\n", ret);
+ goto fail;
+ }
+
+ /*
+ * Although, 120ms seems a lot, this is the amount of delay that the
+ * manufacturer suggests it should be used between the sleep-out and
+ * display-on commands
+ */
+ msleep(120);
+
+ ret = mipi_dsi_dcs_set_display_on(dsi);
+ if (ret < 0) {
+ dev_err(dev, "Failed to set display ON (%d)\n", ret);
+ goto fail;
+ }
+
+ /*
+ * Also, 100ms delay between display-on and backlight enable as per
+ * manufacturer initialization sequence.
+ */
+ msleep(100);
+
+ backlight_enable(panel->backlight);
- rad->enabled = true;
+ panel->enabled = true;
return 0;
fail:
- gpiod_set_value_cansleep(rad->reset, 1);
+ gpiod_set_value_cansleep(panel->reset, 1);
return ret;
}
+static int rad_panel_enable(struct drm_panel *panel)
+{
+ struct rad_panel *rad = to_rad_panel(panel);
+
+ return rad->pdata->enable(rad);
+}
+
static int rad_panel_disable(struct drm_panel *panel)
{
struct rad_panel *rad = to_rad_panel(panel);
@@ -534,15 +576,34 @@ static int rad_init_regulators(struct rad_panel *rad)
return devm_regulator_bulk_get(dev, rad->num_supplies, rad->supplies);
};
+static const struct rad_platform_data rad_rm67191 = {
+ .enable = &rm67191_enable,
+};
+
+static const struct rad_platform_data rad_rm67199 = {
+ .enable = &rm67199_enable,
+};
+
+static const struct of_device_id rad_of_match[] = {
+ { .compatible = "raydium,rm67191", .data = &rad_rm67191 },
+ { .compatible = "raydium,rm67199", .data = &rad_rm67199 },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, rad_of_match);
+
static int rad_panel_probe(struct mipi_dsi_device *dsi)
{
struct device *dev = &dsi->dev;
+ const struct of_device_id *of_id = of_match_device(rad_of_match, dev);
struct device_node *np = dev->of_node;
struct rad_panel *panel;
struct backlight_properties bl_props;
int ret;
u32 video_mode;
+ if (!of_id || !of_id->data)
+ return -ENODEV;
+
panel = devm_kzalloc(&dsi->dev, sizeof(*panel), GFP_KERNEL);
if (!panel)
return -ENOMEM;
@@ -550,23 +611,32 @@ static int rad_panel_probe(struct mipi_dsi_device *dsi)
mipi_dsi_set_drvdata(dsi, panel);
panel->dsi = dsi;
+ panel->pdata = of_id->data;
dsi->format = MIPI_DSI_FMT_RGB888;
- dsi->mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO;
+ dsi->mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_NO_EOT_PACKET;
ret = of_property_read_u32(np, "video-mode", &video_mode);
if (!ret) {
switch (video_mode) {
case 0:
/* burst mode */
- dsi->mode_flags |= MIPI_DSI_MODE_VIDEO_BURST;
+ dsi->mode_flags |= MIPI_DSI_MODE_VIDEO_BURST |
+ MIPI_DSI_MODE_VIDEO;
break;
case 1:
/* non-burst mode with sync event */
+ dsi->mode_flags |= MIPI_DSI_MODE_VIDEO;
break;
case 2:
/* non-burst mode with sync pulse */
- dsi->mode_flags |= MIPI_DSI_MODE_VIDEO_SYNC_PULSE;
+ dsi->mode_flags |= MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
+ MIPI_DSI_MODE_VIDEO;
+ break;
+ case 3:
+ /* command mode */
+ dsi->mode_flags |= MIPI_DSI_CLOCK_NON_CONTINUOUS |
+ MIPI_DSI_MODE_VSYNC_FLUSH;
break;
default:
dev_warn(dev, "invalid video mode %d\n", video_mode);
@@ -580,9 +650,15 @@ static int rad_panel_probe(struct mipi_dsi_device *dsi)
return ret;
}
- panel->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
- if (IS_ERR(panel->reset))
- return PTR_ERR(panel->reset);
+ panel->reset = devm_gpiod_get_optional(dev, "reset",
+ GPIOD_OUT_LOW |
+ GPIOD_FLAGS_BIT_NONEXCLUSIVE);
+ if (IS_ERR(panel->reset)) {
+ ret = PTR_ERR(panel->reset);
+ dev_err(dev, "Failed to get reset gpio (%d)\n", ret);
+ return ret;
+ }
+ gpiod_set_value_cansleep(panel->reset, 1);
memset(&bl_props, 0, sizeof(bl_props));
bl_props.type = BACKLIGHT_RAW;
@@ -638,12 +714,6 @@ static void rad_panel_shutdown(struct mipi_dsi_device *dsi)
rad_panel_unprepare(&rad->panel);
}
-static const struct of_device_id rad_of_match[] = {
- { .compatible = "raydium,rm67191", },
- { /* sentinel */ }
-};
-MODULE_DEVICE_TABLE(of, rad_of_match);
-
static struct mipi_dsi_driver rad_panel_driver = {
.driver = {
.name = "panel-raydium-rm67191",
diff --git a/drivers/gpu/drm/panel/panel-raydium-rm68200.c b/drivers/gpu/drm/panel/panel-raydium-rm68200.c
index 412c0dbcb2b6..1288389600f3 100644
--- a/drivers/gpu/drm/panel/panel-raydium-rm68200.c
+++ b/drivers/gpu/drm/panel/panel-raydium-rm68200.c
@@ -75,6 +75,7 @@
struct rm68200 {
struct device *dev;
struct drm_panel panel;
+ struct gpio_desc *enable_gpio;
struct gpio_desc *reset_gpio;
struct regulator *supply;
bool prepared;
@@ -234,10 +235,22 @@ static void rm68200_init_sequence(struct rm68200 *ctx)
static int rm68200_disable(struct drm_panel *panel)
{
struct rm68200 *ctx = panel_to_rm68200(panel);
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+ int ret;
if (!ctx->enabled)
return 0;
+ ret = mipi_dsi_dcs_set_display_off(dsi);
+ if (ret)
+ dev_warn(panel->dev, "failed to set display off: %d\n", ret);
+
+ ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
+ if (ret)
+ dev_warn(panel->dev, "failed to enter sleep mode: %d\n", ret);
+
+ msleep(120);
+
ctx->enabled = false;
return 0;
@@ -246,27 +259,17 @@ static int rm68200_disable(struct drm_panel *panel)
static int rm68200_unprepare(struct drm_panel *panel)
{
struct rm68200 *ctx = panel_to_rm68200(panel);
- struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
- int ret;
if (!ctx->prepared)
return 0;
- ret = mipi_dsi_dcs_set_display_off(dsi);
- if (ret)
- dev_warn(panel->dev, "failed to set display off: %d\n", ret);
-
- ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
- if (ret)
- dev_warn(panel->dev, "failed to enter sleep mode: %d\n", ret);
-
- msleep(120);
-
if (ctx->reset_gpio) {
gpiod_set_value_cansleep(ctx->reset_gpio, 1);
msleep(20);
}
+ gpiod_set_value_cansleep(ctx->enable_gpio, 0);
+
regulator_disable(ctx->supply);
ctx->prepared = false;
@@ -277,7 +280,6 @@ static int rm68200_unprepare(struct drm_panel *panel)
static int rm68200_prepare(struct drm_panel *panel)
{
struct rm68200 *ctx = panel_to_rm68200(panel);
- struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
int ret;
if (ctx->prepared)
@@ -289,6 +291,8 @@ static int rm68200_prepare(struct drm_panel *panel)
return ret;
}
+ gpiod_set_value_cansleep(ctx->enable_gpio, 1);
+
if (ctx->reset_gpio) {
gpiod_set_value_cansleep(ctx->reset_gpio, 1);
msleep(20);
@@ -296,6 +300,20 @@ static int rm68200_prepare(struct drm_panel *panel)
msleep(100);
}
+ ctx->prepared = true;
+
+ return 0;
+}
+
+static int rm68200_enable(struct drm_panel *panel)
+{
+ struct rm68200 *ctx = panel_to_rm68200(panel);
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+ int ret;
+
+ if (ctx->enabled)
+ return 0;
+
rm68200_init_sequence(ctx);
ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
@@ -310,18 +328,6 @@ static int rm68200_prepare(struct drm_panel *panel)
msleep(20);
- ctx->prepared = true;
-
- return 0;
-}
-
-static int rm68200_enable(struct drm_panel *panel)
-{
- struct rm68200 *ctx = panel_to_rm68200(panel);
-
- if (ctx->enabled)
- return 0;
-
ctx->enabled = true;
return 0;
@@ -369,6 +375,13 @@ static int rm68200_probe(struct mipi_dsi_device *dsi)
if (!ctx)
return -ENOMEM;
+ ctx->enable_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_LOW);
+ if (IS_ERR(ctx->enable_gpio)) {
+ ret = PTR_ERR(ctx->enable_gpio);
+ dev_err(dev, "cannot get enable GPIO: %d\n", ret);
+ return ret;
+ }
+
ctx->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
if (IS_ERR(ctx->reset_gpio)) {
ret = PTR_ERR(ctx->reset_gpio);
@@ -390,8 +403,8 @@ static int rm68200_probe(struct mipi_dsi_device *dsi)
dsi->lanes = 2;
dsi->format = MIPI_DSI_FMT_RGB888;
- dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST |
- MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS;
+ dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
+ MIPI_DSI_MODE_LPM;
drm_panel_init(&ctx->panel, dev, &rm68200_drm_funcs,
DRM_MODE_CONNECTOR_DSI);
diff --git a/drivers/gpu/drm/panel/panel-rocktech-hx8394f.c b/drivers/gpu/drm/panel/panel-rocktech-hx8394f.c
new file mode 100644
index 000000000000..ed768cba88a4
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-rocktech-hx8394f.c
@@ -0,0 +1,404 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/*
+ * Copyright 2021,2022 NXP
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/regulator/consumer.h>
+
+#include <video/mipi_display.h>
+
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_panel.h>
+
+/* User Define command set */
+#define UD_SETADDRESSMODE 0x36 /* Set address mode */
+#define UD_SETSEQUENCE 0xB0 /* Set sequence */
+#define UD_SETPOWER 0xB1 /* Set power */
+#define UD_SETDISP 0xB2 /* Set display related register */
+#define UD_SETCYC 0xB4 /* Set display waveform cycles */
+#define UD_SETVCOM 0xB6 /* Set VCOM voltage */
+#define UD_SETTE 0xB7 /* Set internal TE function */
+#define UD_SETSENSOR 0xB8 /* Set temperature sensor */
+#define UD_SETEXTC 0xB9 /* Set extension command */
+#define UD_SETMIPI 0xBA /* Set MIPI control */
+#define UD_SETOTP 0xBB /* Set OTP */
+#define UD_SETREGBANK 0xBD /* Set register bank */
+#define UD_SETDGCLUT 0xC1 /* Set DGC LUT */
+#define UD_SETID 0xC3 /* Set ID */
+#define UD_SETDDB 0xC4 /* Set DDB */
+#define UD_SETCABC 0xC9 /* Set CABC control */
+#define UD_SETCABCGAIN 0xCA
+#define UD_SETPANEL 0xCC
+#define UD_SETOFFSET 0xD2
+#define UD_SETGIP0 0xD3 /* Set GIP Option0 */
+#define UD_SETGIP1 0xD5 /* Set GIP Option1 */
+#define UD_SETGIP2 0xD6 /* Set GIP Option2 */
+#define UD_SETGPO 0xD9
+#define UD_SETSCALING 0xDD
+#define UD_SETIDLE 0xDF
+#define UD_SETGAMMA 0xE0 /* Set gamma curve related setting */
+#define UD_SETCHEMODE_DYN 0xE4
+#define UD_SETCHE 0xE5
+#define UD_SETCESEL 0xE6 /* Enable color enhance */
+#define UD_SET_SP_CMD 0xE9
+#define UD_SETREADINDEX 0xFE /* Set SPI Read Index */
+#define UD_GETSPIREAD 0xFF /* SPI Read Command Data */
+
+struct hx8394f {
+ struct device *dev;
+ struct drm_panel panel;
+ struct gpio_desc *enable_gpio;
+ struct gpio_desc *reset_gpio;
+ struct regulator_bulk_data supplies[2];
+ bool prepared;
+ bool enabled;
+};
+
+static const struct drm_display_mode default_mode = {
+ .clock = 66000,
+ .hdisplay = 720,
+ .hsync_start = 720 + 52,
+ .hsync_end = 720 + 52 + 10,
+ .htotal = 720 + 52 + 10 + 52,
+ .vdisplay = 1280,
+ .vsync_start = 1280 + 16,
+ .vsync_end = 1280 + 16 + 7,
+ .vtotal = 1280 + 16 + 7 + 16,
+ .flags = 0,
+ .width_mm = 68,
+ .height_mm = 122,
+};
+
+static inline struct hx8394f *panel_to_hx8394f(struct drm_panel *panel)
+{
+ return container_of(panel, struct hx8394f, panel);
+}
+
+static void hx8394f_dcs_write_buf(struct hx8394f *ctx, const void *data,
+ size_t len)
+{
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+ int err;
+
+ err = mipi_dsi_dcs_write_buffer(dsi, data, len);
+ if (err < 0)
+ dev_err_ratelimited(ctx->dev, "MIPI DSI DCS write buffer failed: %d\n", err);
+}
+
+#define dcs_write_seq(ctx, seq...) \
+({ \
+ static const u8 d[] = { seq }; \
+ \
+ hx8394f_dcs_write_buf(ctx, d, ARRAY_SIZE(d)); \
+})
+
+static void hx8394f_init_sequence(struct hx8394f *ctx)
+{
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+ u8 mipi_data[] = {UD_SETMIPI, 0x60, 0x03, 0x68, 0x6B, 0xB2, 0xC0};
+
+ dcs_write_seq(ctx, UD_SETADDRESSMODE, 0x02);
+
+ dcs_write_seq(ctx, UD_SETEXTC, 0xFF, 0x83, 0x94);
+
+ /* SETMIPI */
+ mipi_data[1] = 0x60 | (dsi->lanes - 1);
+ hx8394f_dcs_write_buf(ctx, mipi_data, ARRAY_SIZE(mipi_data));
+
+ dcs_write_seq(ctx, UD_SETPOWER, 0x48, 0x12, 0x72, 0x09, 0x32, 0x54,
+ 0x71, 0x71, 0x57, 0x47);
+
+ dcs_write_seq(ctx, UD_SETDISP, 0x00, 0x80, 0x64, 0x15, 0x0E, 0x11);
+
+ dcs_write_seq(ctx, UD_SETCYC, 0x73, 0x74, 0x73, 0x74, 0x73, 0x74, 0x01,
+ 0x0C, 0x86, 0x75, 0x00, 0x3F, 0x73, 0x74, 0x73, 0x74,
+ 0x73, 0x74, 0x01, 0x0C, 0x86);
+
+ dcs_write_seq(ctx, UD_SETGIP0, 0x00, 0x00, 0x07, 0x07, 0x40, 0x07, 0x0C,
+ 0x00, 0x08, 0x10, 0x08, 0x00, 0x08, 0x54, 0x15, 0x0A,
+ 0x05, 0x0A, 0x02, 0x15, 0x06, 0x05, 0x06, 0x47, 0x44,
+ 0x0A, 0x0A, 0x4B, 0x10, 0x07, 0x07, 0x0C, 0x40);
+
+ dcs_write_seq(ctx, UD_SETGIP1, 0x1C, 0x1C, 0x1D, 0x1D, 0x00, 0x01, 0x02,
+ 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B,
+ 0x24, 0x25, 0x18, 0x18, 0x26, 0x27, 0x18, 0x18, 0x18,
+ 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+ 0x18, 0x18, 0x18, 0x18, 0x20, 0x21, 0x18, 0x18, 0x18,
+ 0x18);
+
+ dcs_write_seq(ctx, UD_SETGIP2, 0x1C, 0x1C, 0x1D, 0x1D, 0x07, 0x06, 0x05,
+ 0x04, 0x03, 0x02, 0x01, 0x00, 0x0B, 0x0A, 0x09, 0x08,
+ 0x21, 0x20, 0x18, 0x18, 0x27, 0x26, 0x18, 0x18, 0x18,
+ 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+ 0x18, 0x18, 0x18, 0x18, 0x25, 0x24, 0x18, 0x18, 0x18,
+ 0x18);
+
+ dcs_write_seq(ctx, UD_SETVCOM, 0x92, 0x92);
+
+ dcs_write_seq(ctx, UD_SETGAMMA, 0x00, 0x0A, 0x15, 0x1B, 0x1E, 0x21,
+ 0x24, 0x22, 0x47, 0x56, 0x65, 0x66, 0x6E, 0x82, 0x88,
+ 0x8B, 0x9A, 0x9D, 0x98, 0xA8, 0xB9, 0x5D, 0x5C, 0x61,
+ 0x66, 0x6A, 0x6F, 0x7F, 0x7F, 0x00, 0x0A, 0x15, 0x1B,
+ 0x1E, 0x21, 0x24, 0x22, 0x47, 0x56, 0x65, 0x65, 0x6E,
+ 0x81, 0x87, 0x8B, 0x98, 0x9D, 0x99, 0xA8, 0xBA, 0x5D,
+ 0x5D, 0x62, 0x67, 0x6B, 0x72, 0x7F, 0x7F);
+ dcs_write_seq(ctx, 0xC0, 0x1F, 0x31);
+ dcs_write_seq(ctx, UD_SETPANEL, 0x03);
+ dcs_write_seq(ctx, 0xD4, 0x02);
+ dcs_write_seq(ctx, UD_SETREGBANK, 0x02);
+ dcs_write_seq(ctx, 0xD8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF);
+ dcs_write_seq(ctx, UD_SETREGBANK, 0x00);
+ dcs_write_seq(ctx, UD_SETREGBANK, 0x01);
+ dcs_write_seq(ctx, UD_SETPOWER, 0x00);
+ dcs_write_seq(ctx, UD_SETREGBANK, 0x00);
+ dcs_write_seq(ctx, 0xBF, 0x40, 0x81, 0x50, 0x00, 0x1A, 0xFC, 0x01);
+ dcs_write_seq(ctx, 0xC6, 0xED);
+}
+
+static int hx8394f_disable(struct drm_panel *panel)
+{
+ struct hx8394f *ctx = panel_to_hx8394f(panel);
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+ int ret;
+
+ if (!ctx->enabled)
+ return 0;
+
+ ret = mipi_dsi_dcs_set_display_off(dsi);
+ if (ret)
+ dev_warn(panel->dev, "failed to set display off: %d\n", ret);
+
+ ret = mipi_dsi_dcs_enter_sleep_mode(dsi);
+ if (ret)
+ dev_warn(panel->dev, "failed to enter sleep mode: %d\n", ret);
+
+ msleep(120);
+
+ ctx->enabled = false;
+
+ return 0;
+}
+
+static int hx8394f_unprepare(struct drm_panel *panel)
+{
+ struct hx8394f *ctx = panel_to_hx8394f(panel);
+ int ret;
+
+ if (!ctx->prepared)
+ return 0;
+
+ if (ctx->reset_gpio) {
+ gpiod_set_value_cansleep(ctx->reset_gpio, 1);
+ msleep(20);
+ }
+
+ gpiod_set_value_cansleep(ctx->enable_gpio, 0);
+
+ ret = regulator_bulk_disable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
+ if (ret < 0) {
+ dev_err(ctx->dev, "failed to disable supplies: %d\n", ret);
+ return ret;
+ }
+
+ ctx->prepared = false;
+
+ return 0;
+}
+
+static int hx8394f_prepare(struct drm_panel *panel)
+{
+ struct hx8394f *ctx = panel_to_hx8394f(panel);
+ int ret;
+
+ if (ctx->prepared)
+ return 0;
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(ctx->supplies), ctx->supplies);
+ if (ret) {
+ dev_err(ctx->dev, "failed to enable supplies: %d\n", ret);
+ return ret;
+ }
+
+ gpiod_set_value_cansleep(ctx->enable_gpio, 1);
+
+ if (ctx->reset_gpio) {
+ gpiod_set_value_cansleep(ctx->reset_gpio, 1);
+ msleep(20);
+ gpiod_set_value_cansleep(ctx->reset_gpio, 0);
+ msleep(55);
+ }
+
+ ctx->prepared = true;
+
+ return 0;
+}
+
+static int hx8394f_enable(struct drm_panel *panel)
+{
+ struct hx8394f *ctx = panel_to_hx8394f(panel);
+ struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
+ int ret;
+
+ if (ctx->enabled)
+ return 0;
+
+ hx8394f_init_sequence(ctx);
+
+ /* Set tear ON */
+ ret = mipi_dsi_dcs_set_tear_on(dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
+ if (ret < 0) {
+ dev_err(ctx->dev, "failed to set tear ON (%d)\n", ret);
+ return ret;
+ }
+
+ ret = mipi_dsi_dcs_exit_sleep_mode(dsi);
+ if (ret)
+ return ret;
+
+ msleep(120);
+
+ ret = mipi_dsi_dcs_set_display_on(dsi);
+ if (ret)
+ return ret;
+
+ msleep(50);
+
+ ctx->enabled = true;
+
+ return 0;
+}
+
+static int hx8394f_get_modes(struct drm_panel *panel,
+ struct drm_connector *connector)
+{
+ struct drm_display_mode *mode;
+
+ mode = drm_mode_duplicate(connector->dev, &default_mode);
+ if (!mode) {
+ dev_err(panel->dev, "failed to add mode %ux%u@%u\n",
+ default_mode.hdisplay, default_mode.vdisplay,
+ drm_mode_vrefresh(&default_mode));
+ return -ENOMEM;
+ }
+
+ drm_mode_set_name(mode);
+
+ mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+ drm_mode_probed_add(connector, mode);
+
+ connector->display_info.width_mm = mode->width_mm;
+ connector->display_info.height_mm = mode->height_mm;
+
+ return 1;
+}
+
+static const struct drm_panel_funcs hx8394f_drm_funcs = {
+ .disable = hx8394f_disable,
+ .unprepare = hx8394f_unprepare,
+ .prepare = hx8394f_prepare,
+ .enable = hx8394f_enable,
+ .get_modes = hx8394f_get_modes,
+};
+
+static int hx8394f_probe(struct mipi_dsi_device *dsi)
+{
+ struct device *dev = &dsi->dev;
+ struct hx8394f *ctx;
+ int ret;
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->enable_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_LOW);
+ if (IS_ERR(ctx->enable_gpio)) {
+ ret = PTR_ERR(ctx->enable_gpio);
+ dev_err(dev, "failed to get enable GPIO: %d\n", ret);
+ return ret;
+ }
+
+ ctx->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(ctx->reset_gpio)) {
+ ret = PTR_ERR(ctx->reset_gpio);
+ dev_err(dev, "failed to get reset GPIO: %d\n", ret);
+ return ret;
+ }
+
+ ctx->supplies[0].supply = "vcc";
+ ctx->supplies[1].supply = "iovcc";
+ ret = devm_regulator_bulk_get(dev, 2, ctx->supplies);
+ if (ret < 0)
+ return ret;
+
+ mipi_dsi_set_drvdata(dsi, ctx);
+
+ ctx->dev = dev;
+
+ ret = of_property_read_u32(dev->of_node, "himax,dsi-lanes",
+ &dsi->lanes);
+ if (ret) {
+ dev_err(dev, "failed to get himax,dsi-lanes property: %d\n",
+ ret);
+ return ret;
+ }
+
+ dsi->format = MIPI_DSI_FMT_RGB888;
+ dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
+ MIPI_DSI_MODE_LPM;
+
+ drm_panel_init(&ctx->panel, dev, &hx8394f_drm_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+
+ ret = drm_panel_of_backlight(&ctx->panel);
+ if (ret)
+ return ret;
+
+ drm_panel_add(&ctx->panel);
+
+ ret = mipi_dsi_attach(dsi);
+ if (ret < 0) {
+ dev_err(dev, "mipi_dsi_attach() failed: %d\n", ret);
+ drm_panel_remove(&ctx->panel);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int hx8394f_remove(struct mipi_dsi_device *dsi)
+{
+ struct hx8394f *ctx = mipi_dsi_get_drvdata(dsi);
+
+ mipi_dsi_detach(dsi);
+ drm_panel_remove(&ctx->panel);
+
+ return 0;
+}
+
+static const struct of_device_id hx8394f_of_match[] = {
+ { .compatible = "rocktech,hx8394f" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, hx8394f_of_match);
+
+static struct mipi_dsi_driver hx8394f_driver = {
+ .probe = hx8394f_probe,
+ .remove = hx8394f_remove,
+ .driver = {
+ .name = "panel-rocktech-hx8394f",
+ .of_match_table = hx8394f_of_match,
+ },
+};
+module_mipi_dsi_driver(hx8394f_driver);
+
+MODULE_AUTHOR("NXP Semiconductor");
+MODULE_DESCRIPTION("DRM Driver for Rocktech Himax8394f MIPI DSI panel");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c
index 7cf0af78b7bc..8fdd04e7ef59 100644
--- a/drivers/gpu/drm/panel/panel-simple.c
+++ b/drivers/gpu/drm/panel/panel-simple.c
@@ -1500,6 +1500,37 @@ static const struct panel_desc bananapi_s070wv20_ct16 = {
},
};
+static const struct drm_display_mode boe_ev121wxm_n10_1850_mode = {
+ .clock = 71143,
+ .hdisplay = 1280,
+ .hsync_start = 1280 + 48,
+ .hsync_end = 1280 + 48 + 32,
+ .htotal = 1280 + 48 + 32 + 80,
+ .vdisplay = 800,
+ .vsync_start = 800 + 3,
+ .vsync_end = 800 + 3 + 6,
+ .vtotal = 800 + 3 + 6 + 14,
+};
+
+static const struct panel_desc boe_ev121wxm_n10_1850 = {
+ .modes = &boe_ev121wxm_n10_1850_mode,
+ .num_modes = 1,
+ .bpc = 8,
+ .size = {
+ .width = 261,
+ .height = 163,
+ },
+ .delay = {
+ .prepare = 8,
+ .enable = 300,
+ .unprepare = 300,
+ .disable = 60,
+ },
+ .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG,
+ .bus_flags = DRM_BUS_FLAG_DE_HIGH,
+ .connector_type = DRM_MODE_CONNECTOR_LVDS,
+};
+
static const struct drm_display_mode boe_hv070wsa_mode = {
.clock = 42105,
.hdisplay = 1024,
@@ -2834,6 +2865,43 @@ static const struct panel_desc kingdisplay_kd116n21_30nv_a010 = {
.connector_type = DRM_MODE_CONNECTOR_eDP,
};
+static const struct display_timing jdi_tx26d202vm0bwa_timing = {
+ .pixelclock = { 151820000, 156720000, 159780000 },
+ .hactive = { 1920, 1920, 1920 },
+ .hfront_porch = { 76, 100, 112 },
+ .hback_porch = { 74, 100, 112 },
+ .hsync_len = { 30, 30, 30 },
+ .vactive = { 1200, 1200, 1200},
+ .vfront_porch = { 3, 5, 10 },
+ .vback_porch = { 2, 5, 10 },
+ .vsync_len = { 5, 5, 5 },
+ .flags = DISPLAY_FLAGS_DE_HIGH,
+};
+
+static const struct panel_desc jdi_tx26d202vm0bwa = {
+ .timings = &jdi_tx26d202vm0bwa_timing,
+ .num_timings = 1,
+ .bpc = 8,
+ .size = {
+ .width = 217,
+ .height = 136,
+ },
+ .delay = {
+ /*
+ * The panel spec recommends one second delay
+ * to the below items. However, it's a bit too
+ * long in pratice. Based on tests, it turns
+ * out 100 milliseconds is fine.
+ */
+ .prepare = 100,
+ .enable = 100,
+ .unprepare = 100,
+ .disable = 100,
+ },
+ .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG,
+ .connector_type = DRM_MODE_CONNECTOR_LVDS,
+};
+
static const struct display_timing koe_tx14d24vm1bpa_timing = {
.pixelclock = { 5580000, 5850000, 6200000 },
.hactive = { 320, 320, 320 },
@@ -4550,6 +4618,9 @@ static const struct of_device_id platform_of_match[] = {
.compatible = "bananapi,s070wv20-ct16",
.data = &bananapi_s070wv20_ct16,
}, {
+ .compatible = "boe,ev121wxm-n10-1850",
+ .data = &boe_ev121wxm_n10_1850,
+ }, {
.compatible = "boe,hv070wsa-100",
.data = &boe_hv070wsa
}, {
@@ -4703,6 +4774,9 @@ static const struct of_device_id platform_of_match[] = {
.compatible = "kingdisplay,kd116n21-30nv-a010",
.data = &kingdisplay_kd116n21_30nv_a010,
}, {
+ .compatible = "jdi,tx26d202vm0bwa",
+ .data = &jdi_tx26d202vm0bwa,
+ }, {
.compatible = "koe,tx14d24vm1bpa",
.data = &koe_tx14d24vm1bpa,
}, {
diff --git a/drivers/gpu/drm/panel/panel-wks-101wx001.c b/drivers/gpu/drm/panel/panel-wks-101wx001.c
new file mode 100644
index 000000000000..ef924ab3a477
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-wks-101wx001.c
@@ -0,0 +1,228 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * WKS 101WX001-WCT parallel LCD panel driver
+ *
+ * Copyright 2020 NXP
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+
+#include <video/display_timing.h>
+#include <video/videomode.h>
+
+#include <drm/drm_crtc.h>
+#include <drm/drm_device.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_print.h>
+
+struct wks_panel {
+ struct drm_panel panel;
+ struct gpio_desc *bl_ctr;
+ struct regulator *vcc;
+
+ bool prepared;
+ bool enabled;
+};
+
+static const struct drm_display_mode default_mode = {
+ .clock = 71100,
+ .hdisplay = 1280,
+ .hsync_start = 1280 + 70,
+ .hsync_end = 1280 + 70 + 10,
+ .htotal = 1280 + 70 + 10 + 80,
+ .vdisplay = 800,
+ .vsync_start = 800 + 10,
+ .vsync_end = 800 + 10 + 3,
+ .vtotal = 800 + 10 + 3 + 10,
+ .width_mm = 217,
+ .height_mm = 135,
+ .flags = DRM_MODE_FLAG_NHSYNC |
+ DRM_MODE_FLAG_NVSYNC,
+};
+
+static const u32 wks_bus_formats[] = {
+ MEDIA_BUS_FMT_RGB666_1X18,
+};
+
+static const u32 wks_bus_flags = DRM_BUS_FLAG_DE_HIGH |
+ DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE;
+
+static inline struct wks_panel *to_wks_panel(struct drm_panel *panel)
+{
+ return container_of(panel, struct wks_panel, panel);
+}
+
+static int wks_panel_prepare(struct drm_panel *panel)
+{
+ struct wks_panel *p = to_wks_panel(panel);
+ int err;
+
+ if (p->prepared)
+ return 0;
+
+ err = regulator_enable(p->vcc);
+ if (err < 0) {
+ dev_err(panel->dev, "failed to enable vcc: %d\n", err);
+ return err;
+ }
+
+ p->prepared = true;
+
+ return 0;
+}
+
+static int wks_panel_enable(struct drm_panel *panel)
+{
+ struct wks_panel *p = to_wks_panel(panel);
+
+ if (p->enabled)
+ return 0;
+
+ gpiod_set_value_cansleep(p->bl_ctr, 1);
+
+ p->enabled = true;
+
+ return 0;
+}
+
+
+static int wks_panel_disable(struct drm_panel *panel)
+{
+ struct wks_panel *p = to_wks_panel(panel);
+
+ if (!p->enabled)
+ return 0;
+
+ gpiod_set_value_cansleep(p->bl_ctr, 0);
+
+ p->enabled = false;
+
+ return 0;
+}
+
+static int wks_panel_unprepare(struct drm_panel *panel)
+{
+ struct wks_panel *p = to_wks_panel(panel);
+
+ if (!p->prepared)
+ return 0;
+
+ regulator_disable(p->vcc);
+
+ p->prepared = false;
+
+ return 0;
+}
+
+static int wks_panel_get_modes(struct drm_panel *panel,
+ struct drm_connector *connector)
+{
+ struct drm_display_mode *mode;
+
+ mode = drm_mode_duplicate(connector->dev, &default_mode);
+ if (!mode) {
+ DRM_DEV_ERROR(panel->dev, "failed to add mode %ux%ux@%d\n",
+ default_mode.hdisplay, default_mode.vdisplay,
+ drm_mode_vrefresh(&default_mode));
+ return -ENOMEM;
+ }
+
+ drm_mode_set_name(mode);
+ mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+ drm_mode_probed_add(connector, mode);
+ DRM_DEV_DEBUG_DRIVER(panel->dev, "Mode flags: 0x%08X", mode->flags);
+
+ connector->display_info.width_mm = mode->width_mm;
+ connector->display_info.height_mm = mode->height_mm;
+ connector->display_info.bus_flags = wks_bus_flags;
+
+ drm_display_info_set_bus_formats(&connector->display_info,
+ wks_bus_formats,
+ ARRAY_SIZE(wks_bus_formats));
+ return 1;
+
+}
+
+static const struct drm_panel_funcs wks_panel_funcs = {
+ .prepare = wks_panel_prepare,
+ .enable = wks_panel_enable,
+ .disable = wks_panel_disable,
+ .unprepare = wks_panel_unprepare,
+ .get_modes = wks_panel_get_modes,
+};
+
+static int wks_panel_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct wks_panel *panel;
+ int err;
+
+ panel = devm_kzalloc(dev, sizeof(*panel), GFP_KERNEL);
+ if (!panel)
+ return -ENOMEM;
+
+ panel->vcc = devm_regulator_get(dev, "vcc");
+ if (IS_ERR(panel->vcc))
+ return PTR_ERR(panel->vcc);
+
+ panel->bl_ctr = devm_gpiod_get(dev, "blctr", GPIOD_OUT_LOW);
+ if (IS_ERR(panel->bl_ctr)) {
+ err = PTR_ERR(panel->bl_ctr);
+ dev_err(dev, "Failed to get blctr gpio (%d)\n", err);
+ return err;
+ }
+
+ drm_panel_init(&panel->panel,
+ dev,
+ &wks_panel_funcs,
+ DRM_MODE_CONNECTOR_DPI);
+
+ drm_panel_add(&panel->panel);
+ dev_set_drvdata(dev, panel);
+
+ return 0;
+}
+
+static int wks_panel_remove(struct platform_device *pdev)
+{
+ struct wks_panel *p = dev_get_drvdata(&pdev->dev);
+
+ drm_panel_remove(&p->panel);
+
+ wks_panel_disable(&p->panel);
+
+ return 0;
+}
+
+static void wks_panel_shutdown(struct platform_device *pdev)
+{
+ struct wks_panel *p = dev_get_drvdata(&pdev->dev);
+
+ wks_panel_disable(&p->panel);
+}
+
+static const struct of_device_id wks_of_match[] = {
+ { .compatible = "wks,101wx001", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, wks_of_match);
+
+static struct platform_driver wks_panel_platform_driver = {
+ .driver = {
+ .name = "panel-wks-101wx001",
+ .of_match_table = wks_of_match,
+ },
+ .probe = wks_panel_probe,
+ .remove = wks_panel_remove,
+ .shutdown = wks_panel_shutdown,
+};
+module_platform_driver(wks_panel_platform_driver);
+
+MODULE_AUTHOR("Marco Franchi <marco.franchi@nxp.com>");
+MODULE_DESCRIPTION("Seiko 43WVF1G panel driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/imx/Kconfig b/drivers/gpu/imx/Kconfig
new file mode 100644
index 000000000000..0c9f14f02a87
--- /dev/null
+++ b/drivers/gpu/imx/Kconfig
@@ -0,0 +1,19 @@
+source "drivers/gpu/imx/ipu-v3/Kconfig"
+source "drivers/gpu/imx/dpu/Kconfig"
+source "drivers/gpu/imx/dpu-blit/Kconfig"
+source "drivers/gpu/imx/lcdif/Kconfig"
+source "drivers/gpu/imx/lcdifv3/Kconfig"
+config IMX8_PC
+ tristate
+ default y if IMX_DPU_CORE=y
+ default m if IMX_DPU_CORE=m
+
+config IMX8_PRG
+ tristate
+ default y if IMX_DPU_CORE=y
+ default m if IMX_DPU_CORE=m
+
+config IMX8_DPRC
+ tristate
+ default y if IMX_DPU_CORE=y
+ default m if IMX_DPU_CORE=m
diff --git a/drivers/gpu/imx/Makefile b/drivers/gpu/imx/Makefile
new file mode 100644
index 000000000000..3c08def348d6
--- /dev/null
+++ b/drivers/gpu/imx/Makefile
@@ -0,0 +1,8 @@
+obj-$(CONFIG_IMX_IPUV3_CORE) += ipu-v3/
+obj-$(CONFIG_IMX_DPU_CORE) += dpu/
+obj-$(CONFIG_IMX_DPU_BLIT) += dpu-blit/
+obj-$(CONFIG_IMX_LCDIF_CORE) += lcdif/
+obj-$(CONFIG_IMX_LCDIFV3_CORE) += lcdifv3/
+obj-$(CONFIG_IMX8_PC) += imx8_pc.o
+obj-$(CONFIG_IMX8_PRG) += imx8_prg.o
+obj-$(CONFIG_IMX8_DPRC) += imx8_dprc.o
diff --git a/drivers/gpu/imx/dpu-blit/Kconfig b/drivers/gpu/imx/dpu-blit/Kconfig
new file mode 100644
index 000000000000..d71d9a7f9515
--- /dev/null
+++ b/drivers/gpu/imx/dpu-blit/Kconfig
@@ -0,0 +1,5 @@
+config IMX_DPU_BLIT
+ tristate
+ depends on IMX_DPU_CORE
+ default y if IMX_DPU_CORE=y
+ default m if IMX_DPU_CORE=m
diff --git a/drivers/gpu/imx/dpu-blit/Makefile b/drivers/gpu/imx/dpu-blit/Makefile
new file mode 100644
index 000000000000..6d06b88b9d5e
--- /dev/null
+++ b/drivers/gpu/imx/dpu-blit/Makefile
@@ -0,0 +1,5 @@
+ccflags-y += -I $(srctree)/$(src)/../dpu/
+
+imx-dpu-blit-objs := dpu-blit.o
+
+obj-$(CONFIG_IMX_DPU_BLIT) += imx-dpu-blit.o
diff --git a/drivers/gpu/imx/dpu-blit/dpu-blit-registers.h b/drivers/gpu/imx/dpu-blit/dpu-blit-registers.h
new file mode 100644
index 000000000000..14563746c7eb
--- /dev/null
+++ b/drivers/gpu/imx/dpu-blit/dpu-blit-registers.h
@@ -0,0 +1,284 @@
+/*
+ * Copyright 2017,2022 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef __DPU_BLIT_REGISTERS_H__
+#define __DPU_BLIT_REGISTERS_H__
+
+/* Registers Defination */
+#define COMCTRL_IPIDENTIFIER ((uint32_t)(0))
+
+#define PIXENGCFG_STORE9_TRIGGER ((uint32_t)(0x954))
+
+#define COMCTRL_USERINTERRUPTMASK0 ((uint32_t)(0x48))
+#define COMCTRL_USERINTERRUPTMASK0_USERINTERRUPTMASK0_MASK 0xFFFFFFFFU
+#define COMCTRL_USERINTERRUPTENABLE0 ((uint32_t)(0x80))
+
+#define COMCTRL_INTERRUPTENABLE0 ((uint32_t)(0x50))
+
+#define COMCTRL_INTERRUPTSTATUS0 ((uint32_t)(0x68))
+#define COMCTRL_USERINTERRUPTSTATUS0 ((uint32_t)(0x98))
+
+#define COMCTRL_USERINTERRUPTCLEAR0 ((uint32_t)(0x90))
+#define COMCTRL_USERINTERRUPTCLEAR0_USERINTERRUPTCLEAR0_MASK 0xFFFFFFFFU
+
+#define COMCTRL_INTERRUPTCLEAR0 ((uint32_t)(0x60))
+#define COMCTRL_INTERRUPTCLEAR0_INTERRUPTCLEAR0_MASK 0xFFFFFFFFU
+
+#define COMCTRL_USERINTERRUPTPRESET1 ((uint32_t)(0x8C))
+
+#define PIXENGCFG_FETCHDECODE9_DYNAMIC ((uint32_t)(0x828))
+#define PIXENGCFG_FETCHDECODE9_DYNAMIC_RESET_VALUE 0U
+
+#define PIXENGCFG_FETCHWARP9_DYNAMIC ((uint32_t)(0x848))
+#define PIXENGCFG_FETCHWARP9_DYNAMIC_RESET_VALUE 0U
+
+#define PIXENGCFG_ROP9_DYNAMIC ((uint32_t)(0x868))
+#define PIXENGCFG_ROP9_DYNAMIC_RESET_VALUE 0x1000000U
+
+#define PIXENGCFG_MATRIX9_DYNAMIC ((uint32_t)(0x8A8))
+#define PIXENGCFG_MATRIX9_DYNAMIC_RESET_VALUE 0x1000000U
+
+#define PIXENGCFG_HSCALER9_DYNAMIC ((uint32_t)(0x8C8))
+#define PIXENGCFG_HSCALER9_DYNAMIC_RESET_VALUE 0x1000000U
+
+#define PIXENGCFG_VSCALER9_DYNAMIC ((uint32_t)(0x8E8))
+#define PIXENGCFG_VSCALER9_DYNAMIC_RESET_VALUE 0x1000000U
+
+#define PIXENGCFG_BLITBLEND9_DYNAMIC ((uint32_t)(0x928))
+#define PIXENGCFG_BLITBLEND9_DYNAMIC_RESET_VALUE 0x1000000U
+
+#define PIXENGCFG_STORE9_STATIC ((uint32_t)(0x948))
+#define PIXENGCFG_STORE9_STATIC_RESET_VALUE 0x800010U
+#define PIXENGCFG_STORE9_STATIC_RESET_MASK 0xFFFFFFFFU
+#define PIXENGCFG_STORE9_STATIC_STORE9_SHDEN_MASK 0x1U
+#define PIXENGCFG_STORE9_STATIC_STORE9_SHDEN_SHIFT 0U
+#define PIXENGCFG_STORE9_STATIC_STORE9_POWERDOWN_MASK 0x10U
+#define PIXENGCFG_STORE9_STATIC_STORE9_POWERDOWN_SHIFT 4U
+#define PIXENGCFG_STORE9_STATIC_STORE9_SYNC_MODE_MASK 0x100U
+#define PIXENGCFG_STORE9_STATIC_STORE9_SYNC_MODE_SHIFT 8U
+#define PIXENGCFG_STORE9_STATIC_STORE9_SYNC_MODE__SINGLE 0U
+#define PIXENGCFG_STORE9_STATIC_STORE9_SYNC_MODE__AUTO 0x1U
+#define PIXENGCFG_STORE9_STATIC_STORE9_SW_RESET_MASK 0x800U
+#define PIXENGCFG_STORE9_STATIC_STORE9_SW_RESET_SHIFT 11U
+/* Field Value: STORE9_SW_RESET__OPERATION, Normal Operation */
+#define PIXENGCFG_STORE9_STATIC_STORE9_SW_RESET__OPERATION 0U
+/* Field Value: STORE9_SW_RESET__SWRESET, Software Reset */
+#define PIXENGCFG_STORE9_STATIC_STORE9_SW_RESET__SWRESET 0x1U
+#define PIXENGCFG_STORE9_STATIC_STORE9_DIV_MASK 0xFF0000U
+#define PIXENGCFG_STORE9_STATIC_STORE9_DIV_SHIFT 16U
+
+#define PIXENGCFG_STORE9_DYNAMIC ((uint32_t)(0x94C))
+
+#define FETCHDECODE9_STATICCONTROL ((uint32_t)(0x1008))
+#define FETCHDECODE9_STATICCONTROL_OFFSET ((uint32_t)(0x8))
+#define FETCHDECODE9_STATICCONTROL_RESET_VALUE 0U
+#define FETCHDECODE9_STATICCONTROL_SHDEN_MASK 0x1U
+#define FETCHDECODE9_STATICCONTROL_SHDEN_SHIFT 0U
+#define FETCHDECODE9_STATICCONTROL_BASEADDRESSAUTOUPDATE_MASK 0xFF0000U
+#define FETCHDECODE9_STATICCONTROL_BASEADDRESSAUTOUPDATE_SHIFT 16U
+
+#define FETCHDECODE9_BURSTBUFFERMANAGEMENT ((uint32_t)(0x100C))
+#define FETCHDECODE9_BASEADDRESS0 ((uint32_t)(0x101C))
+#define FETCHDECODE9_SOURCEBUFFERATTRIBUTES0 ((uint32_t)(0x1020))
+#define FETCHDECODE9_SOURCEBUFFERDIMENSION0 ((uint32_t)(0x1024))
+#define FETCHDECODE9_COLORCOMPONENTBITS0 ((uint32_t)(0x1028))
+#define FETCHDECODE9_COLORCOMPONENTSHIFT0 ((uint32_t)(0x102C))
+#define FETCHDECODE9_LAYEROFFSET0 ((uint32_t)(0x1030))
+#define FETCHDECODE9_CLIPWINDOWOFFSET0 ((uint32_t)(0x1034))
+#define FETCHDECODE9_CLIPWINDOWDIMENSIONS0 ((uint32_t)(0x1038))
+#define FETCHDECODE9_CONSTANTCOLOR0 ((uint32_t)(0x103C))
+#define FETCHDECODE9_LAYERPROPERTY0 ((uint32_t)(0x1040))
+#define FETCHDECODE9_FRAMEDIMENSIONS ((uint32_t)(0x1044))
+#define FETCHDECODE9_FRAMERESAMPLING ((uint32_t)(0x1048))
+#define FETCHDECODE9_CONTROL ((uint32_t)(0x1054))
+
+#define FETCHWARP9_STATICCONTROL ((uint32_t)(0x1808))
+#define FETCHWARP9_STATICCONTROL_OFFSET ((uint32_t)(0x8))
+#define FETCHWARP9_STATICCONTROL_RESET_VALUE 0xFF000000U
+#define FETCHWARP9_STATICCONTROL_RESET_MASK 0xFFFFFFFFU
+#define FETCHWARP9_STATICCONTROL_SHDEN_MASK 0x1U
+#define FETCHWARP9_STATICCONTROL_SHDEN_SHIFT 0U
+#define FETCHWARP9_STATICCONTROL_BASEADDRESSAUTOUPDATE_MASK 0xFF0000U
+#define FETCHWARP9_STATICCONTROL_BASEADDRESSAUTOUPDATE_SHIFT 16U
+#define FETCHWARP9_STATICCONTROL_SHDLDREQSTICKY_MASK 0xFF000000U
+#define FETCHWARP9_STATICCONTROL_SHDLDREQSTICKY_SHIFT 24U
+
+#define FETCHWARP9_BURSTBUFFERMANAGEMENT ((uint32_t)(0x180C))
+#define FETCHWARP9_BASEADDRESS0 ((uint32_t)(0x1810))
+#define FETCHWARP9_SOURCEBUFFERATTRIBUTES0 ((uint32_t)(0x1814))
+#define FETCHWARP9_SOURCEBUFFERDIMENSION0 ((uint32_t)(0x1818))
+#define FETCHWARP9_COLORCOMPONENTBITS0 ((uint32_t)(0x181C))
+#define FETCHWARP9_COLORCOMPONENTSHIFT0 ((uint32_t)(0x1820))
+#define FETCHWARP9_LAYEROFFSET0 ((uint32_t)(0x1824))
+#define FETCHWARP9_CLIPWINDOWOFFSET0 ((uint32_t)(0x1828))
+#define FETCHWARP9_CLIPWINDOWDIMENSIONS0 ((uint32_t)(0x182C))
+#define FETCHWARP9_CONSTANTCOLOR0 ((uint32_t)(0x1830))
+#define FETCHWARP9_LAYERPROPERTY0 ((uint32_t)(0x1834))
+#define FETCHWARP9_FRAMEDIMENSIONS ((uint32_t)(0x1950))
+#define FETCHWARP9_FRAMERESAMPLING ((uint32_t)(0x1954))
+#define FETCHWARP9_CONTROL ((uint32_t)(0x1970))
+
+
+#define FETCHECO9_STATICCONTROL ((uint32_t)(0x1C08))
+#define FETCHECO9_STATICCONTROL_OFFSET ((uint32_t)(0x8))
+#define FETCHECO9_STATICCONTROL_RESET_VALUE 0U
+#define FETCHECO9_STATICCONTROL_RESET_MASK 0xFFFFFFFFU
+#define FETCHECO9_STATICCONTROL_SHDEN_MASK 0x1U
+#define FETCHECO9_STATICCONTROL_SHDEN_SHIFT 0U
+#define FETCHECO9_STATICCONTROL_BASEADDRESSAUTOUPDATE_MASK 0xFF0000U
+#define FETCHECO9_STATICCONTROL_BASEADDRESSAUTOUPDATE_SHIFT 16U
+
+#define FETCHECO9_BURSTBUFFERMANAGEMENT ((uint32_t)(0x1C0C))
+#define FETCHECO9_BASEADDRESS0 ((uint32_t)(0x1C10))
+#define FETCHECO9_SOURCEBUFFERATTRIBUTES0 ((uint32_t)(0x1C14))
+#define FETCHECO9_SOURCEBUFFERDIMENSION0 ((uint32_t)(0x1C18))
+#define FETCHECO9_COLORCOMPONENTBITS0 ((uint32_t)(0x1C1C))
+#define FETCHECO9_COLORCOMPONENTSHIFT0 ((uint32_t)(0x1C20))
+#define FETCHECO9_LAYEROFFSET0 ((uint32_t)(0x1C24))
+#define FETCHECO9_CLIPWINDOWOFFSET0 ((uint32_t)(0x1C28))
+#define FETCHECO9_CLIPWINDOWDIMENSIONS0 ((uint32_t)(0x1C2C))
+#define FETCHECO9_CONSTANTCOLOR0 ((uint32_t)(0x1C30))
+#define FETCHECO9_LAYERPROPERTY0 ((uint32_t)(0x1C34))
+#define FETCHECO9_FRAMEDIMENSIONS ((uint32_t)(0x1C38))
+#define FETCHECO9_FRAMERESAMPLING ((uint32_t)(0x1C3C))
+#define FETCHECO9_CONTROL ((uint32_t)(0x1C40))
+
+
+#define ROP9_STATICCONTROL ((uint32_t)(0x2008))
+#define ROP9_STATICCONTROL_OFFSET ((uint32_t)(0x8))
+#define ROP9_STATICCONTROL_RESET_VALUE 0U
+#define ROP9_STATICCONTROL_RESET_MASK 0xFFFFFFFFU
+#define ROP9_STATICCONTROL_SHDEN_MASK 0x1U
+#define ROP9_STATICCONTROL_SHDEN_SHIFT 0U
+
+#define ROP9_CONTROL ((uint32_t)(0x200C))
+
+#define MATRIX9_STATICCONTROL ((uint32_t)(0x2C08))
+#define MATRIX9_STATICCONTROL_OFFSET ((uint32_t)(0x8))
+#define MATRIX9_STATICCONTROL_RESET_VALUE 0U
+#define MATRIX9_STATICCONTROL_RESET_MASK 0xFFFFFFFFU
+#define MATRIX9_STATICCONTROL_SHDEN_MASK 0x1U
+#define MATRIX9_STATICCONTROL_SHDEN_SHIFT 0U
+
+#define MATRIX9_CONTROL ((uint32_t)(0x2C0C))
+
+#define HSCALER9_SETUP1 ((uint32_t)(0x300C))
+#define HSCALER9_SETUP2 ((uint32_t)(0x3010))
+#define HSCALER9_CONTROL ((uint32_t)(0x3014))
+
+#define VSCALER9_STATICCONTROL ((uint32_t)(0x3408))
+#define VSCALER9_STATICCONTROL_OFFSET ((uint32_t)(0x8))
+#define VSCALER9_STATICCONTROL_RESET_VALUE 0U
+#define VSCALER9_STATICCONTROL_RESET_MASK 0xFFFFFFFFU
+#define VSCALER9_STATICCONTROL_SHDEN_MASK 0x1U
+#define VSCALER9_STATICCONTROL_SHDEN_SHIFT 0U
+
+#define VSCALER9_SETUP1 ((uint32_t)(0x340C))
+#define VSCALER9_SETUP2 ((uint32_t)(0x3410))
+#define VSCALER9_SETUP3 ((uint32_t)(0x3414))
+#define VSCALER9_SETUP4 ((uint32_t)(0x3418))
+#define VSCALER9_SETUP5 ((uint32_t)(0x341C))
+#define VSCALER9_CONTROL ((uint32_t)(0x3420))
+
+#define HSCALER9_STATICCONTROL ((uint32_t)(0x3008))
+#define HSCALER9_STATICCONTROL_OFFSET ((uint32_t)(0x8))
+#define HSCALER9_STATICCONTROL_RESET_VALUE 0U
+#define HSCALER9_STATICCONTROL_RESET_MASK 0xFFFFFFFFU
+#define HSCALER9_STATICCONTROL_SHDEN_MASK 0x1U
+#define HSCALER9_STATICCONTROL_SHDEN_SHIFT 0U
+
+#define BLITBLEND9_STATICCONTROL ((uint32_t)(0x3C08))
+#define BLITBLEND9_STATICCONTROL_OFFSET ((uint32_t)(0x8))
+#define BLITBLEND9_STATICCONTROL_RESET_VALUE 0U
+#define BLITBLEND9_STATICCONTROL_RESET_MASK 0xFFFFFFFFU
+#define BLITBLEND9_STATICCONTROL_SHDEN_MASK 0x1U
+#define BLITBLEND9_STATICCONTROL_SHDEN_SHIFT 0U
+
+#define BLITBLEND9_CONTROL ((uint32_t)(0x3C0C))
+#define BLITBLEND9_CONSTANTCOLOR ((uint32_t)(0x3C14))
+#define BLITBLEND9_COLORREDBLENDFUNCTION ((uint32_t)(0x3C18))
+#define BLITBLEND9_COLORGREENBLENDFUNCTION ((uint32_t)(0x3C1C))
+#define BLITBLEND9_COLORBLUEBLENDFUNCTION ((uint32_t)(0x3C20))
+#define BLITBLEND9_ALPHABLENDFUNCTION ((uint32_t)(0x3C24))
+#define BLITBLEND9_BLENDMODE1 ((uint32_t)(0x3C28))
+#define BLITBLEND9_BLENDMODE2 ((uint32_t)(0x3C2C))
+
+
+#define STORE9_STATICCONTROL ((uint32_t)(0x4008))
+#define STORE9_STATICCONTROL_OFFSET ((uint32_t)(0x8))
+#define STORE9_STATICCONTROL_RESET_VALUE 0U
+#define STORE9_STATICCONTROL_RESET_MASK 0xFFFFFFFFU
+#define STORE9_STATICCONTROL_SHDEN_MASK 0x1U
+#define STORE9_STATICCONTROL_SHDEN_SHIFT 0U
+#define STORE9_STATICCONTROL_BASEADDRESSAUTOUPDATE_MASK 0x100U
+#define STORE9_STATICCONTROL_BASEADDRESSAUTOUPDATE_SHIFT 8U
+
+#define STORE9_BURSTBUFFERMANAGEMENT ((uint32_t)(0x400C))
+#define STORE9_BASEADDRESS ((uint32_t)(0x4018))
+#define STORE9_DESTINATIONBUFFERATTRIBUTES ((uint32_t)(0x401C))
+#define STORE9_DESTINATIONBUFFERDIMENSION ((uint32_t)(0x4020))
+#define STORE9_FRAMEOFFSET ((uint32_t)(0x4024))
+#define STORE9_COLORCOMPONENTBITS ((uint32_t)(0x4028))
+#define STORE9_COLORCOMPONENTSHIFT ((uint32_t)(0x402C))
+#define STORE9_CONTROL ((uint32_t)(0x4030))
+
+#define STORE9_START ((uint32_t)(0x403C))
+
+/* pixengcfg */
+#define PIXENGCFG_CLKEN_MASK 0x3000000U
+#define PIXENGCFG_CLKEN_SHIFT 24U
+/* Field Value: _CLKEN__DISABLE, Clock for block is disabled */
+#define PIXENGCFG_CLKEN__DISABLE 0U
+#define PIXENGCFG_CLKEN__AUTOMATIC 0x1U
+/* Field Value: _CLKEN__FULL, Clock for block is without gating */
+#define PIXENGCFG_CLKEN__FULL 0x3U
+
+#define PIXENGCFG_DIVIDER_RESET 0x80
+
+
+/* command sequencer */
+#define CMDSEQ_HIF ((uint32_t)(0x400))
+
+#define CMDSEQ_LOCKUNLOCKHIF ((uint32_t)(0x500))
+#define CMDSEQ_LOCKUNLOCKHIF_LOCKUNLOCKHIF__LOCK_KEY 0x5651F763U
+#define CMDSEQ_LOCKUNLOCKHIF_LOCKUNLOCKHIF__UNLOCK_KEY 0x691DB936U
+
+#define CMDSEQ_LOCKUNLOCK ((uint32_t)(0x580))
+#define CMDSEQ_LOCKUNLOCK_LOCKUNLOCK__LOCK_KEY 0x5651F763U
+#define CMDSEQ_LOCKUNLOCK_LOCKUNLOCK__UNLOCK_KEY 0x691DB936U
+
+#define CMDSEQ_BUFFERADDRESS ((uint32_t)(0x588))
+#define CMDSEQ_BUFFERSIZE ((uint32_t)(0x58C))
+
+#define CMDSEQ_CONTROL ((uint32_t)(0x594))
+#define CMDSEQ_CONTROL_OFFSET ((uint32_t)(0x194))
+#define CMDSEQ_CONTROL_RESET_VALUE 0U
+#define CMDSEQ_CONTROL_RESET_MASK 0xFFFFFFFFU
+#define CMDSEQ_CONTROL_CLRAXIW_MASK 0x1U
+#define CMDSEQ_CONTROL_CLRAXIW_SHIFT 0U
+#define CMDSEQ_CONTROL_CLRRBUF_MASK 0x4U
+#define CMDSEQ_CONTROL_CLRRBUF_SHIFT 2U
+#define CMDSEQ_CONTROL_CLRCMDBUF_MASK 0x8U
+#define CMDSEQ_CONTROL_CLRCMDBUF_SHIFT 3U
+#define CMDSEQ_CONTROL_CLEAR_MASK 0x80000000U
+#define CMDSEQ_CONTROL_CLEAR_SHIFT 31U
+
+#define CMDSEQ_STATUS ((uint32_t)(0x598))
+#define CMDSEQ_STATUS_OFFSET ((uint32_t)(0x198))
+#define CMDSEQ_STATUS_RESET_VALUE 0x41000080U
+#define CMDSEQ_STATUS_RESET_MASK 0xFFFFFFFFU
+#define CMDSEQ_STATUS_FIFOSPACE_MASK 0x1FFFFU
+#define CMDSEQ_STATUS_IDLE_MASK 0x40000000U
+
+#endif
diff --git a/drivers/gpu/imx/dpu-blit/dpu-blit.c b/drivers/gpu/imx/dpu-blit/dpu-blit.c
new file mode 100644
index 000000000000..72e1c5f234ba
--- /dev/null
+++ b/drivers/gpu/imx/dpu-blit/dpu-blit.c
@@ -0,0 +1,611 @@
+/*
+ * Copyright (C) 2016 Freescale Semiconductor, Inc.
+ * Copyright 2017-2019,2022 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include <video/dpu.h>
+#include <video/imx8-prefetch.h>
+
+#include "dpu-blit.h"
+#include "dpu-blit-registers.h"
+#include "dpu-prv.h"
+
+void dpu_be_wait(struct dpu_bliteng *dpu_be);
+
+static inline u32 dpu_be_read(struct dpu_bliteng *dpu_be, unsigned int offset)
+{
+ return readl(dpu_be->base + offset);
+}
+
+static inline void dpu_be_write(struct dpu_bliteng *dpu_be, u32 value,
+ unsigned int offset)
+{
+ writel(value, dpu_be->base + offset);
+}
+
+static void dpu_cs_wait_fifo_space(struct dpu_bliteng *dpu_be)
+{
+ while ((dpu_be_read(dpu_be, CMDSEQ_STATUS) &
+ CMDSEQ_STATUS_FIFOSPACE_MASK) < CMDSEQ_FIFO_SPACE_THRESHOLD)
+ usleep_range(30, 50);
+}
+
+static void dpu_cs_wait_idle(struct dpu_bliteng *dpu_be)
+{
+ while ((dpu_be_read(dpu_be, CMDSEQ_STATUS) &
+ CMDSEQ_STATUS_IDLE_MASK) == 0x0)
+ mdelay(1);
+}
+
+static int dpu_cs_alloc_command_buffer(struct dpu_bliteng *dpu_be)
+{
+ /* command buffer need 32 bit address */
+ dpu_be->buffer_addr_virt =
+ alloc_pages_exact(COMMAND_BUFFER_SIZE,
+ GFP_KERNEL | GFP_DMA | GFP_DMA32 | __GFP_ZERO);
+ if (!dpu_be->buffer_addr_virt) {
+ dev_err(dpu_be->dev, "memory alloc failed for dpu command buffer\n");
+ return -ENOMEM;
+ }
+
+ dpu_be->buffer_addr_phy =
+ (u32)virt_to_phys(dpu_be->buffer_addr_virt);
+
+ return 0;
+}
+
+static void dpu_cs_static_setup(struct dpu_bliteng *dpu_be)
+{
+ dpu_cs_wait_idle(dpu_be);
+
+ /* LockUnlock and LockUnlockHIF */
+ dpu_be_write(dpu_be, CMDSEQ_LOCKUNLOCKHIF_LOCKUNLOCKHIF__UNLOCK_KEY,
+ CMDSEQ_LOCKUNLOCKHIF);
+ dpu_be_write(dpu_be, CMDSEQ_LOCKUNLOCK_LOCKUNLOCK__UNLOCK_KEY,
+ CMDSEQ_LOCKUNLOCK);
+
+ /* Control */
+ dpu_be_write(dpu_be, 1 << CMDSEQ_CONTROL_CLEAR_SHIFT,
+ CMDSEQ_CONTROL);
+
+ /* BufferAddress and BufferSize */
+ dpu_be_write(dpu_be, dpu_be->buffer_addr_phy, CMDSEQ_BUFFERADDRESS);
+ dpu_be_write(dpu_be, COMMAND_BUFFER_SIZE / WORD_SIZE,
+ CMDSEQ_BUFFERSIZE);
+}
+
+static struct dprc *
+dpu_be_dprc_get(struct dpu_soc *dpu, int dprc_id)
+{
+ struct dprc *dprc;
+
+ dprc = dprc_lookup_by_phandle(dpu->dev,
+ "fsl,dpr-channels",
+ dprc_id);
+
+ return dprc;
+}
+
+void dpu_be_configure_prefetch(struct dpu_bliteng *dpu_be,
+ u32 width, u32 height,
+ u32 x_offset, u32 y_offset,
+ u32 stride, u32 format, u64 modifier,
+ u64 baddr, u64 uv_addr)
+{
+ struct dprc *dprc;
+ bool dprc_en=false;
+
+ /* Enable DPR, dprc1 is connected to plane0 */
+ dprc = dpu_be->dprc[1];
+
+ /*
+ * Force sync command sequncer in conditions:
+ * 1. tile work with dprc/prg (baddr)
+ * 2. switch tile to linear (!start)
+ */
+ if (!dpu_be->start || baddr) {
+ dpu_be_wait(dpu_be);
+ }
+
+ dpu_be->sync = true;
+
+ if (baddr == 0x0) {
+ if (!dpu_be->start) {
+ dprc_disable(dprc);
+ dpu_be->start = true;
+ }
+ return;
+ }
+
+ if (dpu_be->modifier != modifier && !dpu_be->start) {
+ dprc_disable(dprc);
+ dprc_en = true;
+ }
+
+ dpu_be->modifier = modifier;
+
+ dprc_configure(dprc, 0,
+ width, height,
+ x_offset, y_offset,
+ stride, format, modifier,
+ baddr, uv_addr,
+ dpu_be->start,
+ dpu_be->start,
+ false);
+
+ if (dpu_be->start || dprc_en) {
+ dprc_enable(dprc);
+ }
+
+ dprc_reg_update(dprc);
+
+ dpu_be->start = false;
+}
+EXPORT_SYMBOL(dpu_be_configure_prefetch);
+
+int dpu_bliteng_get_empty_instance(struct dpu_bliteng **dpu_be,
+ struct device *dev)
+{
+ if (!dpu_be || !dev)
+ return -EINVAL;
+
+ *dpu_be = devm_kzalloc(dev, sizeof(struct dpu_bliteng), GFP_KERNEL);
+ if (!(*dpu_be))
+ return -ENOMEM;
+
+ return 0;
+}
+EXPORT_SYMBOL(dpu_bliteng_get_empty_instance);
+
+u32 *dpu_bliteng_get_cmd_list(struct dpu_bliteng *dpu_be)
+{
+ return dpu_be->cmd_list;
+}
+EXPORT_SYMBOL(dpu_bliteng_get_cmd_list);
+
+s32 dpu_bliteng_get_id(struct dpu_bliteng *dpu_be)
+{
+ return dpu_be->id;
+}
+EXPORT_SYMBOL(dpu_bliteng_get_id);
+
+void dpu_bliteng_set_id(struct dpu_bliteng *dpu_be, int id)
+{
+ dpu_be->id = id;
+}
+EXPORT_SYMBOL(dpu_bliteng_set_id);
+
+void dpu_bliteng_set_dev(struct dpu_bliteng *dpu_be, struct device *dev)
+{
+ dpu_be->dev = dev;
+}
+EXPORT_SYMBOL(dpu_bliteng_set_dev);
+
+int dpu_be_get(struct dpu_bliteng *dpu_be)
+{
+ mutex_lock(&dpu_be->mutex);
+
+ return 0;
+}
+EXPORT_SYMBOL(dpu_be_get);
+
+void dpu_be_put(struct dpu_bliteng *dpu_be)
+{
+ mutex_unlock(&dpu_be->mutex);
+}
+EXPORT_SYMBOL(dpu_be_put);
+
+static irqreturn_t dpu_bliteng_irq_handler(int irq, void *dev_id)
+{
+ int i = 0;
+ struct dpu_bliteng *dpu_be = dev_id;
+ struct dpu_be_fence *fence = NULL;
+
+ for (i = 0; i < 4; i++) {
+ if (irq == dpu_be->irq_comctrl_sw[i])
+ break;
+ }
+
+ if (i < 4) {
+ /* Found and release the fence */
+ fence = dpu_be->fence[i];
+ dpu_be->fence[i] = NULL;
+ up(&dpu_be->sema[i]);
+ }
+
+ if (fence) {
+ /* Signal the fence when all triggered */
+ if (atomic_dec_and_test(&fence->refcnt)) {
+ dma_fence_signal(&fence->base);
+ fence->signaled = true;
+ }
+
+ dma_fence_put(&fence->base);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static const char *dpu_be_fence_get_driver_name(struct dma_fence *fence)
+{
+ return "imx-dpu-bliteng";
+}
+
+static const char *dpu_be_fence_get_timeline_name(struct dma_fence *fence)
+{
+ return "dpu-bliteng-fence";
+}
+
+static bool dpu_be_fence_signaled(struct dma_fence *fence)
+{
+ struct dpu_be_fence *bfence = (struct dpu_be_fence *)fence;
+
+ return bfence->signaled;
+}
+
+static bool dpu_be_fence_enable_signaling(struct dma_fence *fence)
+{
+ return !dpu_be_fence_signaled(fence);
+}
+
+static struct dma_fence_ops dpu_be_fence_ops = {
+ .get_driver_name = dpu_be_fence_get_driver_name,
+ .get_timeline_name = dpu_be_fence_get_timeline_name,
+ .enable_signaling = dpu_be_fence_enable_signaling,
+ .signaled = dpu_be_fence_signaled,
+ .release = dma_fence_free,
+};
+
+int dpu_be_get_fence(struct dpu_bliteng *dpu_be)
+{
+ int fd = -1;
+ u64 seqno = 0;
+ struct dpu_be_fence *fence = NULL;
+ struct sync_file *sync = NULL;
+
+ /* Allocate a fence pointer */
+ fence = kzalloc(sizeof(struct dpu_be_fence), GFP_KERNEL);
+ if (!fence)
+ return -1;
+
+ /* Init fence pointer */
+ fence->signaled = false;
+ spin_lock_init(&fence->lock);
+ atomic_set(&fence->refcnt, 0);
+
+ /* Init dma fence base data */
+ seqno = atomic64_inc_return(&dpu_be->seqno);
+ dma_fence_init(&fence->base, &dpu_be_fence_ops, &fence->lock, dpu_be->context, seqno);
+
+ /* Create sync file pointer */
+ sync = sync_file_create(&fence->base);
+ if (!sync) {
+ dev_err(dpu_be->dev, "failed to create fence sync file.\n");
+ goto failed;
+ }
+
+ /* Get the unused file descriptor */
+ fd = get_unused_fd_flags(O_CLOEXEC);
+ if (fd < 0) {
+ dev_err(dpu_be->dev, "failed to get unused file descriptor.\n");
+ goto failed;
+ }
+
+ /* Setup fd to fence sync */
+ fd_install(fd, sync->file);
+
+ return fd;
+
+failed:
+ if (sync)
+ fput(sync->file);
+
+ kfree(fence);
+
+ return -1;
+}
+EXPORT_SYMBOL(dpu_be_get_fence);
+
+static int dpu_be_emit_fence(struct dpu_bliteng *dpu_be, struct dpu_be_fence *fence, bool stall)
+{
+ int i = 0;
+
+ /* Get the available fence index with spin-lock */
+ spin_lock(&dpu_be->lock);
+ i = dpu_be->next_fence_idx++;
+ dpu_be->next_fence_idx &= 0x3;
+ spin_unlock(&dpu_be->lock);
+
+ /*Acquire fence semaphore released by irq handler */
+ down(&dpu_be->sema[i]);
+
+ WARN_ON(dpu_be->fence[i]);
+ dpu_be->fence[i] = fence;
+
+ dpu_cs_wait_fifo_space(dpu_be);
+
+ /* Write comctrl interrupt PRESET to command sequencer */
+ dpu_be_write(dpu_be, 0x14000001, CMDSEQ_HIF);
+ dpu_be_write(dpu_be, COMCTRL_USERINTERRUPTPRESET1, CMDSEQ_HIF);
+ dpu_be_write(dpu_be, 1 << (i + (IRQ_COMCTRL_SW0 & 0x1F)), CMDSEQ_HIF);
+
+ /*Stall until semaphore released */
+ if (stall) {
+ down(&dpu_be->sema[i]);
+ up(&dpu_be->sema[i]);
+ }
+
+ return 0;
+}
+
+int dpu_be_set_fence(struct dpu_bliteng *dpu_be, int fd)
+{
+ struct dpu_be_fence *fence = NULL;
+
+ /* Retrieve fence pointer from sync file descriptor */
+ fence = (struct dpu_be_fence *)sync_file_get_fence(fd);
+ if (!fence) {
+ dev_err(dpu_be->dev, "failed to get fence pointer.\n");
+ return -1;
+ }
+
+ /* Setup the fence and active it asynchronously */
+ dpu_be_emit_fence(dpu_be, fence, false);
+
+ /* Increase fence and base reference */
+ atomic_inc(&fence->refcnt);
+ dma_fence_get(&fence->base);
+
+ return 0;
+}
+EXPORT_SYMBOL(dpu_be_set_fence);
+
+int dpu_be_blit(struct dpu_bliteng *dpu_be,
+ u32 *cmdlist, u32 cmdnum)
+{
+ int i;
+
+ if (cmdnum > CMDSEQ_FIFO_SPACE_THRESHOLD) {
+ dev_err(dpu_be->dev, "dpu blit cmdnum[%d] should be less than %d !\n",
+ cmdnum, CMDSEQ_FIFO_SPACE_THRESHOLD);
+ return -EINVAL;
+ }
+ dpu_cs_wait_fifo_space(dpu_be);
+
+ for (i = 0; i < cmdnum; i++)
+ dpu_be_write(dpu_be, cmdlist[i], CMDSEQ_HIF);
+
+ return 0;
+}
+EXPORT_SYMBOL(dpu_be_blit);
+
+void dpu_be_wait(struct dpu_bliteng *dpu_be)
+{
+ if (!dpu_be->sync) return;
+
+ /* Emit empty fence and stall until signaled */
+ dpu_be_emit_fence(dpu_be, NULL, true);
+
+ dpu_be->sync = false;
+}
+EXPORT_SYMBOL(dpu_be_wait);
+
+static void dpu_be_init_units(struct dpu_bliteng *dpu_be)
+{
+ u32 staticcontrol;
+ u32 pixengcfg_unit_dynamic;
+
+ staticcontrol =
+ 1 << FETCHDECODE9_STATICCONTROL_SHDEN_SHIFT |
+ 0 << FETCHDECODE9_STATICCONTROL_BASEADDRESSAUTOUPDATE_SHIFT |
+ FETCHDECODE9_STATICCONTROL_RESET_VALUE;
+ dpu_be_write(dpu_be, staticcontrol, FETCHDECODE9_STATICCONTROL);
+
+ staticcontrol =
+ 1 << FETCHWARP9_STATICCONTROL_SHDEN_SHIFT |
+ 0 << FETCHWARP9_STATICCONTROL_BASEADDRESSAUTOUPDATE_SHIFT |
+ FETCHWARP9_STATICCONTROL_RESET_VALUE;
+ dpu_be_write(dpu_be, staticcontrol, FETCHWARP9_STATICCONTROL);
+
+ staticcontrol =
+ 1 << FETCHECO9_STATICCONTROL_SHDEN_SHIFT |
+ 0 << FETCHECO9_STATICCONTROL_BASEADDRESSAUTOUPDATE_SHIFT |
+ FETCHECO9_STATICCONTROL_RESET_VALUE;
+ dpu_be_write(dpu_be, staticcontrol, FETCHECO9_STATICCONTROL);
+
+ staticcontrol =
+ 1 << HSCALER9_STATICCONTROL_SHDEN_SHIFT |
+ HSCALER9_STATICCONTROL_RESET_VALUE;
+ dpu_be_write(dpu_be, staticcontrol, HSCALER9_STATICCONTROL);
+
+ staticcontrol =
+ 1 << VSCALER9_STATICCONTROL_SHDEN_SHIFT |
+ VSCALER9_STATICCONTROL_RESET_VALUE;
+ dpu_be_write(dpu_be, staticcontrol, VSCALER9_STATICCONTROL);
+
+ staticcontrol =
+ 1 << ROP9_STATICCONTROL_SHDEN_SHIFT |
+ ROP9_STATICCONTROL_RESET_VALUE;
+ dpu_be_write(dpu_be, staticcontrol, ROP9_STATICCONTROL);
+
+ staticcontrol =
+ 1 << MATRIX9_STATICCONTROL_SHDEN_SHIFT |
+ MATRIX9_STATICCONTROL_RESET_VALUE;
+ dpu_be_write(dpu_be, staticcontrol, MATRIX9_STATICCONTROL);
+
+ staticcontrol =
+ 1 << BLITBLEND9_STATICCONTROL_SHDEN_SHIFT |
+ BLITBLEND9_STATICCONTROL_RESET_VALUE;
+ dpu_be_write(dpu_be, staticcontrol, BLITBLEND9_STATICCONTROL);
+
+ staticcontrol =
+ 1 << STORE9_STATICCONTROL_SHDEN_SHIFT |
+ 0 << STORE9_STATICCONTROL_BASEADDRESSAUTOUPDATE_SHIFT |
+ STORE9_STATICCONTROL_RESET_VALUE;
+ dpu_be_write(dpu_be, staticcontrol, STORE9_STATICCONTROL);
+
+ /* Safety_Pixengcfg Dynamic */
+ pixengcfg_unit_dynamic =
+ PIXENGCFG_CLKEN__AUTOMATIC << PIXENGCFG_CLKEN_SHIFT |
+ PIXENGCFG_FETCHDECODE9_DYNAMIC_RESET_VALUE;
+ dpu_be_write(dpu_be, pixengcfg_unit_dynamic,
+ PIXENGCFG_FETCHDECODE9_DYNAMIC);
+
+ pixengcfg_unit_dynamic =
+ PIXENGCFG_CLKEN__AUTOMATIC << PIXENGCFG_CLKEN_SHIFT |
+ PIXENGCFG_FETCHWARP9_DYNAMIC_RESET_VALUE;
+ dpu_be_write(dpu_be, pixengcfg_unit_dynamic,
+ PIXENGCFG_FETCHWARP9_DYNAMIC);
+
+ pixengcfg_unit_dynamic =
+ PIXENGCFG_CLKEN__AUTOMATIC << PIXENGCFG_CLKEN_SHIFT |
+ PIXENGCFG_ROP9_DYNAMIC_RESET_VALUE;
+ dpu_be_write(dpu_be, pixengcfg_unit_dynamic,
+ PIXENGCFG_ROP9_DYNAMIC);
+
+ pixengcfg_unit_dynamic =
+ PIXENGCFG_CLKEN__AUTOMATIC << PIXENGCFG_CLKEN_SHIFT |
+ PIXENGCFG_MATRIX9_DYNAMIC_RESET_VALUE;
+ dpu_be_write(dpu_be, pixengcfg_unit_dynamic,
+ PIXENGCFG_MATRIX9_DYNAMIC);
+
+ pixengcfg_unit_dynamic =
+ PIXENGCFG_CLKEN__AUTOMATIC << PIXENGCFG_CLKEN_SHIFT |
+ PIXENGCFG_HSCALER9_DYNAMIC_RESET_VALUE;
+ dpu_be_write(dpu_be, pixengcfg_unit_dynamic,
+ PIXENGCFG_HSCALER9_DYNAMIC);
+
+ pixengcfg_unit_dynamic =
+ PIXENGCFG_CLKEN__AUTOMATIC << PIXENGCFG_CLKEN_SHIFT |
+ PIXENGCFG_VSCALER9_DYNAMIC_RESET_VALUE;
+ dpu_be_write(dpu_be, pixengcfg_unit_dynamic,
+ PIXENGCFG_VSCALER9_DYNAMIC);
+
+ pixengcfg_unit_dynamic =
+ PIXENGCFG_CLKEN__AUTOMATIC << PIXENGCFG_CLKEN_SHIFT |
+ PIXENGCFG_BLITBLEND9_DYNAMIC_RESET_VALUE;
+ dpu_be_write(dpu_be, pixengcfg_unit_dynamic,
+ PIXENGCFG_BLITBLEND9_DYNAMIC);
+}
+
+int dpu_bliteng_init(struct dpu_bliteng *dpu_bliteng)
+{
+ struct dpu_soc *dpu = dev_get_drvdata(dpu_bliteng->dev->parent);
+ struct platform_device *dpu_pdev =
+ container_of(dpu->dev, struct platform_device, dev);
+ struct resource *res;
+ unsigned long dpu_base;
+ void __iomem *base;
+ u32 *cmd_list;
+ int ret;
+ int i, virq, sw_irqs[4] = {IRQ_COMCTRL_SW0, IRQ_COMCTRL_SW1,
+ IRQ_COMCTRL_SW2, IRQ_COMCTRL_SW3};
+
+ cmd_list = kzalloc(sizeof(*cmd_list) * CMDSEQ_FIFO_SPACE_THRESHOLD,
+ GFP_KERNEL);
+ if (!cmd_list)
+ return -ENOMEM;
+ dpu_bliteng->cmd_list = cmd_list;
+
+ res = platform_get_resource(dpu_pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENODEV;
+ dpu_base = res->start;
+
+ /* remap with bigger size */
+ base = devm_ioremap(dpu->dev, dpu_base, 64*SZ_1K);
+ dpu_bliteng->base = base;
+ dpu_bliteng->dpu = dpu;
+
+ mutex_init(&dpu_bliteng->mutex);
+
+ /* Init the uints used by blit engine */
+ /* Maybe this should be in dpu-common.c */
+ dpu_be_init_units(dpu_bliteng);
+
+ /* Init for command sequencer */
+ ret = dpu_cs_alloc_command_buffer(dpu_bliteng);
+ if (ret)
+ return ret;
+
+ dpu_cs_static_setup(dpu_bliteng);
+
+ /*Request general SW interrupts to implement DPU fence */
+ for (i = 0; i < 4; i++) {
+ virq = dpu_map_irq(dpu, sw_irqs[i]);
+ dpu_bliteng->irq_comctrl_sw[i] = virq;
+
+ irq_set_status_flags(virq, IRQ_DISABLE_UNLAZY);
+ ret = devm_request_irq(dpu->dev, virq, dpu_bliteng_irq_handler, 0,
+ "imx-dpu-bliteng", dpu_bliteng);
+ if (ret < 0) {
+ dev_err(dpu->dev, "irq_comctrl_sw%d irq request failed with %d.\n", i, ret);
+ return ret;
+ }
+
+ sema_init(&dpu_bliteng->sema[i], 1);
+ }
+
+ /* Init fence context and seqno */
+ dpu_bliteng->context = dma_fence_context_alloc(1);
+ atomic64_set(&dpu_bliteng->seqno, 0);
+ spin_lock_init(&dpu_bliteng->lock);
+
+ /* DPR, each blit engine has two dprc, 0 & 1 */
+ dpu_bliteng->dprc[0] = dpu_be_dprc_get(dpu, 0);
+ dpu_bliteng->dprc[1] = dpu_be_dprc_get(dpu, 1);
+
+ dprc_disable(dpu_bliteng->dprc[0]);
+ dprc_disable(dpu_bliteng->dprc[1]);
+
+ dpu_bliteng->start = true;
+ dpu_bliteng->sync = false;
+
+ dpu_bliteng->modifier = 0;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(dpu_bliteng_init);
+
+void dpu_bliteng_fini(struct dpu_bliteng *dpu_bliteng)
+{
+ int i;
+
+ /* LockUnlock and LockUnlockHIF */
+ dpu_be_write(dpu_bliteng, CMDSEQ_LOCKUNLOCKHIF_LOCKUNLOCKHIF__LOCK_KEY,
+ CMDSEQ_LOCKUNLOCKHIF);
+ dpu_be_write(dpu_bliteng, CMDSEQ_LOCKUNLOCK_LOCKUNLOCK__LOCK_KEY,
+ CMDSEQ_LOCKUNLOCK);
+
+ kfree(dpu_bliteng->cmd_list);
+
+ for (i = 0; i < 4; i++)
+ free_irq(dpu_bliteng->irq_comctrl_sw[i], dpu_bliteng);
+
+ if (dpu_bliteng->buffer_addr_virt)
+ free_pages_exact(dpu_bliteng->buffer_addr_virt,
+ COMMAND_BUFFER_SIZE);
+}
+EXPORT_SYMBOL_GPL(dpu_bliteng_fini);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("NXP Semiconductor");
+MODULE_DESCRIPTION("i.MX DPU BLITENG");
+MODULE_ALIAS("platform:imx-dpu-bliteng");
diff --git a/drivers/gpu/imx/dpu-blit/dpu-blit.h b/drivers/gpu/imx/dpu-blit/dpu-blit.h
new file mode 100644
index 000000000000..fd8cc4908345
--- /dev/null
+++ b/drivers/gpu/imx/dpu-blit/dpu-blit.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2016 Freescale Semiconductor, Inc.
+ * Copyright 2017-2018,2022 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#ifndef __DPU_BLIT_H__
+#define __DPU_BLIT_H__
+
+#include <linux/file.h>
+#include <linux/fdtable.h>
+#include <linux/sync_file.h>
+#include <linux/dma-fence.h>
+#include <linux/dma-fence-array.h>
+
+#define COMMAND_BUFFER_SIZE 65536 /* up to 64k bytes */
+#define CMDSEQ_FIFO_SPACE_THRESHOLD 192
+#define WORD_SIZE 4
+
+struct dpu_be_fence {
+ struct dma_fence base;
+ spinlock_t lock;
+ atomic_t refcnt;
+ bool signaled;
+};
+
+struct dpu_bliteng {
+ struct device *dev;
+ void __iomem *base;
+ s32 id;
+ struct mutex mutex;
+
+ s32 irq_comctrl_sw[4];
+
+ struct semaphore sema[4];
+ struct dpu_be_fence *fence[4];
+ s32 next_fence_idx;
+
+ atomic64_t seqno;
+ spinlock_t lock;
+ u64 context;
+
+ void *buffer_addr_virt;
+ u32 buffer_addr_phy;
+
+ u32 *cmd_list;
+
+ struct dpu_soc *dpu;
+
+ struct dprc *dprc[2];
+
+ bool start;
+ bool sync;
+
+ u64 modifier;
+};
+
+#endif
diff --git a/drivers/gpu/imx/dpu/Kconfig b/drivers/gpu/imx/dpu/Kconfig
new file mode 100644
index 000000000000..d62891118907
--- /dev/null
+++ b/drivers/gpu/imx/dpu/Kconfig
@@ -0,0 +1,8 @@
+config IMX_DPU_CORE
+ tristate "i.MX DPU core support"
+ depends on ARCH_MXC
+ select GENERIC_IRQ_CHIP
+ help
+ Choose this if you have a Freescale i.MX8QM or i.MX8QXP system and
+ want to use the Display Processing Unit. This option only enables
+ DPU base support.
diff --git a/drivers/gpu/imx/dpu/Makefile b/drivers/gpu/imx/dpu/Makefile
new file mode 100644
index 000000000000..5b568e998d7e
--- /dev/null
+++ b/drivers/gpu/imx/dpu/Makefile
@@ -0,0 +1,8 @@
+obj-$(CONFIG_IMX_DPU_CORE) += imx-dpu-core.o
+
+imx-dpu-core-objs := dpu-common.o dpu-constframe.o dpu-disengcfg.o \
+ dpu-extdst.o dpu-fetchdecode.o dpu-fetcheco.o \
+ dpu-fetchlayer.o dpu-fetchwarp.o dpu-fetchunit.o \
+ dpu-framegen.o dpu-hscaler.o dpu-layerblend.o \
+ dpu-sc-misc.o dpu-signature.o dpu-store.o dpu-tcon.o \
+ dpu-vscaler.o
diff --git a/drivers/gpu/imx/dpu/dpu-common.c b/drivers/gpu/imx/dpu/dpu-common.c
new file mode 100644
index 000000000000..bda0dba8fede
--- /dev/null
+++ b/drivers/gpu/imx/dpu/dpu-common.c
@@ -0,0 +1,1361 @@
+/*
+ * Copyright (C) 2016 Freescale Semiconductor, Inc.
+ * Copyright 2017-2020,2022 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <linux/clk.h>
+#include <linux/fb.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/irqdomain.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+#include <video/dpu.h>
+#include <video/imx8-pc.h>
+#include <video/imx8-prefetch.h>
+#include "dpu-prv.h"
+
+#define IMX_DPU_BLITENG_NAME "imx-drm-dpu-bliteng"
+
+static bool display_plane_video_proc = true;
+module_param(display_plane_video_proc, bool, 0444);
+MODULE_PARM_DESC(display_plane_video_proc,
+ "Enable video processing for display [default=true]");
+
+#define DPU_CM_REG_DEFINE1(name1, name2) \
+static inline u32 name1(const struct cm_reg_ofs *ofs) \
+{ \
+ return ofs->name2; \
+}
+
+#define DPU_CM_REG_DEFINE2(name1, name2) \
+static inline u32 name1(const struct cm_reg_ofs *ofs, \
+ unsigned int n) \
+{ \
+ return ofs->name2 + (4 * n); \
+}
+
+DPU_CM_REG_DEFINE1(LOCKUNLOCK, lockunlock);
+DPU_CM_REG_DEFINE1(LOCKSTATUS, lockstatus);
+DPU_CM_REG_DEFINE2(USERINTERRUPTMASK, userinterruptmask);
+DPU_CM_REG_DEFINE2(INTERRUPTENABLE, interruptenable);
+DPU_CM_REG_DEFINE2(INTERRUPTPRESET, interruptpreset);
+DPU_CM_REG_DEFINE2(INTERRUPTCLEAR, interruptclear);
+DPU_CM_REG_DEFINE2(INTERRUPTSTATUS, interruptstatus);
+DPU_CM_REG_DEFINE2(USERINTERRUPTENABLE, userinterruptenable);
+DPU_CM_REG_DEFINE2(USERINTERRUPTPRESET, userinterruptpreset);
+DPU_CM_REG_DEFINE2(USERINTERRUPTCLEAR, userinterruptclear);
+DPU_CM_REG_DEFINE2(USERINTERRUPTSTATUS, userinterruptstatus);
+DPU_CM_REG_DEFINE1(GENERALPURPOSE, generalpurpose);
+
+static inline u32 dpu_cm_read(struct dpu_soc *dpu, unsigned int offset)
+{
+ return readl(dpu->cm_reg + offset);
+}
+
+static inline void dpu_cm_write(struct dpu_soc *dpu,
+ unsigned int offset, u32 value)
+{
+ writel(value, dpu->cm_reg + offset);
+}
+
+/* Constant Frame Unit */
+static const unsigned long cf_ofss[] = {0x4400, 0x5400, 0x4c00, 0x5c00};
+static const unsigned long cf_pec_ofss[] = {0x960, 0x9e0, 0x9a0, 0xa20};
+
+/* Display Engine Configuration Unit */
+static const unsigned long dec_ofss[] = {0xb400, 0xb420};
+
+/* External Destination Unit */
+static const unsigned long ed_ofss[] = {0x4800, 0x5800, 0x5000, 0x6000};
+static const unsigned long ed_pec_ofss[] = {0x980, 0xa00, 0x9c0, 0xa40};
+
+/* Fetch Decode Unit */
+static const unsigned long fd_ofss[] = {0x6c00, 0x7800};
+static const unsigned long fd_pec_ofss[] = {0xa80, 0xaa0};
+
+/* Fetch ECO Unit */
+static const unsigned long fe_ofss[] = {0x7400, 0x8000, 0x6800, 0x1c00};
+static const unsigned long fe_pec_ofss[] = {0xa90, 0xab0, 0xa70, 0x850};
+
+/* Frame Generator Unit */
+static const unsigned long fg_ofss[] = {0xb800, 0xd400};
+
+/* Fetch Layer Unit */
+static const unsigned long fl_ofss[] = {0x8400};
+static const unsigned long fl_pec_ofss[] = {0xac0};
+
+/* Fetch Warp Unit */
+static const unsigned long fw_ofss[] = {0x6400};
+static const unsigned long fw_pec_ofss[] = {0xa60};
+
+/* Horizontal Scaler Unit */
+static const unsigned long hs_ofss[] = {0x9000, 0x9c00, 0x3000};
+static const unsigned long hs_pec_ofss[] = {0xb00, 0xb60, 0x8c0};
+
+/* Layer Blend Unit */
+static const unsigned long lb_ofss[] = {0xa400, 0xa800, 0xac00, 0xb000};
+static const unsigned long lb_pec_ofss[] = {0xba0, 0xbc0, 0xbe0, 0xc00};
+
+/* Signature Unit */
+static const unsigned long sig_ofss[] = {0xd000, 0xec00};
+
+/* Store Unit */
+static const unsigned long st_ofss[] = {0x4000};
+static const unsigned long st_pec_ofss[] = {0x940};
+
+/* Timing Controller Unit */
+static const unsigned long tcon_ofss[] = {0xcc00, 0xe800};
+
+/* Vertical Scaler Unit */
+static const unsigned long vs_ofss[] = {0x9400, 0xa000, 0x3400};
+static const unsigned long vs_pec_ofss[] = {0xb20, 0xb80, 0x8e0};
+
+static const struct dpu_unit _cfs = {
+ .name = "ConstFrame",
+ .num = ARRAY_SIZE(cf_ids),
+ .ids = cf_ids,
+ .pec_ofss = cf_pec_ofss,
+ .ofss = cf_ofss,
+};
+
+static const struct dpu_unit _decs = {
+ .name = "DisEngCfg",
+ .num = ARRAY_SIZE(dec_ids),
+ .ids = dec_ids,
+ .pec_ofss = NULL,
+ .ofss = dec_ofss,
+};
+
+static const struct dpu_unit _eds = {
+ .name = "ExtDst",
+ .num = ARRAY_SIZE(ed_ids),
+ .ids = ed_ids,
+ .pec_ofss = ed_pec_ofss,
+ .ofss = ed_ofss,
+};
+
+static const struct dpu_unit _fds = {
+ .name = "FetchDecode",
+ .num = ARRAY_SIZE(fd_ids),
+ .ids = fd_ids,
+ .pec_ofss = fd_pec_ofss,
+ .ofss = fd_ofss,
+ .dprc_ids = fd_dprc_ids,
+};
+
+static const struct dpu_unit _fes = {
+ .name = "FetchECO",
+ .num = ARRAY_SIZE(fe_ids),
+ .ids = fe_ids,
+ .pec_ofss = fe_pec_ofss,
+ .ofss = fe_ofss,
+};
+
+static const struct dpu_unit _fgs = {
+ .name = "FrameGen",
+ .num = ARRAY_SIZE(fg_ids),
+ .ids = fg_ids,
+ .pec_ofss = NULL,
+ .ofss = fg_ofss,
+};
+
+static const struct dpu_unit _fls = {
+ .name = "FetchLayer",
+ .num = ARRAY_SIZE(fl_ids),
+ .ids = fl_ids,
+ .pec_ofss = fl_pec_ofss,
+ .ofss = fl_ofss,
+ .dprc_ids = fl_dprc_ids,
+};
+
+static const struct dpu_unit _fws = {
+ .name = "FetchWarp",
+ .num = ARRAY_SIZE(fw_ids),
+ .ids = fw_ids,
+ .pec_ofss = fw_pec_ofss,
+ .ofss = fw_ofss,
+ .dprc_ids = fw_dprc_ids,
+};
+
+static const struct dpu_unit _hss = {
+ .name = "HScaler",
+ .num = ARRAY_SIZE(hs_ids),
+ .ids = hs_ids,
+ .pec_ofss = hs_pec_ofss,
+ .ofss = hs_ofss,
+};
+
+static const struct dpu_unit _lbs = {
+ .name = "LayerBlend",
+ .num = ARRAY_SIZE(lb_ids),
+ .ids = lb_ids,
+ .pec_ofss = lb_pec_ofss,
+ .ofss = lb_ofss,
+};
+
+static const struct dpu_unit _sigs = {
+ .name = "Signature",
+ .num = ARRAY_SIZE(sig_ids),
+ .ids = sig_ids,
+ .pec_ofss = NULL,
+ .ofss = sig_ofss,
+};
+
+static const struct dpu_unit _sts = {
+ .name = "Store",
+ .num = ARRAY_SIZE(st_ids),
+ .ids = st_ids,
+ .pec_ofss = st_pec_ofss,
+ .ofss = st_ofss,
+};
+
+static const struct dpu_unit _tcons = {
+ .name = "TCon",
+ .num = ARRAY_SIZE(tcon_ids),
+ .ids = tcon_ids,
+ .pec_ofss = NULL,
+ .ofss = tcon_ofss,
+};
+
+static const struct dpu_unit _vss = {
+ .name = "VScaler",
+ .num = ARRAY_SIZE(vs_ids),
+ .ids = vs_ids,
+ .pec_ofss = vs_pec_ofss,
+ .ofss = vs_ofss,
+};
+
+static const struct cm_reg_ofs _cm_reg_ofs = {
+ .ipidentifier = 0,
+ .lockunlock = 0x40,
+ .lockstatus = 0x44,
+ .userinterruptmask = 0x48,
+ .interruptenable = 0x50,
+ .interruptpreset = 0x58,
+ .interruptclear = 0x60,
+ .interruptstatus = 0x68,
+ .userinterruptenable = 0x80,
+ .userinterruptpreset = 0x88,
+ .userinterruptclear = 0x90,
+ .userinterruptstatus = 0x98,
+ .generalpurpose = 0x100,
+};
+
+static const unsigned long unused_irq[] = {0x00000000, 0xfffe0008};
+
+static const struct dpu_data dpu_data_qxp = {
+ .cm_ofs = 0x0,
+ .cfs = &_cfs,
+ .decs = &_decs,
+ .eds = &_eds,
+ .fds = &_fds,
+ .fes = &_fes,
+ .fgs = &_fgs,
+ .fls = &_fls,
+ .fws = &_fws,
+ .hss = &_hss,
+ .lbs = &_lbs,
+ .sigs = &_sigs,
+ .sts = &_sts,
+ .tcons = &_tcons,
+ .vss = &_vss,
+ .cm_reg_ofs = &_cm_reg_ofs,
+ .unused_irq = unused_irq,
+ .plane_src_mask = DPU_PLANE_SRC_FL0_ID | DPU_PLANE_SRC_FW2_ID |
+ DPU_PLANE_SRC_FD0_ID | DPU_PLANE_SRC_FD1_ID,
+ .has_dual_ldb = true,
+ .syncmode_min_prate = UINT_MAX, /* pc is unused */
+ .singlemode_max_width = UINT_MAX, /* pc is unused */
+};
+
+static const struct dpu_data dpu_data_qm = {
+ .cm_ofs = 0x0,
+ .cfs = &_cfs,
+ .decs = &_decs,
+ .eds = &_eds,
+ .fds = &_fds,
+ .fes = &_fes,
+ .fgs = &_fgs,
+ .fls = &_fls,
+ .fws = &_fws,
+ .hss = &_hss,
+ .lbs = &_lbs,
+ .sigs = &_sigs,
+ .sts = &_sts,
+ .tcons = &_tcons,
+ .vss = &_vss,
+ .cm_reg_ofs = &_cm_reg_ofs,
+ .unused_irq = unused_irq,
+ .plane_src_mask = DPU_PLANE_SRC_FL0_ID | DPU_PLANE_SRC_FW2_ID |
+ DPU_PLANE_SRC_FD0_ID | DPU_PLANE_SRC_FD1_ID,
+ .has_dual_ldb = false,
+ .syncmode_min_prate = 300000,
+ .singlemode_max_width = 2560,
+ .master_stream_id = 1,
+};
+
+static const struct of_device_id dpu_dt_ids[] = {
+ {
+ .compatible = "fsl,imx8qxp-dpu",
+ .data = &dpu_data_qxp,
+ }, {
+ .compatible = "fsl,imx8qm-dpu",
+ .data = &dpu_data_qm,
+ }, {
+ /* sentinel */
+ }
+};
+MODULE_DEVICE_TABLE(of, dpu_dt_ids);
+
+unsigned int dpu_get_syncmode_min_prate(struct dpu_soc *dpu)
+{
+ return dpu->data->syncmode_min_prate;
+}
+EXPORT_SYMBOL_GPL(dpu_get_syncmode_min_prate);
+
+unsigned int dpu_get_singlemode_max_width(struct dpu_soc *dpu)
+{
+ return dpu->data->singlemode_max_width;
+}
+EXPORT_SYMBOL_GPL(dpu_get_singlemode_max_width);
+
+unsigned int dpu_get_master_stream_id(struct dpu_soc *dpu)
+{
+ return dpu->data->master_stream_id;
+}
+EXPORT_SYMBOL_GPL(dpu_get_master_stream_id);
+
+bool dpu_vproc_has_fetcheco_cap(u32 cap_mask)
+{
+ return !!(cap_mask & DPU_VPROC_CAP_FETCHECO);
+}
+EXPORT_SYMBOL_GPL(dpu_vproc_has_fetcheco_cap);
+
+bool dpu_vproc_has_hscale_cap(u32 cap_mask)
+{
+ return !!(cap_mask & DPU_VPROC_CAP_HSCALE);
+}
+EXPORT_SYMBOL_GPL(dpu_vproc_has_hscale_cap);
+
+bool dpu_vproc_has_vscale_cap(u32 cap_mask)
+{
+ return !!(cap_mask & DPU_VPROC_CAP_VSCALE);
+}
+EXPORT_SYMBOL_GPL(dpu_vproc_has_vscale_cap);
+
+u32 dpu_vproc_get_fetcheco_cap(u32 cap_mask)
+{
+ return cap_mask & DPU_VPROC_CAP_FETCHECO;
+}
+EXPORT_SYMBOL_GPL(dpu_vproc_get_fetcheco_cap);
+
+u32 dpu_vproc_get_hscale_cap(u32 cap_mask)
+{
+ return cap_mask & DPU_VPROC_CAP_HSCALE;
+}
+EXPORT_SYMBOL_GPL(dpu_vproc_get_hscale_cap);
+
+u32 dpu_vproc_get_vscale_cap(u32 cap_mask)
+{
+ return cap_mask & DPU_VPROC_CAP_VSCALE;
+}
+EXPORT_SYMBOL_GPL(dpu_vproc_get_vscale_cap);
+
+int dpu_format_horz_chroma_subsampling(u32 format)
+{
+ switch (format) {
+ case DRM_FORMAT_YUYV:
+ case DRM_FORMAT_UYVY:
+ case DRM_FORMAT_NV12:
+ case DRM_FORMAT_NV21:
+ case DRM_FORMAT_NV16:
+ case DRM_FORMAT_NV61:
+ return 2;
+ default:
+ return 1;
+ }
+}
+
+int dpu_format_vert_chroma_subsampling(u32 format)
+{
+ switch (format) {
+ case DRM_FORMAT_NV12:
+ case DRM_FORMAT_NV21:
+ return 2;
+ default:
+ return 1;
+ }
+}
+
+int dpu_format_num_planes(u32 format)
+{
+ switch (format) {
+ case DRM_FORMAT_NV12:
+ case DRM_FORMAT_NV21:
+ case DRM_FORMAT_NV16:
+ case DRM_FORMAT_NV61:
+ case DRM_FORMAT_NV24:
+ case DRM_FORMAT_NV42:
+ return 2;
+ default:
+ return 1;
+ }
+}
+
+int dpu_format_plane_width(int width, u32 format, int plane)
+{
+ if (plane >= dpu_format_num_planes(format))
+ return 0;
+
+ if (plane == 0)
+ return width;
+
+ return width / dpu_format_horz_chroma_subsampling(format);
+}
+
+int dpu_format_plane_height(int height, u32 format, int plane)
+{
+ if (plane >= dpu_format_num_planes(format))
+ return 0;
+
+ if (plane == 0)
+ return height;
+
+ return height / dpu_format_vert_chroma_subsampling(format);
+}
+
+static void dpu_detach_pm_domains(struct dpu_soc *dpu)
+{
+ if (dpu->pd_pll1_link && !IS_ERR(dpu->pd_pll1_link))
+ device_link_del(dpu->pd_pll1_link);
+ if (dpu->pd_pll1_dev && !IS_ERR(dpu->pd_pll1_dev))
+ dev_pm_domain_detach(dpu->pd_pll1_dev, true);
+
+ if (dpu->pd_pll0_link && !IS_ERR(dpu->pd_pll0_link))
+ device_link_del(dpu->pd_pll0_link);
+ if (dpu->pd_pll0_dev && !IS_ERR(dpu->pd_pll0_dev))
+ dev_pm_domain_detach(dpu->pd_pll0_dev, true);
+
+ if (dpu->pd_dc_link && !IS_ERR(dpu->pd_dc_link))
+ device_link_del(dpu->pd_dc_link);
+ if (dpu->pd_dc_dev && !IS_ERR(dpu->pd_dc_dev))
+ dev_pm_domain_detach(dpu->pd_dc_dev, true);
+
+ dpu->pd_dc_dev = NULL;
+ dpu->pd_dc_link = NULL;
+ dpu->pd_pll0_dev = NULL;
+ dpu->pd_pll0_link = NULL;
+ dpu->pd_pll1_dev = NULL;
+ dpu->pd_pll1_link = NULL;
+}
+
+static int dpu_attach_pm_domains(struct dpu_soc *dpu)
+{
+ struct device *dev = dpu->dev;
+ u32 flags = DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME | DL_FLAG_RPM_ACTIVE;
+ int ret = 0;
+
+ dpu->pd_dc_dev = dev_pm_domain_attach_by_name(dev, "dc");
+ if (IS_ERR(dpu->pd_dc_dev)) {
+ ret = PTR_ERR(dpu->pd_dc_dev);
+ dev_err(dev, "Failed to attach dc pd dev: %d\n", ret);
+ goto fail;
+ }
+ dpu->pd_dc_link = device_link_add(dev, dpu->pd_dc_dev, flags);
+ if (IS_ERR(dpu->pd_dc_link)) {
+ ret = PTR_ERR(dpu->pd_dc_link);
+ dev_err(dev, "Failed to add device link to dc pd dev: %d\n",
+ ret);
+ goto fail;
+ }
+
+ dpu->pd_pll0_dev = dev_pm_domain_attach_by_name(dev, "pll0");
+ if (IS_ERR(dpu->pd_pll0_dev)) {
+ ret = PTR_ERR(dpu->pd_pll0_dev);
+ dev_err(dev, "Failed to attach pll0 pd dev: %d\n", ret);
+ goto fail;
+ }
+ dpu->pd_pll0_link = device_link_add(dev, dpu->pd_pll0_dev, flags);
+ if (IS_ERR(dpu->pd_pll0_link)) {
+ ret = PTR_ERR(dpu->pd_pll0_link);
+ dev_err(dev, "Failed to add device link to pll0 pd dev: %d\n",
+ ret);
+ goto fail;
+ }
+
+ dpu->pd_pll1_dev = dev_pm_domain_attach_by_name(dev, "pll1");
+ if (IS_ERR(dpu->pd_pll1_dev)) {
+ ret = PTR_ERR(dpu->pd_pll1_dev);
+ dev_err(dev, "Failed to attach pll0 pd dev: %d\n", ret);
+ goto fail;
+ }
+ dpu->pd_pll1_link = device_link_add(dev, dpu->pd_pll1_dev, flags);
+ if (IS_ERR(dpu->pd_pll1_link)) {
+ ret = PTR_ERR(dpu->pd_pll1_link);
+ dev_err(dev, "Failed to add device link to pll1 pd dev: %d\n",
+ ret);
+ goto fail;
+ }
+
+ return ret;
+fail:
+ dpu_detach_pm_domains(dpu);
+ return ret;
+}
+
+#define DPU_UNITS_ADDR_DBG(unit) \
+{ \
+ const struct dpu_unit *us = data->unit##s; \
+ int i; \
+ for (i = 0; i < us->num; i++) { \
+ if (us->pec_ofss) { \
+ dev_dbg(&pdev->dev, "%s%d: pixengcfg @ 0x%08lx,"\
+ " unit @ 0x%08lx\n", us->name, \
+ us->ids[i], \
+ dpu_base + us->pec_ofss[i], \
+ dpu_base + us->ofss[i]); \
+ } else { \
+ dev_dbg(&pdev->dev, \
+ "%s%d: unit @ 0x%08lx\n", us->name, \
+ us->ids[i], dpu_base + us->ofss[i]); \
+ } \
+ } \
+}
+
+static void dpu_units_addr_dbg(struct dpu_soc *dpu,
+ struct platform_device *pdev, unsigned long dpu_base)
+{
+ const struct dpu_data *data = dpu->data;
+
+ dev_dbg(dpu->dev, "Common: 0x%08lx\n", dpu_base + data->cm_ofs);
+ DPU_UNITS_ADDR_DBG(cf);
+ DPU_UNITS_ADDR_DBG(dec);
+ DPU_UNITS_ADDR_DBG(ed);
+ DPU_UNITS_ADDR_DBG(fd);
+ DPU_UNITS_ADDR_DBG(fe);
+ DPU_UNITS_ADDR_DBG(fg);
+ DPU_UNITS_ADDR_DBG(fl);
+ DPU_UNITS_ADDR_DBG(fw);
+ DPU_UNITS_ADDR_DBG(hs);
+ DPU_UNITS_ADDR_DBG(lb);
+ DPU_UNITS_ADDR_DBG(sig);
+ DPU_UNITS_ADDR_DBG(st);
+ DPU_UNITS_ADDR_DBG(tcon);
+ DPU_UNITS_ADDR_DBG(vs);
+}
+
+static int dpu_get_irq(struct platform_device *pdev, struct dpu_soc *dpu)
+{
+#define DPU_GET_IRQ(name) \
+{ \
+ dpu->irq_##name = platform_get_irq_byname(pdev, "" #name ""); \
+ dev_dbg(dpu->dev, "irq_" #name ": %d\n", dpu->irq_##name); \
+ if (dpu->irq_##name < 0) { \
+ dev_err(dpu->dev, "failed to get irq " #name "\n"); \
+ return dpu->irq_##name; \
+ } \
+}
+
+ DPU_GET_IRQ(extdst0_shdload);
+ DPU_GET_IRQ(extdst4_shdload);
+ DPU_GET_IRQ(extdst1_shdload);
+ DPU_GET_IRQ(extdst5_shdload);
+ DPU_GET_IRQ(disengcfg_shdload0);
+ DPU_GET_IRQ(disengcfg_framecomplete0);
+ DPU_GET_IRQ(sig0_shdload);
+ DPU_GET_IRQ(sig0_valid);
+ DPU_GET_IRQ(disengcfg_shdload1);
+ DPU_GET_IRQ(disengcfg_framecomplete1);
+ DPU_GET_IRQ(sig1_shdload);
+ DPU_GET_IRQ(sig1_valid);
+ DPU_GET_IRQ(comctrl_sw0);
+ DPU_GET_IRQ(comctrl_sw1);
+ DPU_GET_IRQ(comctrl_sw2);
+ DPU_GET_IRQ(comctrl_sw3);
+
+ return 0;
+}
+
+static void dpu_irq_handle(struct irq_desc *desc, enum dpu_irq irq)
+{
+ struct dpu_soc *dpu = irq_desc_get_handler_data(desc);
+ const struct dpu_data *data = dpu->data;
+ const struct cm_reg_ofs *ofs = data->cm_reg_ofs;
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ unsigned int virq;
+ u32 status;
+
+ chained_irq_enter(chip, desc);
+
+ status = dpu_cm_read(dpu, USERINTERRUPTSTATUS(ofs, irq / 32));
+ status &= dpu_cm_read(dpu, USERINTERRUPTENABLE(ofs, irq / 32));
+
+ if (status & BIT(irq % 32)) {
+ virq = irq_linear_revmap(dpu->domain, irq);
+ if (virq)
+ generic_handle_irq(virq);
+ }
+
+ chained_irq_exit(chip, desc);
+}
+
+#define DPU_IRQ_HANDLER_DEFINE(name1, name2) \
+static void dpu_##name1##_irq_handler(struct irq_desc *desc) \
+{ \
+ dpu_irq_handle(desc, IRQ_##name2); \
+}
+
+DPU_IRQ_HANDLER_DEFINE(extdst0_shdload, EXTDST0_SHDLOAD)
+DPU_IRQ_HANDLER_DEFINE(extdst4_shdload, EXTDST4_SHDLOAD)
+DPU_IRQ_HANDLER_DEFINE(extdst1_shdload, EXTDST1_SHDLOAD)
+DPU_IRQ_HANDLER_DEFINE(extdst5_shdload, EXTDST5_SHDLOAD)
+DPU_IRQ_HANDLER_DEFINE(disengcfg_shdload0, DISENGCFG_SHDLOAD0)
+DPU_IRQ_HANDLER_DEFINE(disengcfg_framecomplete0, DISENGCFG_FRAMECOMPLETE0)
+DPU_IRQ_HANDLER_DEFINE(sig0_shdload, SIG0_SHDLOAD);
+DPU_IRQ_HANDLER_DEFINE(sig0_valid, SIG0_VALID);
+DPU_IRQ_HANDLER_DEFINE(disengcfg_shdload1, DISENGCFG_SHDLOAD1)
+DPU_IRQ_HANDLER_DEFINE(disengcfg_framecomplete1, DISENGCFG_FRAMECOMPLETE1)
+DPU_IRQ_HANDLER_DEFINE(sig1_shdload, SIG1_SHDLOAD);
+DPU_IRQ_HANDLER_DEFINE(sig1_valid, SIG1_VALID);
+DPU_IRQ_HANDLER_DEFINE(comctrl_sw0, COMCTRL_SW0);
+DPU_IRQ_HANDLER_DEFINE(comctrl_sw1, COMCTRL_SW1);
+DPU_IRQ_HANDLER_DEFINE(comctrl_sw2, COMCTRL_SW2);
+DPU_IRQ_HANDLER_DEFINE(comctrl_sw3, COMCTRL_SW3);
+
+int dpu_map_irq(struct dpu_soc *dpu, int irq)
+{
+ int virq = irq_linear_revmap(dpu->domain, irq);
+
+ if (!virq)
+ virq = irq_create_mapping(dpu->domain, irq);
+
+ return virq;
+}
+EXPORT_SYMBOL_GPL(dpu_map_irq);
+
+static int dpu_irq_init(struct dpu_soc *dpu)
+{
+ const struct dpu_data *data = dpu->data;
+ const struct cm_reg_ofs *ofs = data->cm_reg_ofs;
+ struct irq_chip_generic *gc;
+ struct irq_chip_type *ct;
+ int ret, i;
+
+ dpu->domain = irq_domain_add_linear(dpu->dev->of_node,
+ dpu->irq_line_num,
+ &irq_generic_chip_ops, dpu);
+ if (!dpu->domain) {
+ dev_err(dpu->dev, "failed to add irq domain\n");
+ return -ENODEV;
+ }
+
+ ret = irq_alloc_domain_generic_chips(dpu->domain, 32, 1, "DPU",
+ handle_level_irq, 0, 0, 0);
+ if (ret < 0) {
+ dev_err(dpu->dev, "failed to alloc generic irq chips\n");
+ irq_domain_remove(dpu->domain);
+ return ret;
+ }
+
+ for (i = 0; i < dpu->irq_line_num; i += 32) {
+ /* Mask and clear all interrupts */
+ dpu_cm_write(dpu, USERINTERRUPTENABLE(ofs, i / 32), 0);
+ dpu_cm_write(dpu, USERINTERRUPTCLEAR(ofs, i / 32),
+ ~data->unused_irq[i / 32]);
+ dpu_cm_write(dpu, INTERRUPTENABLE(ofs, i / 32), 0);
+ dpu_cm_write(dpu, INTERRUPTCLEAR(ofs, i / 32),
+ ~data->unused_irq[i / 32]);
+
+ /* Set all interrupts to user mode */
+ dpu_cm_write(dpu, USERINTERRUPTMASK(ofs, i / 32),
+ ~data->unused_irq[i / 32]);
+
+ gc = irq_get_domain_generic_chip(dpu->domain, i);
+ gc->reg_base = dpu->cm_reg;
+ gc->unused = data->unused_irq[i / 32];
+ ct = gc->chip_types;
+ ct->chip.irq_ack = irq_gc_ack_set_bit;
+ ct->chip.irq_mask = irq_gc_mask_clr_bit;
+ ct->chip.irq_unmask = irq_gc_mask_set_bit;
+ ct->regs.ack = USERINTERRUPTCLEAR(ofs, i / 32);
+ ct->regs.mask = USERINTERRUPTENABLE(ofs, i / 32);
+ }
+
+#define DPU_IRQ_CHIP_PM_GET(name) \
+{ \
+ ret = irq_chip_pm_get(irq_get_irq_data(dpu->irq_##name)); \
+ if (ret < 0) { \
+ dev_err(dpu->dev, \
+ "failed to get irq chip PM for irq%d %d\n", \
+ dpu->irq_##name, ret); \
+ goto pm_get_rollback; \
+ } \
+ dpu->irq_chip_pm_get_##name = true; \
+}
+
+ DPU_IRQ_CHIP_PM_GET(extdst0_shdload);
+ DPU_IRQ_CHIP_PM_GET(extdst4_shdload);
+ DPU_IRQ_CHIP_PM_GET(extdst1_shdload);
+ DPU_IRQ_CHIP_PM_GET(extdst5_shdload);
+ DPU_IRQ_CHIP_PM_GET(disengcfg_shdload0);
+ DPU_IRQ_CHIP_PM_GET(disengcfg_framecomplete0);
+ DPU_IRQ_CHIP_PM_GET(sig0_shdload);
+ DPU_IRQ_CHIP_PM_GET(sig0_valid);
+ DPU_IRQ_CHIP_PM_GET(disengcfg_shdload1);
+ DPU_IRQ_CHIP_PM_GET(disengcfg_framecomplete1);
+ DPU_IRQ_CHIP_PM_GET(sig1_shdload);
+ DPU_IRQ_CHIP_PM_GET(sig1_valid);
+ DPU_IRQ_CHIP_PM_GET(comctrl_sw0);
+ DPU_IRQ_CHIP_PM_GET(comctrl_sw1);
+ DPU_IRQ_CHIP_PM_GET(comctrl_sw2);
+ DPU_IRQ_CHIP_PM_GET(comctrl_sw3);
+
+#define DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(name) \
+irq_set_chained_handler_and_data(dpu->irq_##name, dpu_##name##_irq_handler, dpu)
+
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(extdst0_shdload);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(extdst4_shdload);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(extdst1_shdload);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(extdst5_shdload);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(disengcfg_shdload0);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(disengcfg_framecomplete0);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(sig0_shdload);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(sig0_valid);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(disengcfg_shdload1);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(disengcfg_framecomplete1);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(sig1_shdload);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(sig1_valid);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(comctrl_sw0);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(comctrl_sw1);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(comctrl_sw2);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(comctrl_sw3);
+
+#define DPU_IRQ_CHIP_PM_PUT_CHECK(name) \
+{ \
+ if (dpu->irq_chip_pm_get_##name) { \
+ irq_chip_pm_put(irq_get_irq_data(dpu->irq_##name)); \
+ dpu->irq_chip_pm_get_##name = false; \
+ } \
+}
+
+ return 0;
+
+pm_get_rollback:
+ DPU_IRQ_CHIP_PM_PUT_CHECK(extdst0_shdload);
+ DPU_IRQ_CHIP_PM_PUT_CHECK(extdst4_shdload);
+ DPU_IRQ_CHIP_PM_PUT_CHECK(extdst1_shdload);
+ DPU_IRQ_CHIP_PM_PUT_CHECK(extdst5_shdload);
+ DPU_IRQ_CHIP_PM_PUT_CHECK(disengcfg_shdload0);
+ DPU_IRQ_CHIP_PM_PUT_CHECK(disengcfg_framecomplete0);
+ DPU_IRQ_CHIP_PM_PUT_CHECK(sig0_shdload);
+ DPU_IRQ_CHIP_PM_PUT_CHECK(sig0_valid);
+ DPU_IRQ_CHIP_PM_PUT_CHECK(disengcfg_shdload1);
+ DPU_IRQ_CHIP_PM_PUT_CHECK(disengcfg_framecomplete1);
+ DPU_IRQ_CHIP_PM_PUT_CHECK(sig1_shdload);
+ DPU_IRQ_CHIP_PM_PUT_CHECK(sig1_valid);
+ DPU_IRQ_CHIP_PM_PUT_CHECK(comctrl_sw0);
+ DPU_IRQ_CHIP_PM_PUT_CHECK(comctrl_sw1);
+ DPU_IRQ_CHIP_PM_PUT_CHECK(comctrl_sw2);
+ DPU_IRQ_CHIP_PM_PUT_CHECK(comctrl_sw3);
+
+ return ret;
+}
+
+static void dpu_irq_exit(struct dpu_soc *dpu)
+{
+ unsigned int i, irq;
+
+#define DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(name) \
+irq_set_chained_handler_and_data(dpu->irq_##name, NULL, NULL)
+
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(extdst0_shdload);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(extdst4_shdload);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(extdst1_shdload);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(extdst5_shdload);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(disengcfg_shdload0);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(disengcfg_framecomplete0);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(sig0_shdload);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(sig0_valid);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(disengcfg_shdload1);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(disengcfg_framecomplete1);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(sig1_shdload);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(sig1_valid);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(comctrl_sw0);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(comctrl_sw1);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(comctrl_sw2);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(comctrl_sw3);
+
+#define DPU_IRQ_CHIP_PM_PUT(name) \
+{ \
+ irq_chip_pm_put(irq_get_irq_data(dpu->irq_##name)); \
+ dpu->irq_chip_pm_get_##name = false; \
+}
+
+ DPU_IRQ_CHIP_PM_PUT(extdst0_shdload);
+ DPU_IRQ_CHIP_PM_PUT(extdst4_shdload);
+ DPU_IRQ_CHIP_PM_PUT(extdst1_shdload);
+ DPU_IRQ_CHIP_PM_PUT(extdst5_shdload);
+ DPU_IRQ_CHIP_PM_PUT(disengcfg_shdload0);
+ DPU_IRQ_CHIP_PM_PUT(disengcfg_framecomplete0);
+ DPU_IRQ_CHIP_PM_PUT(sig0_shdload);
+ DPU_IRQ_CHIP_PM_PUT(sig0_valid);
+ DPU_IRQ_CHIP_PM_PUT(disengcfg_shdload1);
+ DPU_IRQ_CHIP_PM_PUT(disengcfg_framecomplete1);
+ DPU_IRQ_CHIP_PM_PUT(sig1_shdload);
+ DPU_IRQ_CHIP_PM_PUT(sig1_valid);
+ DPU_IRQ_CHIP_PM_PUT(comctrl_sw0);
+ DPU_IRQ_CHIP_PM_PUT(comctrl_sw1);
+ DPU_IRQ_CHIP_PM_PUT(comctrl_sw2);
+ DPU_IRQ_CHIP_PM_PUT(comctrl_sw3);
+
+ for (i = 0; i < dpu->irq_line_num; i++) {
+ irq = irq_linear_revmap(dpu->domain, i);
+ if (irq)
+ irq_dispose_mapping(irq);
+ }
+
+ irq_domain_remove(dpu->domain);
+}
+
+#define _DPU_UNITS_INIT(unit) \
+{ \
+ const struct dpu_unit *us = data->unit##s; \
+ int i; \
+ \
+ /* software check */ \
+ if (WARN_ON(us->num > ARRAY_SIZE(unit##_ids))) \
+ return -EINVAL; \
+ \
+ for (i = 0; i < us->num; i++) \
+ _dpu_##unit##_init(dpu, us->ids[i]); \
+}
+
+static int
+_dpu_submodules_init(struct dpu_soc *dpu, struct platform_device *pdev)
+{
+ const struct dpu_data *data = dpu->data;
+
+ _DPU_UNITS_INIT(cf);
+ _DPU_UNITS_INIT(dec);
+ _DPU_UNITS_INIT(ed);
+ _DPU_UNITS_INIT(fd);
+ _DPU_UNITS_INIT(fe);
+ _DPU_UNITS_INIT(fg);
+ _DPU_UNITS_INIT(fl);
+ _DPU_UNITS_INIT(fw);
+ _DPU_UNITS_INIT(hs);
+ _DPU_UNITS_INIT(lb);
+ _DPU_UNITS_INIT(sig);
+ _DPU_UNITS_INIT(st);
+ _DPU_UNITS_INIT(tcon);
+ _DPU_UNITS_INIT(vs);
+
+ return 0;
+}
+
+#define DPU_UNIT_INIT(dpu, base, unit, name, id, pec_ofs, ofs) \
+{ \
+ int ret; \
+ ret = dpu_##unit##_init((dpu), (id), \
+ (pec_ofs) ? (base) + (pec_ofs) : 0, \
+ (base) + (ofs)); \
+ if (ret) { \
+ dev_err((dpu)->dev, "init %s%d failed with %d\n", \
+ (name), (id), ret); \
+ return ret; \
+ } \
+}
+
+#define DPU_UNITS_INIT(unit) \
+{ \
+ const struct dpu_unit *us = data->unit##s; \
+ int i; \
+ \
+ /* software check */ \
+ if (WARN_ON(us->num > ARRAY_SIZE(unit##_ids))) \
+ return -EINVAL; \
+ \
+ for (i = 0; i < us->num; i++) \
+ DPU_UNIT_INIT(dpu, dpu_base, unit, us->name, \
+ us->ids[i], \
+ us->pec_ofss ? us->pec_ofss[i] : 0, \
+ us->ofss[i]); \
+}
+
+static int dpu_submodules_init(struct dpu_soc *dpu,
+ struct platform_device *pdev, unsigned long dpu_base)
+{
+ const struct dpu_data *data = dpu->data;
+ const struct dpu_unit *fds = data->fds;
+ const struct dpu_unit *fls = data->fls;
+ const struct dpu_unit *fws = data->fws;
+ const struct dpu_unit *tcons = data->tcons;
+ struct dpu_fetchunit *fu;
+ struct dprc *dprc;
+ struct dpu_tcon *tcon;
+ struct pc *pc;
+ int i;
+
+ DPU_UNITS_INIT(cf);
+ DPU_UNITS_INIT(dec);
+ DPU_UNITS_INIT(ed);
+ DPU_UNITS_INIT(fd);
+ DPU_UNITS_INIT(fe);
+ DPU_UNITS_INIT(fg);
+ DPU_UNITS_INIT(fl);
+ DPU_UNITS_INIT(fw);
+ DPU_UNITS_INIT(hs);
+ DPU_UNITS_INIT(lb);
+ DPU_UNITS_INIT(sig);
+ DPU_UNITS_INIT(st);
+ DPU_UNITS_INIT(tcon);
+ DPU_UNITS_INIT(vs);
+
+ for (i = 0; i < fds->num; i++) {
+ dprc = dprc_lookup_by_phandle(dpu->dev, "fsl,dpr-channels",
+ fds->dprc_ids[i]);
+ if (!dprc)
+ return -EPROBE_DEFER;
+
+ fu = dpu_fd_get(dpu, i);
+ fetchunit_get_dprc(fu, dprc);
+ dpu_fd_put(fu);
+ }
+
+ for (i = 0; i < fls->num; i++) {
+ dprc = dprc_lookup_by_phandle(dpu->dev, "fsl,dpr-channels",
+ fls->dprc_ids[i]);
+ if (!dprc)
+ return -EPROBE_DEFER;
+
+ fu = dpu_fl_get(dpu, i);
+ fetchunit_get_dprc(fu, dprc);
+ dpu_fl_put(fu);
+ }
+
+ for (i = 0; i < fws->num; i++) {
+ dprc = dprc_lookup_by_phandle(dpu->dev, "fsl,dpr-channels",
+ fws->dprc_ids[i]);
+ if (!dprc)
+ return -EPROBE_DEFER;
+
+ fu = dpu_fw_get(dpu, fw_ids[i]);
+ fetchunit_get_dprc(fu, dprc);
+ dpu_fw_put(fu);
+ }
+
+ pc = pc_lookup_by_phandle(dpu->dev, "fsl,pixel-combiner");
+ if (!pc)
+ return -EPROBE_DEFER;
+
+ for (i = 0; i < tcons->num; i++) {
+ tcon = dpu_tcon_get(dpu, i);
+ tcon_get_pc(tcon, pc);
+ dpu_tcon_put(tcon);
+ }
+
+ return 0;
+}
+
+static int platform_remove_devices_fn(struct device *dev, void *unused)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+
+ platform_device_unregister(pdev);
+
+ return 0;
+}
+
+static void platform_device_unregister_children(struct platform_device *pdev)
+{
+ device_for_each_child(&pdev->dev, NULL, platform_remove_devices_fn);
+}
+
+struct dpu_platform_reg {
+ struct dpu_client_platformdata pdata;
+ const char *name;
+};
+
+static struct dpu_platform_reg client_reg[] = {
+ {
+ .pdata = {
+ .stream_id = 0,
+ },
+ .name = "imx-dpu-crtc",
+ }, {
+ .pdata = {
+ .stream_id = 1,
+ },
+ .name = "imx-dpu-crtc",
+ }, {
+ .pdata = { },
+ .name = IMX_DPU_BLITENG_NAME,
+ }
+};
+
+static DEFINE_MUTEX(dpu_client_id_mutex);
+static int dpu_client_id;
+
+static int dpu_get_plane_resource(struct dpu_soc *dpu,
+ struct dpu_plane_res *res)
+{
+ const struct dpu_unit *fds = dpu->data->fds;
+ const struct dpu_unit *fls = dpu->data->fls;
+ const struct dpu_unit *fws = dpu->data->fws;
+ const struct dpu_unit *lbs = dpu->data->lbs;
+ struct dpu_plane_grp *grp = plane_res_to_grp(res);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(res->ed); i++) {
+ res->ed[i] = dpu_ed_get(dpu, i);
+ if (IS_ERR(res->ed[i]))
+ return PTR_ERR(res->ed[i]);
+ }
+ for (i = 0; i < fds->num; i++) {
+ res->fd[i] = dpu_fd_get(dpu, i);
+ if (IS_ERR(res->fd[i]))
+ return PTR_ERR(res->fd[i]);
+ }
+ for (i = 0; i < ARRAY_SIZE(res->fe); i++) {
+ res->fe[i] = dpu_fe_get(dpu, i);
+ if (IS_ERR(res->fe[i]))
+ return PTR_ERR(res->fe[i]);
+ grp->hw_plane_fetcheco_num = ARRAY_SIZE(res->fe);
+ }
+ for (i = 0; i < fls->num; i++) {
+ res->fl[i] = dpu_fl_get(dpu, i);
+ if (IS_ERR(res->fl[i]))
+ return PTR_ERR(res->fl[i]);
+ }
+ for (i = 0; i < fws->num; i++) {
+ res->fw[i] = dpu_fw_get(dpu, fw_ids[i]);
+ if (IS_ERR(res->fw[i]))
+ return PTR_ERR(res->fw[i]);
+ }
+ /* HScaler could be shared with capture. */
+ if (display_plane_video_proc) {
+ for (i = 0; i < ARRAY_SIZE(res->hs); i++) {
+ res->hs[i] = dpu_hs_get(dpu, hs_ids[i]);
+ if (IS_ERR(res->hs[i]))
+ return PTR_ERR(res->hs[i]);
+ }
+ grp->hw_plane_hscaler_num = ARRAY_SIZE(res->hs);
+ }
+ for (i = 0; i < lbs->num; i++) {
+ res->lb[i] = dpu_lb_get(dpu, i);
+ if (IS_ERR(res->lb[i]))
+ return PTR_ERR(res->lb[i]);
+ }
+ /* VScaler could be shared with capture. */
+ if (display_plane_video_proc) {
+ for (i = 0; i < ARRAY_SIZE(res->vs); i++) {
+ res->vs[i] = dpu_vs_get(dpu, vs_ids[i]);
+ if (IS_ERR(res->vs[i]))
+ return PTR_ERR(res->vs[i]);
+ }
+ grp->hw_plane_vscaler_num = ARRAY_SIZE(res->vs);
+ }
+
+ grp->hw_plane_num = fds->num + fls->num + fws->num;
+
+ return 0;
+}
+
+static void dpu_put_plane_resource(struct dpu_plane_res *res)
+{
+ struct dpu_plane_grp *grp = plane_res_to_grp(res);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(res->ed); i++) {
+ if (!IS_ERR_OR_NULL(res->ed[i]))
+ dpu_ed_put(res->ed[i]);
+ }
+ for (i = 0; i < ARRAY_SIZE(res->fd); i++) {
+ if (!IS_ERR_OR_NULL(res->fd[i]))
+ dpu_fd_put(res->fd[i]);
+ }
+ for (i = 0; i < ARRAY_SIZE(res->fe); i++) {
+ if (!IS_ERR_OR_NULL(res->fe[i]))
+ dpu_fe_put(res->fe[i]);
+ }
+ for (i = 0; i < ARRAY_SIZE(res->fl); i++) {
+ if (!IS_ERR_OR_NULL(res->fl[i]))
+ dpu_fl_put(res->fl[i]);
+ }
+ for (i = 0; i < ARRAY_SIZE(res->fw); i++) {
+ if (!IS_ERR_OR_NULL(res->fw[i]))
+ dpu_fw_put(res->fw[i]);
+ }
+ for (i = 0; i < ARRAY_SIZE(res->hs); i++) {
+ if (!IS_ERR_OR_NULL(res->hs[i]))
+ dpu_hs_put(res->hs[i]);
+ }
+ for (i = 0; i < ARRAY_SIZE(res->lb); i++) {
+ if (!IS_ERR_OR_NULL(res->lb[i]))
+ dpu_lb_put(res->lb[i]);
+ }
+ for (i = 0; i < ARRAY_SIZE(res->vs); i++) {
+ if (!IS_ERR_OR_NULL(res->vs[i]))
+ dpu_vs_put(res->vs[i]);
+ }
+
+ grp->hw_plane_num = 0;
+}
+
+static int dpu_add_client_devices(struct dpu_soc *dpu)
+{
+ const struct dpu_data *data = dpu->data;
+ struct device *dev = dpu->dev;
+ struct dpu_platform_reg *reg;
+ struct dpu_plane_grp *plane_grp;
+ struct dpu_store *st9 = NULL;
+ size_t client_num, reg_size;
+ int i, id, ret;
+
+ client_num = ARRAY_SIZE(client_reg);
+
+ reg = devm_kcalloc(dev, client_num, sizeof(*reg), GFP_KERNEL);
+ if (!reg)
+ return -ENODEV;
+
+ plane_grp = devm_kzalloc(dev, sizeof(*plane_grp), GFP_KERNEL);
+ if (!plane_grp)
+ return -ENODEV;
+
+ mutex_init(&plane_grp->mutex);
+
+ mutex_lock(&dpu_client_id_mutex);
+ id = dpu_client_id;
+ dpu_client_id += client_num;
+ mutex_unlock(&dpu_client_id_mutex);
+
+ reg_size = client_num * sizeof(struct dpu_platform_reg);
+ memcpy(reg, &client_reg[0], reg_size);
+
+ plane_grp->src_mask = data->plane_src_mask;
+ plane_grp->id = id / client_num;
+ plane_grp->has_vproc = display_plane_video_proc;
+
+ ret = dpu_get_plane_resource(dpu, &plane_grp->res);
+ if (ret)
+ goto err_get_plane_res;
+
+ st9 = dpu_st_get(dpu, 9);
+ if (IS_ERR(st9)) {
+ ret = PTR_ERR(st9);
+ goto err_get_plane_res;
+ }
+
+ for (i = 0; i < client_num; i++) {
+ struct platform_device *pdev;
+ struct device_node *of_node = NULL;
+
+ if (!strcmp(reg[i].name, IMX_DPU_BLITENG_NAME)) {
+ /* As bliteng has no of_node, so to use dpu's. */
+ of_node = dev->of_node;
+ } else {
+ /* Associate subdevice with the corresponding port node. */
+ of_node = of_graph_get_port_by_id(dev->of_node, i);
+ if (!of_node) {
+ dev_info(dev,
+ "no port@%d node in %s, not using DISP%d\n",
+ i, dev->of_node->full_name, i);
+ continue;
+ }
+ }
+
+ reg[i].pdata.plane_grp = plane_grp;
+ reg[i].pdata.di_grp_id = plane_grp->id;
+ reg[i].pdata.st9 = st9;
+
+ pdev = platform_device_alloc(reg[i].name, id++);
+ if (!pdev) {
+ ret = -ENOMEM;
+ goto err_register;
+ }
+
+ pdev->dev.parent = dev;
+
+ reg[i].pdata.of_node = of_node;
+ ret = platform_device_add_data(pdev, &reg[i].pdata,
+ sizeof(reg[i].pdata));
+ if (!ret)
+ ret = platform_device_add(pdev);
+ if (ret) {
+ platform_device_put(pdev);
+ goto err_register;
+ }
+ }
+
+ return 0;
+
+err_register:
+ platform_device_unregister_children(to_platform_device(dev));
+ dpu_st_put(st9);
+err_get_plane_res:
+ dpu_put_plane_resource(&plane_grp->res);
+
+ return ret;
+}
+
+static int dpu_probe(struct platform_device *pdev)
+{
+ const struct of_device_id *of_id;
+ struct device_node *np = pdev->dev.of_node;
+ struct dpu_soc *dpu;
+ struct resource *res;
+ unsigned long dpu_base;
+ const struct dpu_data *data;
+ int ret;
+
+ of_id = of_match_device(dpu_dt_ids, &pdev->dev);
+ if (!of_id)
+ return -ENODEV;
+
+ data = of_id->data;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENODEV;
+
+ dpu_base = res->start;
+
+ dpu = devm_kzalloc(&pdev->dev, sizeof(*dpu), GFP_KERNEL);
+ if (!dpu)
+ return -ENODEV;
+
+ dpu->dev = &pdev->dev;
+ dpu->data = data;
+ dpu->id = of_alias_get_id(np, "dpu");
+ dpu->irq_line_num = platform_irq_count(pdev);
+ if (dpu->irq_line_num < 0)
+ return dpu->irq_line_num;
+
+ dpu_units_addr_dbg(dpu, pdev, dpu_base);
+
+ ret = dpu_get_irq(pdev, dpu);
+ if (ret < 0)
+ return ret;
+
+ ret = dpu_sc_misc_get_handle(dpu);
+ if (ret < 0)
+ return ret;
+
+ spin_lock_init(&dpu->lock);
+
+ dpu->cm_reg = devm_ioremap(dpu->dev, dpu_base + data->cm_ofs, SZ_1K);
+ if (!dpu->cm_reg)
+ return -ENOMEM;
+
+ ret = dpu_attach_pm_domains(dpu);
+ if (ret)
+ return ret;
+
+ ret = dpu_irq_init(dpu);
+ if (ret)
+ goto failed_irq;
+
+ ret = dpu_submodules_init(dpu, pdev, dpu_base);
+ if (ret)
+ goto failed_submodules_init;
+
+ ret = dpu_sc_misc_init(dpu);
+ if (ret < 0) {
+ dev_err(dpu->dev,
+ "failed to initialize pixel link %d\n", ret);
+ goto failed_sc_misc_init;
+ }
+
+ platform_set_drvdata(pdev, dpu);
+
+ ret = dpu_add_client_devices(dpu);
+ if (ret) {
+ dev_err(dpu->dev,
+ "adding client devices failed with %d\n", ret);
+ goto failed_add_clients;
+ }
+
+ dev_info(dpu->dev, "driver probed\n");
+
+ return 0;
+
+failed_add_clients:
+ platform_set_drvdata(pdev, NULL);
+failed_sc_misc_init:
+failed_submodules_init:
+ dpu_irq_exit(dpu);
+failed_irq:
+ dpu_detach_pm_domains(dpu);
+ return ret;
+}
+
+static int dpu_remove(struct platform_device *pdev)
+{
+ struct dpu_soc *dpu = platform_get_drvdata(pdev);
+
+ platform_device_unregister_children(pdev);
+
+ dpu_irq_exit(dpu);
+ dpu_detach_pm_domains(dpu);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int dpu_suspend(struct device *dev)
+{
+ /*
+ * The dpu core driver currently depends on the client drivers
+ * to do suspend operations to leave dpu a cleaned up state
+ * machine status before the system enters sleep mode.
+ */
+ return 0;
+}
+
+static int dpu_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct dpu_soc *dpu = platform_get_drvdata(pdev);
+
+ dpu_sc_misc_init(dpu);
+
+ _dpu_submodules_init(dpu, pdev);
+
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops dpu_pm_ops = {
+ SET_LATE_SYSTEM_SLEEP_PM_OPS(dpu_suspend, dpu_resume)
+};
+
+static struct platform_driver dpu_driver = {
+ .driver = {
+ .pm = &dpu_pm_ops,
+ .name = "dpu-core",
+ .of_match_table = dpu_dt_ids,
+ },
+ .probe = dpu_probe,
+ .remove = dpu_remove,
+};
+
+module_platform_driver(dpu_driver);
+
+MODULE_DESCRIPTION("i.MX DPU driver");
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/imx/dpu/dpu-constframe.c b/drivers/gpu/imx/dpu/dpu-constframe.c
new file mode 100644
index 000000000000..26c7f85fa67a
--- /dev/null
+++ b/drivers/gpu/imx/dpu/dpu-constframe.c
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2016 Freescale Semiconductor, Inc.
+ * Copyright 2017-2019 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include <video/dpu.h>
+#include "dpu-prv.h"
+
+static unsigned int safety_stream_cf_color = 0x0;
+module_param(safety_stream_cf_color, uint, 0444);
+MODULE_PARM_DESC(safety_stream_cf_color,
+"Safety stream constframe color in hex(0xRRGGBBAA) [default=0x00000000]");
+
+#define FRAMEDIMENSIONS 0xC
+#define WIDTH(w) (((w) - 1) & 0x3FFF)
+#define HEIGHT(h) ((((h) - 1) & 0x3FFF) << 16)
+#define CONSTANTCOLOR 0x10
+#define RED(r) (((r) & 0xFF) << 24)
+#define GREEN(g) (((g) & 0xFF) << 16)
+#define BLUE(b) (((b) & 0xFF) << 8)
+#define ALPHA(a) ((a) & 0xFF)
+#define CONTROLTRIGGER 0x14
+#define START 0x18
+#define STATUS 0x1C
+
+struct dpu_constframe {
+ void __iomem *pec_base;
+ void __iomem *base;
+ struct mutex mutex;
+ int id;
+ bool inuse;
+ struct dpu_soc *dpu;
+};
+
+static inline u32 dpu_cf_read(struct dpu_constframe *cf, unsigned int offset)
+{
+ return readl(cf->base + offset);
+}
+
+static inline void dpu_cf_write(struct dpu_constframe *cf,
+ unsigned int offset, u32 value)
+{
+ writel(value, cf->base + offset);
+}
+
+void constframe_shden(struct dpu_constframe *cf, bool enable)
+{
+ u32 val;
+
+ val = enable ? SHDEN : 0;
+
+ mutex_lock(&cf->mutex);
+ dpu_cf_write(cf, STATICCONTROL, val);
+ mutex_unlock(&cf->mutex);
+}
+EXPORT_SYMBOL_GPL(constframe_shden);
+
+void constframe_framedimensions(struct dpu_constframe *cf, unsigned int w,
+ unsigned int h)
+{
+ u32 val;
+
+ val = WIDTH(w) | HEIGHT(h);
+
+ mutex_lock(&cf->mutex);
+ dpu_cf_write(cf, FRAMEDIMENSIONS, val);
+ mutex_unlock(&cf->mutex);
+}
+EXPORT_SYMBOL_GPL(constframe_framedimensions);
+
+void constframe_framedimensions_copy_prim(struct dpu_constframe *cf)
+{
+ struct dpu_constframe *prim_cf = NULL;
+ unsigned int prim_id;
+ int i;
+ u32 val;
+
+ if (cf->id != 0 && cf->id != 1) {
+ dev_warn(cf->dpu->dev, "ConstFrame%d is not a secondary one\n",
+ cf->id);
+ return;
+ }
+
+ prim_id = cf->id + 4;
+
+ for (i = 0; i < ARRAY_SIZE(cf_ids); i++)
+ if (cf_ids[i] == prim_id)
+ prim_cf = cf->dpu->cf_priv[i];
+
+ if (!prim_cf) {
+ dev_warn(cf->dpu->dev, "cannot find ConstFrame%d's primary peer\n",
+ cf->id);
+ return;
+ }
+
+ mutex_lock(&cf->mutex);
+ val = dpu_cf_read(prim_cf, FRAMEDIMENSIONS);
+ dpu_cf_write(cf, FRAMEDIMENSIONS, val);
+ mutex_unlock(&cf->mutex);
+}
+EXPORT_SYMBOL_GPL(constframe_framedimensions_copy_prim);
+
+void constframe_constantcolor(struct dpu_constframe *cf, unsigned int r,
+ unsigned int g, unsigned int b, unsigned int a)
+{
+ u32 val;
+
+ val = RED(r) | GREEN(g) | BLUE(b) | ALPHA(a);
+
+ mutex_lock(&cf->mutex);
+ dpu_cf_write(cf, CONSTANTCOLOR, val);
+ mutex_unlock(&cf->mutex);
+}
+EXPORT_SYMBOL_GPL(constframe_constantcolor);
+
+void constframe_controltrigger(struct dpu_constframe *cf, bool trigger)
+{
+ u32 val;
+
+ val = trigger ? SHDTOKGEN : 0;
+
+ mutex_lock(&cf->mutex);
+ dpu_cf_write(cf, CONTROLTRIGGER, val);
+ mutex_unlock(&cf->mutex);
+}
+EXPORT_SYMBOL_GPL(constframe_controltrigger);
+
+struct dpu_constframe *dpu_cf_get(struct dpu_soc *dpu, int id)
+{
+ struct dpu_constframe *cf;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(cf_ids); i++)
+ if (cf_ids[i] == id)
+ break;
+
+ if (i == ARRAY_SIZE(cf_ids))
+ return ERR_PTR(-EINVAL);
+
+ cf = dpu->cf_priv[i];
+
+ mutex_lock(&cf->mutex);
+
+ if (cf->inuse) {
+ mutex_unlock(&cf->mutex);
+ return ERR_PTR(-EBUSY);
+ }
+
+ cf->inuse = true;
+
+ mutex_unlock(&cf->mutex);
+
+ return cf;
+}
+EXPORT_SYMBOL_GPL(dpu_cf_get);
+
+void dpu_cf_put(struct dpu_constframe *cf)
+{
+ mutex_lock(&cf->mutex);
+
+ cf->inuse = false;
+
+ mutex_unlock(&cf->mutex);
+}
+EXPORT_SYMBOL_GPL(dpu_cf_put);
+
+struct dpu_constframe *dpu_aux_cf_peek(struct dpu_constframe *cf)
+{
+ unsigned int aux_id = cf->id ^ 1;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(cf_ids); i++)
+ if (cf_ids[i] == aux_id)
+ return cf->dpu->cf_priv[i];
+
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(dpu_aux_cf_peek);
+
+void _dpu_cf_init(struct dpu_soc *dpu, unsigned int id)
+{
+ struct dpu_constframe *cf;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(cf_ids); i++)
+ if (cf_ids[i] == id)
+ break;
+
+ if (WARN_ON(i == ARRAY_SIZE(cf_ids)))
+ return;
+
+ cf = dpu->cf_priv[i];
+
+ constframe_shden(cf, true);
+
+ if (id == 4 || id == 5) {
+ mutex_lock(&cf->mutex);
+ dpu_cf_write(cf, CONSTANTCOLOR, safety_stream_cf_color);
+ mutex_unlock(&cf->mutex);
+ }
+}
+
+int dpu_cf_init(struct dpu_soc *dpu, unsigned int id,
+ unsigned long pec_base, unsigned long base)
+{
+ struct dpu_constframe *cf;
+ int i;
+
+ cf = devm_kzalloc(dpu->dev, sizeof(*cf), GFP_KERNEL);
+ if (!cf)
+ return -ENOMEM;
+
+ for (i = 0; i < ARRAY_SIZE(cf_ids); i++)
+ if (cf_ids[i] == id)
+ break;
+
+ if (i == ARRAY_SIZE(cf_ids))
+ return -EINVAL;
+
+ dpu->cf_priv[i] = cf;
+
+ cf->pec_base = devm_ioremap(dpu->dev, pec_base, SZ_16);
+ if (!cf->pec_base)
+ return -ENOMEM;
+
+ cf->base = devm_ioremap(dpu->dev, base, SZ_32);
+ if (!cf->base)
+ return -ENOMEM;
+
+ cf->dpu = dpu;
+ cf->id = id;
+
+ mutex_init(&cf->mutex);
+
+ _dpu_cf_init(dpu, id);
+
+ return 0;
+}
diff --git a/drivers/gpu/imx/dpu/dpu-disengcfg.c b/drivers/gpu/imx/dpu/dpu-disengcfg.c
new file mode 100644
index 000000000000..be7e8011c270
--- /dev/null
+++ b/drivers/gpu/imx/dpu/dpu-disengcfg.c
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2016 Freescale Semiconductor, Inc.
+ * Copyright 2017-2020 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <drm/drm_mode.h>
+#include <linux/io.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include "dpu-prv.h"
+
+#define CLOCKCTRL 0x8
+typedef enum {
+ DSPCLKDIVIDE__DIV1, /* Ext disp clk signal has pix clk freq. */
+ DSPCLKDIVIDE__DIV2, /* Ext disp clk signal has 2x the pix clk freq. */
+} clkdivide_t;
+#define POLARITYCTRL 0xC
+#define POLHS_HIGH BIT(0)
+#define POLVS_HIGH BIT(1)
+#define POLEN_HIGH BIT(2)
+#define PIXINV_INV BIT(3)
+#define SRCSELECT 0x10
+#define SIG_SELECT_MASK 0x3
+
+struct dpu_disengcfg {
+ void __iomem *base;
+ struct mutex mutex;
+ int id;
+ bool inuse;
+ struct dpu_soc *dpu;
+};
+
+static inline u32 dpu_dec_read(struct dpu_disengcfg *dec, unsigned int offset)
+{
+ return readl(dec->base + offset);
+}
+
+static inline void dpu_dec_write(struct dpu_disengcfg *dec,
+ unsigned int offset, u32 value)
+{
+ writel(value, dec->base + offset);
+}
+
+void disengcfg_sig_select(struct dpu_disengcfg *dec, dec_sig_sel_t sig_sel)
+{
+ u32 val;
+
+ mutex_lock(&dec->mutex);
+ val = dpu_dec_read(dec, SRCSELECT);
+ val &= ~SIG_SELECT_MASK;
+ val |= sig_sel;
+ dpu_dec_write(dec, SRCSELECT, val);
+ mutex_unlock(&dec->mutex);
+}
+EXPORT_SYMBOL_GPL(disengcfg_sig_select);
+
+struct dpu_disengcfg *dpu_dec_get(struct dpu_soc *dpu, int id)
+{
+ struct dpu_disengcfg *dec;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(dec_ids); i++)
+ if (dec_ids[i] == id)
+ break;
+
+ if (i == ARRAY_SIZE(dec_ids))
+ return ERR_PTR(-EINVAL);
+
+ dec = dpu->dec_priv[i];
+
+ mutex_lock(&dec->mutex);
+
+ if (dec->inuse) {
+ mutex_unlock(&dec->mutex);
+ return ERR_PTR(-EBUSY);
+ }
+
+ dec->inuse = true;
+
+ mutex_unlock(&dec->mutex);
+
+ return dec;
+}
+EXPORT_SYMBOL_GPL(dpu_dec_get);
+
+void dpu_dec_put(struct dpu_disengcfg *dec)
+{
+ mutex_lock(&dec->mutex);
+
+ dec->inuse = false;
+
+ mutex_unlock(&dec->mutex);
+}
+EXPORT_SYMBOL_GPL(dpu_dec_put);
+
+struct dpu_disengcfg *dpu_aux_dec_peek(struct dpu_disengcfg *dec)
+{
+ return dec->dpu->dec_priv[dec->id ^ 1];
+}
+EXPORT_SYMBOL_GPL(dpu_aux_dec_peek);
+
+void _dpu_dec_init(struct dpu_soc *dpu, unsigned int id)
+{
+ struct dpu_disengcfg *dec;
+ u32 val;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(dec_ids); i++)
+ if (ed_ids[i] == id)
+ break;
+
+ if (WARN_ON(i == ARRAY_SIZE(dec_ids)))
+ return;
+
+ dec = dpu->dec_priv[i];
+
+ val = dpu_dec_read(dec, POLARITYCTRL);
+ val &= ~POLHS_HIGH;
+ val &= ~POLVS_HIGH;
+ dpu_dec_write(dec, POLARITYCTRL, val);
+
+ disengcfg_sig_select(dec, DEC_SIG_SEL_FRAMEGEN);
+}
+
+int dpu_dec_init(struct dpu_soc *dpu, unsigned int id,
+ unsigned long unused, unsigned long base)
+{
+ struct dpu_disengcfg *dec;
+
+ dec = devm_kzalloc(dpu->dev, sizeof(*dec), GFP_KERNEL);
+ if (!dec)
+ return -ENOMEM;
+
+ dpu->dec_priv[id] = dec;
+
+ dec->base = devm_ioremap(dpu->dev, base, SZ_16);
+ if (!dec->base)
+ return -ENOMEM;
+
+ dec->dpu = dpu;
+ dec->id = id;
+ mutex_init(&dec->mutex);
+
+ _dpu_dec_init(dpu, id);
+
+ return 0;
+}
diff --git a/drivers/gpu/imx/dpu/dpu-extdst.c b/drivers/gpu/imx/dpu/dpu-extdst.c
new file mode 100644
index 000000000000..013e03a2537e
--- /dev/null
+++ b/drivers/gpu/imx/dpu/dpu-extdst.c
@@ -0,0 +1,521 @@
+/*
+ * Copyright (C) 2016 Freescale Semiconductor, Inc.
+ * Copyright 2017-2019 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include <video/dpu.h>
+#include "dpu-prv.h"
+
+#define PIXENGCFG_STATIC 0x8
+#define POWERDOWN BIT(4)
+#define SYNC_MODE BIT(8)
+#define SW_RESET BIT(11)
+#define DIV(n) (((n) & 0xFF) << 16)
+#define DIV_RESET 0x80
+#define PIXENGCFG_DYNAMIC 0xC
+#define PIXENGCFG_REQUEST 0x10
+#define SHDLDREQ(n) BIT(n)
+#define SEL_SHDLDREQ BIT(0)
+#define PIXENGCFG_TRIGGER 0x14
+#define SYNC_TRIGGER BIT(0)
+#define TRIGGER_SEQUENCE_COMPLETE BIT(4)
+#define PIXENGCFG_STATUS 0x18
+#define SYNC_BUSY BIT(8)
+#define KICK_MODE BIT(8)
+#define PERFCOUNTMODE BIT(12)
+#define CONTROL 0xC
+#define GAMMAAPPLYENABLE BIT(0)
+#define SOFTWAREKICK 0x10
+#define KICK BIT(0)
+#define STATUS 0x14
+#define CNT_ERR_STS BIT(0)
+#define CONTROLWORD 0x18
+#define CURPIXELCNT 0x1C
+static u16 get_xval(u32 pixel_cnt)
+{
+ return pixel_cnt & 0xFFFF;
+}
+
+static u16 get_yval(u32 pixel_cnt)
+{
+ return pixel_cnt >> 16;
+}
+#define LASTPIXELCNT 0x20
+#define PERFCOUNTER 0x24
+
+struct dpu_extdst {
+ void __iomem *pec_base;
+ void __iomem *base;
+ struct mutex mutex;
+ int id;
+ bool inuse;
+ struct dpu_soc *dpu;
+};
+
+static inline u32 dpu_pec_ed_read(struct dpu_extdst *ed, unsigned int offset)
+{
+ return readl(ed->pec_base + offset);
+}
+
+static inline void dpu_pec_ed_write(struct dpu_extdst *ed,
+ unsigned int offset, u32 value)
+{
+ writel(value, ed->pec_base + offset);
+}
+
+static inline u32 dpu_ed_read(struct dpu_extdst *ed, unsigned int offset)
+{
+ return readl(ed->base + offset);
+}
+
+static inline void dpu_ed_write(struct dpu_extdst *ed,
+ unsigned int offset, u32 value)
+{
+ writel(value, ed->base + offset);
+}
+
+static inline bool dpu_ed_is_safety_stream(struct dpu_extdst *ed)
+{
+ if (ed->id == 4 || ed->id == 5)
+ return true;
+
+ return false;
+}
+
+void extdst_pixengcfg_shden(struct dpu_extdst *ed, bool enable)
+{
+ u32 val;
+
+ mutex_lock(&ed->mutex);
+ val = dpu_pec_ed_read(ed, PIXENGCFG_STATIC);
+ if (enable)
+ val |= SHDEN;
+ else
+ val &= ~SHDEN;
+ dpu_pec_ed_write(ed, PIXENGCFG_STATIC, val);
+ mutex_unlock(&ed->mutex);
+}
+EXPORT_SYMBOL_GPL(extdst_pixengcfg_shden);
+
+void extdst_pixengcfg_powerdown(struct dpu_extdst *ed, bool powerdown)
+{
+ u32 val;
+
+ mutex_lock(&ed->mutex);
+ val = dpu_pec_ed_read(ed, PIXENGCFG_STATIC);
+ if (powerdown)
+ val |= POWERDOWN;
+ else
+ val &= ~POWERDOWN;
+ dpu_pec_ed_write(ed, PIXENGCFG_STATIC, val);
+ mutex_unlock(&ed->mutex);
+}
+EXPORT_SYMBOL_GPL(extdst_pixengcfg_powerdown);
+
+void extdst_pixengcfg_sync_mode(struct dpu_extdst *ed, ed_sync_mode_t mode)
+{
+ u32 val;
+
+ mutex_lock(&ed->mutex);
+ val = dpu_pec_ed_read(ed, PIXENGCFG_STATIC);
+ if (mode == AUTO)
+ val |= SYNC_MODE;
+ else
+ val &= ~SYNC_MODE;
+ dpu_pec_ed_write(ed, PIXENGCFG_STATIC, val);
+ mutex_unlock(&ed->mutex);
+}
+EXPORT_SYMBOL_GPL(extdst_pixengcfg_sync_mode);
+
+void extdst_pixengcfg_reset(struct dpu_extdst *ed, bool reset)
+{
+ u32 val;
+
+ mutex_lock(&ed->mutex);
+ val = dpu_pec_ed_read(ed, PIXENGCFG_STATIC);
+ if (reset)
+ val |= SW_RESET;
+ else
+ val &= ~SW_RESET;
+ dpu_pec_ed_write(ed, PIXENGCFG_STATIC, val);
+ mutex_unlock(&ed->mutex);
+}
+EXPORT_SYMBOL_GPL(extdst_pixengcfg_reset);
+
+void extdst_pixengcfg_div(struct dpu_extdst *ed, u16 div)
+{
+ u32 val;
+
+ mutex_lock(&ed->mutex);
+ val = dpu_pec_ed_read(ed, PIXENGCFG_STATIC);
+ val &= ~0xFF0000;
+ val |= DIV(div);
+ dpu_pec_ed_write(ed, PIXENGCFG_STATIC, val);
+ mutex_unlock(&ed->mutex);
+}
+EXPORT_SYMBOL_GPL(extdst_pixengcfg_div);
+
+void extdst_pixengcfg_syncmode_master(struct dpu_extdst *ed, bool enable)
+{
+ u32 val;
+
+ mutex_lock(&ed->mutex);
+ val = dpu_pec_ed_read(ed, PIXENGCFG_STATIC);
+ if (enable)
+ val |= BIT(16);
+ else
+ val &= ~BIT(16);
+ dpu_pec_ed_write(ed, PIXENGCFG_STATIC, val);
+ mutex_unlock(&ed->mutex);
+}
+EXPORT_SYMBOL_GPL(extdst_pixengcfg_syncmode_master);
+
+int extdst_pixengcfg_src_sel(struct dpu_extdst *ed, extdst_src_sel_t src)
+{
+ mutex_lock(&ed->mutex);
+ dpu_pec_ed_write(ed, PIXENGCFG_DYNAMIC, src);
+ mutex_unlock(&ed->mutex);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(extdst_pixengcfg_src_sel);
+
+void extdst_pixengcfg_sel_shdldreq(struct dpu_extdst *ed)
+{
+ u32 val;
+
+ mutex_lock(&ed->mutex);
+ val = dpu_pec_ed_read(ed, PIXENGCFG_REQUEST);
+ val |= SEL_SHDLDREQ;
+ dpu_pec_ed_write(ed, PIXENGCFG_REQUEST, val);
+ mutex_unlock(&ed->mutex);
+}
+EXPORT_SYMBOL_GPL(extdst_pixengcfg_sel_shdldreq);
+
+void extdst_pixengcfg_shdldreq(struct dpu_extdst *ed, u32 req_mask)
+{
+ u32 val;
+
+ mutex_lock(&ed->mutex);
+ val = dpu_pec_ed_read(ed, PIXENGCFG_REQUEST);
+ val |= req_mask;
+ dpu_pec_ed_write(ed, PIXENGCFG_REQUEST, val);
+ mutex_unlock(&ed->mutex);
+}
+EXPORT_SYMBOL_GPL(extdst_pixengcfg_shdldreq);
+
+void extdst_pixengcfg_sync_trigger(struct dpu_extdst *ed)
+{
+ mutex_lock(&ed->mutex);
+ dpu_pec_ed_write(ed, PIXENGCFG_TRIGGER, SYNC_TRIGGER);
+ mutex_unlock(&ed->mutex);
+}
+EXPORT_SYMBOL_GPL(extdst_pixengcfg_sync_trigger);
+
+void extdst_pixengcfg_trigger_sequence_complete(struct dpu_extdst *ed)
+{
+ mutex_lock(&ed->mutex);
+ dpu_pec_ed_write(ed, PIXENGCFG_TRIGGER, TRIGGER_SEQUENCE_COMPLETE);
+ mutex_unlock(&ed->mutex);
+}
+EXPORT_SYMBOL_GPL(extdst_pixengcfg_trigger_sequence_complete);
+
+bool extdst_pixengcfg_is_sync_busy(struct dpu_extdst *ed)
+{
+ u32 val;
+
+ mutex_lock(&ed->mutex);
+ val = dpu_pec_ed_read(ed, PIXENGCFG_STATUS);
+ mutex_unlock(&ed->mutex);
+
+ return val & SYNC_BUSY;
+}
+EXPORT_SYMBOL_GPL(extdst_pixengcfg_is_sync_busy);
+
+ed_pipeline_status_t extdst_pixengcfg_pipeline_status(struct dpu_extdst *ed)
+{
+ u32 val;
+
+ mutex_lock(&ed->mutex);
+ val = dpu_pec_ed_read(ed, PIXENGCFG_STATUS);
+ mutex_unlock(&ed->mutex);
+
+ return val & 0x3;
+}
+EXPORT_SYMBOL_GPL(extdst_pixengcfg_pipeline_status);
+
+void extdst_shden(struct dpu_extdst *ed, bool enable)
+{
+ u32 val;
+
+ mutex_lock(&ed->mutex);
+ val = dpu_ed_read(ed, STATICCONTROL);
+ if (enable)
+ val |= SHDEN;
+ else
+ val &= ~SHDEN;
+ dpu_ed_write(ed, STATICCONTROL, val);
+ mutex_unlock(&ed->mutex);
+}
+EXPORT_SYMBOL_GPL(extdst_shden);
+
+void extdst_kick_mode(struct dpu_extdst *ed, ed_kick_mode_t mode)
+{
+ u32 val;
+
+ mutex_lock(&ed->mutex);
+ val = dpu_ed_read(ed, STATICCONTROL);
+ val &= ~KICK_MODE;
+ val |= mode;
+ dpu_ed_write(ed, STATICCONTROL, val);
+ mutex_unlock(&ed->mutex);
+}
+EXPORT_SYMBOL_GPL(extdst_kick_mode);
+
+void extdst_perfcountmode(struct dpu_extdst *ed, bool enable)
+{
+ u32 val;
+
+ mutex_lock(&ed->mutex);
+ val = dpu_ed_read(ed, STATICCONTROL);
+ if (enable)
+ val |= PERFCOUNTMODE;
+ else
+ val &= ~PERFCOUNTMODE;
+ dpu_ed_write(ed, STATICCONTROL, val);
+ mutex_unlock(&ed->mutex);
+}
+EXPORT_SYMBOL_GPL(extdst_perfcountmode);
+
+void extdst_gamma_apply_enable(struct dpu_extdst *ed, bool enable)
+{
+ u32 val;
+
+ mutex_lock(&ed->mutex);
+ val = dpu_ed_read(ed, CONTROL);
+ if (enable)
+ val |= GAMMAAPPLYENABLE;
+ else
+ val &= ~GAMMAAPPLYENABLE;
+ dpu_ed_write(ed, CONTROL, val);
+ mutex_unlock(&ed->mutex);
+}
+EXPORT_SYMBOL_GPL(extdst_gamma_apply_enable);
+
+void extdst_kick(struct dpu_extdst *ed)
+{
+ mutex_lock(&ed->mutex);
+ dpu_ed_write(ed, SOFTWAREKICK, KICK);
+ mutex_unlock(&ed->mutex);
+}
+EXPORT_SYMBOL_GPL(extdst_kick);
+
+void extdst_cnt_err_clear(struct dpu_extdst *ed)
+{
+ mutex_lock(&ed->mutex);
+ dpu_ed_write(ed, STATUS, CNT_ERR_STS);
+ mutex_unlock(&ed->mutex);
+}
+EXPORT_SYMBOL_GPL(extdst_cnt_err_clear);
+
+bool extdst_cnt_err_status(struct dpu_extdst *ed)
+{
+ u32 val;
+
+ mutex_lock(&ed->mutex);
+ val = dpu_ed_read(ed, STATUS);
+ mutex_unlock(&ed->mutex);
+
+ return val & CNT_ERR_STS;
+}
+EXPORT_SYMBOL_GPL(extdst_cnt_err_status);
+
+u32 extdst_last_control_word(struct dpu_extdst *ed)
+{
+ u32 val;
+
+ mutex_lock(&ed->mutex);
+ val = dpu_ed_read(ed, CONTROLWORD);
+ mutex_unlock(&ed->mutex);
+
+ return val;
+}
+EXPORT_SYMBOL_GPL(extdst_last_control_word);
+
+void extdst_pixel_cnt(struct dpu_extdst *ed, u16 *x, u16 *y)
+{
+ u32 val;
+
+ mutex_lock(&ed->mutex);
+ val = dpu_ed_read(ed, CURPIXELCNT);
+ mutex_unlock(&ed->mutex);
+
+ *x = get_xval(val);
+ *y = get_yval(val);
+}
+EXPORT_SYMBOL_GPL(extdst_pixel_cnt);
+
+void extdst_last_pixel_cnt(struct dpu_extdst *ed, u16 *x, u16 *y)
+{
+ u32 val;
+
+ mutex_lock(&ed->mutex);
+ val = dpu_ed_read(ed, LASTPIXELCNT);
+ mutex_unlock(&ed->mutex);
+
+ *x = get_xval(val);
+ *y = get_yval(val);
+}
+EXPORT_SYMBOL_GPL(extdst_last_pixel_cnt);
+
+u32 extdst_perfresult(struct dpu_extdst *ed)
+{
+ u32 val;
+
+ mutex_lock(&ed->mutex);
+ val = dpu_ed_read(ed, PERFCOUNTER);
+ mutex_unlock(&ed->mutex);
+
+ return val;
+}
+EXPORT_SYMBOL_GPL(extdst_perfresult);
+
+bool extdst_is_master(struct dpu_extdst *ed)
+{
+ const struct dpu_data *data = ed->dpu->data;
+
+ return ed->id == data->master_stream_id;
+}
+EXPORT_SYMBOL_GPL(extdst_is_master);
+
+struct dpu_extdst *dpu_ed_get(struct dpu_soc *dpu, int id)
+{
+ struct dpu_extdst *ed;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ed_ids); i++)
+ if (ed_ids[i] == id)
+ break;
+
+ if (i == ARRAY_SIZE(ed_ids))
+ return ERR_PTR(-EINVAL);
+
+ ed = dpu->ed_priv[i];
+
+ mutex_lock(&ed->mutex);
+
+ if (ed->inuse) {
+ mutex_unlock(&ed->mutex);
+ return ERR_PTR(-EBUSY);
+ }
+
+ ed->inuse = true;
+
+ mutex_unlock(&ed->mutex);
+
+ return ed;
+}
+EXPORT_SYMBOL_GPL(dpu_ed_get);
+
+void dpu_ed_put(struct dpu_extdst *ed)
+{
+ mutex_lock(&ed->mutex);
+
+ ed->inuse = false;
+
+ mutex_unlock(&ed->mutex);
+}
+EXPORT_SYMBOL_GPL(dpu_ed_put);
+
+struct dpu_extdst *dpu_aux_ed_peek(struct dpu_extdst *ed)
+{
+ unsigned int aux_id = ed->id ^ 1;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ed_ids); i++)
+ if (ed_ids[i] == aux_id)
+ return ed->dpu->ed_priv[i];
+
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(dpu_aux_ed_peek);
+
+void _dpu_ed_init(struct dpu_soc *dpu, unsigned int id)
+{
+ struct dpu_extdst *ed;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ed_ids); i++)
+ if (ed_ids[i] == id)
+ break;
+
+ if (WARN_ON(i == ARRAY_SIZE(ed_ids)))
+ return;
+
+ ed = dpu->ed_priv[i];
+
+ extdst_pixengcfg_src_sel(ed, ED_SRC_DISABLE);
+ extdst_pixengcfg_shden(ed, true);
+ extdst_pixengcfg_powerdown(ed, false);
+ extdst_pixengcfg_sync_mode(ed, SINGLE);
+ extdst_pixengcfg_reset(ed, false);
+ extdst_pixengcfg_div(ed, DIV_RESET);
+ extdst_shden(ed, true);
+ extdst_perfcountmode(ed, false);
+ extdst_kick_mode(ed, EXTERNAL);
+}
+
+int dpu_ed_init(struct dpu_soc *dpu, unsigned int id,
+ unsigned long pec_base, unsigned long base)
+{
+ struct dpu_extdst *ed;
+ int ret, i;
+
+ ed = devm_kzalloc(dpu->dev, sizeof(*ed), GFP_KERNEL);
+ if (!ed)
+ return -ENOMEM;
+
+ for (i = 0; i < ARRAY_SIZE(ed_ids); i++)
+ if (ed_ids[i] == id)
+ break;
+
+ if (i == ARRAY_SIZE(ed_ids))
+ return -EINVAL;
+
+ dpu->ed_priv[i] = ed;
+
+ ed->pec_base = devm_ioremap(dpu->dev, pec_base, SZ_32);
+ if (!ed->pec_base)
+ return -ENOMEM;
+
+ ed->base = devm_ioremap(dpu->dev, base, SZ_64);
+ if (!ed->base)
+ return -ENOMEM;
+
+ ed->dpu = dpu;
+ ed->id = id;
+ mutex_init(&ed->mutex);
+
+ ret = extdst_pixengcfg_src_sel(ed, ED_SRC_DISABLE);
+ if (ret < 0)
+ return ret;
+
+ _dpu_ed_init(dpu, id);
+
+ return 0;
+}
diff --git a/drivers/gpu/imx/dpu/dpu-fetchdecode.c b/drivers/gpu/imx/dpu/dpu-fetchdecode.c
new file mode 100644
index 000000000000..66d5862fbfa1
--- /dev/null
+++ b/drivers/gpu/imx/dpu/dpu-fetchdecode.c
@@ -0,0 +1,673 @@
+/*
+ * Copyright (C) 2016 Freescale Semiconductor, Inc.
+ * Copyright 2017-2019,2021 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <drm/drm_blend.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include <video/dpu.h>
+#include "dpu-prv.h"
+
+static const u32 fd_vproc_cap[2] = {
+ DPU_VPROC_CAP_HSCALER4 | DPU_VPROC_CAP_VSCALER4 |
+ DPU_VPROC_CAP_FETCHECO0,
+ DPU_VPROC_CAP_HSCALER5 | DPU_VPROC_CAP_VSCALER5 |
+ DPU_VPROC_CAP_FETCHECO1,
+};
+
+#define PIXENGCFG_DYNAMIC 0x8
+static const fd_dynamic_src_sel_t fd_srcs[2][4] = {
+ {
+ FD_SRC_DISABLE, FD_SRC_FETCHECO0,
+ FD_SRC_FETCHDECODE1, FD_SRC_FETCHWARP2
+ }, {
+ FD_SRC_DISABLE, FD_SRC_FETCHECO1,
+ FD_SRC_FETCHDECODE0, FD_SRC_FETCHWARP2
+ },
+};
+
+#define PIXENGCFG_STATUS 0xC
+
+#define RINGBUFSTARTADDR0 0x10
+#define RINGBUFWRAPADDR0 0x14
+#define FRAMEPROPERTIES0 0x18
+#define BASEADDRESS0 0x1C
+#define SOURCEBUFFERATTRIBUTES0 0x20
+#define SOURCEBUFFERDIMENSION0 0x24
+#define COLORCOMPONENTBITS0 0x28
+#define COLORCOMPONENTSHIFT0 0x2C
+#define LAYEROFFSET0 0x30
+#define CLIPWINDOWOFFSET0 0x34
+#define CLIPWINDOWDIMENSIONS0 0x38
+#define CONSTANTCOLOR0 0x3C
+#define LAYERPROPERTY0 0x40
+#define FRAMEDIMENSIONS 0x44
+#define FRAMERESAMPLING 0x48
+#define DECODECONTROL 0x4C
+#define SOURCEBUFFERLENGTH 0x50
+#define CONTROL 0x54
+#define CONTROLTRIGGER 0x58
+#define START 0x5C
+#define FETCHTYPE 0x60
+#define DECODERSTATUS 0x64
+#define READADDRESS0 0x68
+#define BURSTBUFFERPROPERTIES 0x6C
+#define STATUS 0x70
+#define HIDDENSTATUS 0x74
+
+struct dpu_fetchdecode {
+ struct dpu_fetchunit fu;
+ fetchtype_t fetchtype;
+};
+
+int fetchdecode_pixengcfg_dynamic_src_sel(struct dpu_fetchunit *fu,
+ fd_dynamic_src_sel_t src)
+{
+ int i;
+
+ mutex_lock(&fu->mutex);
+ for (i = 0; i < 4; i++) {
+ if (fd_srcs[fu->id][i] == src) {
+ dpu_pec_fu_write(fu, PIXENGCFG_DYNAMIC, src);
+ mutex_unlock(&fu->mutex);
+ return 0;
+ }
+ }
+ mutex_unlock(&fu->mutex);
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(fetchdecode_pixengcfg_dynamic_src_sel);
+
+static void
+fetchdecode_set_baseaddress(struct dpu_fetchunit *fu, unsigned int width,
+ unsigned int x_offset, unsigned int y_offset,
+ unsigned int mt_w, unsigned int mt_h,
+ int bpp, dma_addr_t baddr)
+{
+ unsigned int burst_size, stride;
+ bool nonzero_mod = !!mt_w;
+
+ if (nonzero_mod) {
+ /* consider PRG x offset to calculate buffer address */
+ baddr += (dma_addr_t)(x_offset % mt_w) * (bpp / 8);
+
+ burst_size = fetchunit_burst_size_fixup_tkt343664(baddr);
+
+ stride = width * (bpp / 8);
+ stride = fetchunit_stride_fixup_tkt339017(stride, burst_size,
+ baddr, nonzero_mod);
+
+ /* consider PRG y offset to calculate buffer address */
+ baddr += (dma_addr_t)(y_offset % mt_h) * stride;
+ }
+
+ mutex_lock(&fu->mutex);
+ dpu_fu_write(fu, BASEADDRESS0, baddr);
+ mutex_unlock(&fu->mutex);
+}
+
+static void fetchdecode_set_src_bpp(struct dpu_fetchunit *fu, int bpp)
+{
+ u32 val;
+
+ mutex_lock(&fu->mutex);
+ val = dpu_fu_read(fu, SOURCEBUFFERATTRIBUTES0);
+ val &= ~0x3f0000;
+ val |= BITSPERPIXEL(bpp);
+ dpu_fu_write(fu, SOURCEBUFFERATTRIBUTES0, val);
+ mutex_unlock(&fu->mutex);
+}
+
+static void
+fetchdecode_set_src_stride(struct dpu_fetchunit *fu,
+ unsigned int width, unsigned int x_offset,
+ unsigned int mt_w, int bpp, unsigned int stride,
+ dma_addr_t baddr, bool use_prefetch)
+{
+ unsigned int burst_size;
+ bool nonzero_mod = !!mt_w;
+ u32 val;
+
+ if (use_prefetch) {
+ /* consider PRG x offset to calculate buffer address */
+ if (nonzero_mod)
+ baddr += (dma_addr_t)(x_offset % mt_w) * (bpp / 8);
+
+ burst_size = fetchunit_burst_size_fixup_tkt343664(baddr);
+
+ stride = width * (bpp / 8);
+ stride = fetchunit_stride_fixup_tkt339017(stride, burst_size,
+ baddr, nonzero_mod);
+ }
+
+ mutex_lock(&fu->mutex);
+ val = dpu_fu_read(fu, SOURCEBUFFERATTRIBUTES0);
+ val &= ~0xffff;
+ val |= STRIDE(stride);
+ dpu_fu_write(fu, SOURCEBUFFERATTRIBUTES0, val);
+ mutex_unlock(&fu->mutex);
+}
+
+static void
+fetchdecode_set_src_buf_dimensions(struct dpu_fetchunit *fu,
+ unsigned int w, unsigned int h,
+ u32 unused, bool deinterlace)
+{
+ u32 val;
+
+ if (deinterlace)
+ h /= 2;
+
+ val = LINEWIDTH(w) | LINECOUNT(h);
+
+ mutex_lock(&fu->mutex);
+ dpu_fu_write(fu, SOURCEBUFFERDIMENSION0, val);
+ mutex_unlock(&fu->mutex);
+}
+
+static void fetchdecode_set_fmt(struct dpu_fetchunit *fu,
+ u32 fmt,
+ enum drm_color_encoding color_encoding,
+ enum drm_color_range color_range,
+ bool deinterlace)
+{
+ u32 val, bits, shift;
+ bool is_planar_yuv = false, is_rastermode_yuv422 = false;
+ bool is_yuv422upsamplingmode_interpolate = false;
+ bool is_inputselect_compact = false;
+ bool need_csc = false;
+ int i;
+
+ switch (fmt) {
+ case DRM_FORMAT_YUYV:
+ case DRM_FORMAT_UYVY:
+ is_rastermode_yuv422 = true;
+ is_yuv422upsamplingmode_interpolate = true;
+ need_csc = true;
+ break;
+ case DRM_FORMAT_NV16:
+ case DRM_FORMAT_NV61:
+ is_yuv422upsamplingmode_interpolate = true;
+ fallthrough;
+ case DRM_FORMAT_NV12:
+ case DRM_FORMAT_NV21:
+ if (deinterlace)
+ is_yuv422upsamplingmode_interpolate = true;
+ is_planar_yuv = true;
+ is_rastermode_yuv422 = true;
+ is_inputselect_compact = true;
+ need_csc = true;
+ break;
+ case DRM_FORMAT_NV24:
+ case DRM_FORMAT_NV42:
+ is_planar_yuv = true;
+ is_yuv422upsamplingmode_interpolate = true;
+ is_inputselect_compact = true;
+ need_csc = true;
+ break;
+ default:
+ break;
+ }
+
+ mutex_lock(&fu->mutex);
+ val = dpu_fu_read(fu, CONTROL);
+ val &= ~YUV422UPSAMPLINGMODE_MASK;
+ val &= ~INPUTSELECT_MASK;
+ val &= ~RASTERMODE_MASK;
+ if (is_yuv422upsamplingmode_interpolate)
+ val |= YUV422UPSAMPLINGMODE(YUV422UPSAMPLINGMODE__INTERPOLATE);
+ else
+ val |= YUV422UPSAMPLINGMODE(YUV422UPSAMPLINGMODE__REPLICATE);
+ if (is_inputselect_compact)
+ val |= INPUTSELECT(INPUTSELECT__COMPPACK);
+ else
+ val |= INPUTSELECT(INPUTSELECT__INACTIVE);
+ if (is_rastermode_yuv422)
+ val |= RASTERMODE(RASTERMODE__YUV422);
+ else
+ val |= RASTERMODE(RASTERMODE__NORMAL);
+ dpu_fu_write(fu, CONTROL, val);
+
+ val = dpu_fu_read(fu, LAYERPROPERTY0);
+ val &= ~YUVCONVERSIONMODE_MASK;
+ if (need_csc) {
+ /* assuming fetchdecode always ouputs RGB pixel formats */
+ if (color_encoding == DRM_COLOR_YCBCR_BT709)
+ val |= YUVCONVERSIONMODE(YUVCONVERSIONMODE__ITU709);
+ else if (color_encoding == DRM_COLOR_YCBCR_BT601 &&
+ color_range == DRM_COLOR_YCBCR_FULL_RANGE)
+ val |= YUVCONVERSIONMODE(YUVCONVERSIONMODE__ITU601_FR);
+ else
+ val |= YUVCONVERSIONMODE(YUVCONVERSIONMODE__ITU601);
+ } else {
+ val |= YUVCONVERSIONMODE(YUVCONVERSIONMODE__OFF);
+ }
+ dpu_fu_write(fu, LAYERPROPERTY0, val);
+ mutex_unlock(&fu->mutex);
+
+ for (i = 0; i < ARRAY_SIZE(dpu_pixel_format_matrix); i++) {
+ if (dpu_pixel_format_matrix[i].pixel_format == fmt) {
+ bits = dpu_pixel_format_matrix[i].bits;
+ shift = dpu_pixel_format_matrix[i].shift;
+
+ if (is_planar_yuv) {
+ bits &= ~(U_BITS_MASK | V_BITS_MASK);
+ shift &= ~(U_SHIFT_MASK | V_SHIFT_MASK);
+ }
+
+ mutex_lock(&fu->mutex);
+ dpu_fu_write(fu, COLORCOMPONENTBITS0, bits);
+ dpu_fu_write(fu, COLORCOMPONENTSHIFT0, shift);
+ mutex_unlock(&fu->mutex);
+ return;
+ }
+ }
+
+ WARN_ON(1);
+}
+
+void fetchdecode_layeroffset(struct dpu_fetchunit *fu, unsigned int x,
+ unsigned int y)
+{
+ u32 val;
+
+ val = LAYERXOFFSET(x) | LAYERYOFFSET(y);
+
+ mutex_lock(&fu->mutex);
+ dpu_fu_write(fu, LAYEROFFSET0, val);
+ mutex_unlock(&fu->mutex);
+}
+EXPORT_SYMBOL_GPL(fetchdecode_layeroffset);
+
+void fetchdecode_clipoffset(struct dpu_fetchunit *fu, unsigned int x,
+ unsigned int y)
+{
+ u32 val;
+
+ val = CLIPWINDOWXOFFSET(x) | CLIPWINDOWYOFFSET(y);
+
+ mutex_lock(&fu->mutex);
+ dpu_fu_write(fu, CLIPWINDOWOFFSET0, val);
+ mutex_unlock(&fu->mutex);
+}
+EXPORT_SYMBOL_GPL(fetchdecode_clipoffset);
+
+static void
+fetchdecode_set_pixel_blend_mode(struct dpu_fetchunit *fu,
+ unsigned int pixel_blend_mode, u16 alpha,
+ u32 fb_format)
+{
+ u32 mode = 0, val;
+
+ if (pixel_blend_mode == DRM_MODE_BLEND_PREMULTI ||
+ pixel_blend_mode == DRM_MODE_BLEND_COVERAGE) {
+ mode = ALPHACONSTENABLE;
+
+ switch (fb_format) {
+ case DRM_FORMAT_ARGB8888:
+ case DRM_FORMAT_ABGR8888:
+ case DRM_FORMAT_RGBA8888:
+ case DRM_FORMAT_BGRA8888:
+ mode |= ALPHASRCENABLE;
+ break;
+ }
+ }
+
+ mutex_lock(&fu->mutex);
+ val = dpu_fu_read(fu, LAYERPROPERTY0);
+ val &= ~(PREMULCONSTRGB | ALPHA_ENABLE_MASK | RGB_ENABLE_MASK);
+ val |= mode;
+ dpu_fu_write(fu, LAYERPROPERTY0, val);
+
+ val = dpu_fu_read(fu, CONSTANTCOLOR0);
+ val &= ~CONSTANTALPHA_MASK;
+ val |= CONSTANTALPHA(alpha >> 8);
+ dpu_fu_write(fu, CONSTANTCOLOR0, val);
+ mutex_unlock(&fu->mutex);
+}
+
+static void fetchdecode_enable_src_buf(struct dpu_fetchunit *fu)
+{
+ u32 val;
+
+ mutex_lock(&fu->mutex);
+ val = dpu_fu_read(fu, LAYERPROPERTY0);
+ val |= SOURCEBUFFERENABLE;
+ dpu_fu_write(fu, LAYERPROPERTY0, val);
+ mutex_unlock(&fu->mutex);
+}
+
+static void fetchdecode_disable_src_buf(struct dpu_fetchunit *fu)
+{
+ u32 val;
+
+ mutex_lock(&fu->mutex);
+ val = dpu_fu_read(fu, LAYERPROPERTY0);
+ val &= ~SOURCEBUFFERENABLE;
+ dpu_fu_write(fu, LAYERPROPERTY0, val);
+ mutex_unlock(&fu->mutex);
+}
+
+static bool fetchdecode_is_enabled(struct dpu_fetchunit *fu)
+{
+ u32 val;
+
+ mutex_lock(&fu->mutex);
+ val = dpu_fu_read(fu, LAYERPROPERTY0);
+ mutex_unlock(&fu->mutex);
+
+ return !!(val & SOURCEBUFFERENABLE);
+}
+
+void fetchdecode_clipdimensions(struct dpu_fetchunit *fu, unsigned int w,
+ unsigned int h)
+{
+ u32 val;
+
+ val = CLIPWINDOWWIDTH(w) | CLIPWINDOWHEIGHT(h);
+
+ mutex_lock(&fu->mutex);
+ dpu_fu_write(fu, CLIPWINDOWDIMENSIONS0, val);
+ mutex_unlock(&fu->mutex);
+}
+EXPORT_SYMBOL_GPL(fetchdecode_clipdimensions);
+
+static void
+fetchdecode_set_framedimensions(struct dpu_fetchunit *fu,
+ unsigned int w, unsigned int h,
+ bool deinterlace)
+{
+ u32 val;
+
+ if (deinterlace)
+ h /= 2;
+
+ val = FRAMEWIDTH(w) | FRAMEHEIGHT(h);
+
+ mutex_lock(&fu->mutex);
+ dpu_fu_write(fu, FRAMEDIMENSIONS, val);
+ mutex_unlock(&fu->mutex);
+}
+
+void fetchdecode_rgb_constantcolor(struct dpu_fetchunit *fu,
+ u8 r, u8 g, u8 b, u8 a)
+{
+ u32 val;
+
+ val = rgb_color(r, g, b, a);
+
+ mutex_lock(&fu->mutex);
+ dpu_fu_write(fu, CONSTANTCOLOR0, val);
+ mutex_unlock(&fu->mutex);
+}
+EXPORT_SYMBOL_GPL(fetchdecode_rgb_constantcolor);
+
+void fetchdecode_yuv_constantcolor(struct dpu_fetchunit *fu, u8 y, u8 u, u8 v)
+{
+ u32 val;
+
+ val = yuv_color(y, u, v);
+
+ mutex_lock(&fu->mutex);
+ dpu_fu_write(fu, CONSTANTCOLOR0, val);
+ mutex_unlock(&fu->mutex);
+}
+EXPORT_SYMBOL_GPL(fetchdecode_yuv_constantcolor);
+
+static void fetchdecode_set_controltrigger(struct dpu_fetchunit *fu)
+{
+ mutex_lock(&fu->mutex);
+ dpu_fu_write(fu, CONTROLTRIGGER, SHDTOKGEN);
+ mutex_unlock(&fu->mutex);
+}
+
+int fetchdecode_fetchtype(struct dpu_fetchunit *fu, fetchtype_t *type)
+{
+ struct dpu_soc *dpu = fu->dpu;
+ u32 val;
+
+ mutex_lock(&fu->mutex);
+ val = dpu_fu_read(fu, FETCHTYPE);
+ val &= FETCHTYPE_MASK;
+ mutex_unlock(&fu->mutex);
+
+ switch (val) {
+ case FETCHTYPE__DECODE:
+ case FETCHTYPE__LAYER:
+ case FETCHTYPE__WARP:
+ case FETCHTYPE__ECO:
+ case FETCHTYPE__PERSP:
+ case FETCHTYPE__ROT:
+ case FETCHTYPE__DECODEL:
+ case FETCHTYPE__LAYERL:
+ case FETCHTYPE__ROTL:
+ break;
+ default:
+ dev_warn(dpu->dev, "Invalid fetch type %u for FetchDecode%d\n",
+ val, fu->id);
+ return -EINVAL;
+ }
+
+ *type = val;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(fetchdecode_fetchtype);
+
+u32 fetchdecode_get_vproc_mask(struct dpu_fetchunit *fu)
+{
+ return fd_vproc_cap[fu->id];
+}
+EXPORT_SYMBOL_GPL(fetchdecode_get_vproc_mask);
+
+struct dpu_fetchunit *fetchdecode_get_fetcheco(struct dpu_fetchunit *fu)
+{
+ struct dpu_soc *dpu = fu->dpu;
+
+ switch (fu->id) {
+ case 0:
+ case 1:
+ return dpu->fe_priv[fu->id];
+ default:
+ WARN_ON(1);
+ }
+
+ return ERR_PTR(-EINVAL);
+}
+EXPORT_SYMBOL_GPL(fetchdecode_get_fetcheco);
+
+bool fetchdecode_need_fetcheco(struct dpu_fetchunit *fu, u32 fmt)
+{
+ struct dpu_fetchunit *fe = fetchdecode_get_fetcheco(fu);
+
+ if (IS_ERR_OR_NULL(fe))
+ return false;
+
+ switch (fmt) {
+ case DRM_FORMAT_NV12:
+ case DRM_FORMAT_NV21:
+ case DRM_FORMAT_NV16:
+ case DRM_FORMAT_NV61:
+ case DRM_FORMAT_NV24:
+ case DRM_FORMAT_NV42:
+ return true;
+ }
+
+ return false;
+}
+EXPORT_SYMBOL_GPL(fetchdecode_need_fetcheco);
+
+struct dpu_hscaler *fetchdecode_get_hscaler(struct dpu_fetchunit *fu)
+{
+ struct dpu_soc *dpu = fu->dpu;
+
+ switch (fu->id) {
+ case 0:
+ case 2:
+ return dpu->hs_priv[0];
+ case 1:
+ case 3:
+ return dpu->hs_priv[1];
+ default:
+ WARN_ON(1);
+ }
+
+ return ERR_PTR(-EINVAL);
+}
+EXPORT_SYMBOL_GPL(fetchdecode_get_hscaler);
+
+struct dpu_vscaler *fetchdecode_get_vscaler(struct dpu_fetchunit *fu)
+{
+ struct dpu_soc *dpu = fu->dpu;
+
+ switch (fu->id) {
+ case 0:
+ case 2:
+ return dpu->vs_priv[0];
+ case 1:
+ case 3:
+ return dpu->vs_priv[1];
+ default:
+ WARN_ON(1);
+ }
+
+ return ERR_PTR(-EINVAL);
+}
+EXPORT_SYMBOL_GPL(fetchdecode_get_vscaler);
+
+struct dpu_fetchunit *dpu_fd_get(struct dpu_soc *dpu, int id)
+{
+ struct dpu_fetchunit *fu;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(fd_ids); i++)
+ if (fd_ids[i] == id)
+ break;
+
+ if (i == ARRAY_SIZE(fd_ids))
+ return ERR_PTR(-EINVAL);
+
+ fu = dpu->fd_priv[i];
+
+ mutex_lock(&fu->mutex);
+
+ if (fu->inuse) {
+ mutex_unlock(&fu->mutex);
+ return ERR_PTR(-EBUSY);
+ }
+
+ fu->inuse = true;
+
+ mutex_unlock(&fu->mutex);
+
+ return fu;
+}
+EXPORT_SYMBOL_GPL(dpu_fd_get);
+
+void dpu_fd_put(struct dpu_fetchunit *fu)
+{
+ mutex_lock(&fu->mutex);
+
+ fu->inuse = false;
+
+ mutex_unlock(&fu->mutex);
+}
+EXPORT_SYMBOL_GPL(dpu_fd_put);
+
+static const struct dpu_fetchunit_ops fd_ops = {
+ .set_burstlength = fetchunit_set_burstlength,
+ .set_baseaddress = fetchdecode_set_baseaddress,
+ .set_src_bpp = fetchdecode_set_src_bpp,
+ .set_src_stride = fetchdecode_set_src_stride,
+ .set_src_buf_dimensions = fetchdecode_set_src_buf_dimensions,
+ .set_fmt = fetchdecode_set_fmt,
+ .set_pixel_blend_mode = fetchdecode_set_pixel_blend_mode,
+ .enable_src_buf = fetchdecode_enable_src_buf,
+ .disable_src_buf = fetchdecode_disable_src_buf,
+ .is_enabled = fetchdecode_is_enabled,
+ .set_framedimensions = fetchdecode_set_framedimensions,
+ .set_controltrigger = fetchdecode_set_controltrigger,
+ .get_stream_id = fetchunit_get_stream_id,
+ .set_stream_id = fetchunit_set_stream_id,
+};
+
+void _dpu_fd_init(struct dpu_soc *dpu, unsigned int id)
+{
+ struct dpu_fetchunit *fu;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(fd_ids); i++)
+ if (fd_ids[i] == id)
+ break;
+
+ if (WARN_ON(i == ARRAY_SIZE(fd_ids)))
+ return;
+
+ fu = dpu->fd_priv[i];
+
+ fetchdecode_pixengcfg_dynamic_src_sel(fu, FD_SRC_DISABLE);
+ fetchunit_baddr_autoupdate(fu, 0x0);
+ fetchunit_shden(fu, true);
+
+ mutex_lock(&fu->mutex);
+ dpu_fu_write(fu, BURSTBUFFERMANAGEMENT,
+ SETNUMBUFFERS(16) | SETBURSTLENGTH(16));
+ mutex_unlock(&fu->mutex);
+}
+
+int dpu_fd_init(struct dpu_soc *dpu, unsigned int id,
+ unsigned long pec_base, unsigned long base)
+{
+ struct dpu_fetchdecode *fd;
+ struct dpu_fetchunit *fu;
+ int ret;
+
+ fd = devm_kzalloc(dpu->dev, sizeof(*fd), GFP_KERNEL);
+ if (!fd)
+ return -ENOMEM;
+
+ fu = &fd->fu;
+ dpu->fd_priv[id] = fu;
+
+ fu->pec_base = devm_ioremap(dpu->dev, pec_base, SZ_16);
+ if (!fu->pec_base)
+ return -ENOMEM;
+
+ fu->base = devm_ioremap(dpu->dev, base, SZ_1K);
+ if (!fu->base)
+ return -ENOMEM;
+
+ fu->dpu = dpu;
+ fu->id = id;
+ fu->type = FU_T_FD;
+ fu->ops = &fd_ops;
+ fu->name = "fetchdecode";
+
+ mutex_init(&fu->mutex);
+
+ ret = fetchdecode_pixengcfg_dynamic_src_sel(fu, FD_SRC_DISABLE);
+ if (ret < 0)
+ return ret;
+
+ ret = fetchdecode_fetchtype(fu, &fd->fetchtype);
+ if (ret < 0)
+ return ret;
+
+ _dpu_fd_init(dpu, id);
+
+ return 0;
+}
diff --git a/drivers/gpu/imx/dpu/dpu-fetcheco.c b/drivers/gpu/imx/dpu/dpu-fetcheco.c
new file mode 100644
index 000000000000..d8bfb79e3ae9
--- /dev/null
+++ b/drivers/gpu/imx/dpu/dpu-fetcheco.c
@@ -0,0 +1,407 @@
+/*
+ * Copyright 2017-2019 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include <video/dpu.h>
+#include "dpu-prv.h"
+
+#define BASEADDRESS0 0x10
+#define SOURCEBUFFERATTRIBUTES0 0x14
+#define SOURCEBUFFERDIMENSION0 0x18
+#define COLORCOMPONENTBITS0 0x1C
+#define COLORCOMPONENTSHIFT0 0x20
+#define LAYEROFFSET0 0x24
+#define CLIPWINDOWOFFSET0 0x28
+#define CLIPWINDOWDIMENSIONS0 0x2C
+#define CONSTANTCOLOR0 0x30
+#define LAYERPROPERTY0 0x34
+#define FRAMEDIMENSIONS 0x38
+#define FRAMERESAMPLING 0x3C
+#define CONTROL 0x40
+#define CONTROLTRIGGER 0x44
+#define START 0x48
+#define FETCHTYPE 0x4C
+#define BURSTBUFFERPROPERTIES 0x50
+#define HIDDENSTATUS 0x54
+
+struct dpu_fetcheco {
+ struct dpu_fetchunit fu;
+};
+
+static void
+fetcheco_set_src_buf_dimensions(struct dpu_fetchunit *fu,
+ unsigned int w, unsigned int h,
+ u32 fmt, bool deinterlace)
+{
+ int width, height;
+ u32 val;
+
+ if (deinterlace) {
+ width = w;
+ height = h / 2;
+ } else {
+ width = dpu_format_plane_width(w, fmt, 1);
+ height = dpu_format_plane_height(h, fmt, 1);
+ }
+
+ switch (fmt) {
+ case DRM_FORMAT_NV12:
+ case DRM_FORMAT_NV21:
+ case DRM_FORMAT_NV16:
+ case DRM_FORMAT_NV61:
+ case DRM_FORMAT_NV24:
+ case DRM_FORMAT_NV42:
+ break;
+ default:
+ WARN(1, "Unsupported FetchEco pixel format 0x%08x\n", fmt);
+ return;
+ }
+
+ val = LINEWIDTH(width) | LINECOUNT(height);
+
+ mutex_lock(&fu->mutex);
+ dpu_fu_write(fu, SOURCEBUFFERDIMENSION0, val);
+ mutex_unlock(&fu->mutex);
+}
+
+static void fetcheco_set_fmt(struct dpu_fetchunit *fu,
+ u32 fmt,
+ enum drm_color_encoding unused1,
+ enum drm_color_range unused2,
+ bool unused3)
+{
+ u32 val, bits, shift;
+ int i, hsub, vsub;
+ unsigned int x, y;
+
+ switch (fmt) {
+ case DRM_FORMAT_NV12:
+ case DRM_FORMAT_NV21:
+ case DRM_FORMAT_NV16:
+ case DRM_FORMAT_NV61:
+ case DRM_FORMAT_NV24:
+ case DRM_FORMAT_NV42:
+ break;
+ default:
+ WARN(1, "Unsupported FetchEco pixel format 0x%08x\n", fmt);
+ return;
+ }
+
+ hsub = dpu_format_horz_chroma_subsampling(fmt);
+ switch (hsub) {
+ case 1:
+ x = 0x4;
+ break;
+ case 2:
+ x = 0x2;
+ break;
+ default:
+ WARN_ON(1);
+ return;
+ }
+
+ vsub = dpu_format_vert_chroma_subsampling(fmt);
+ switch (vsub) {
+ case 1:
+ y = 0x4;
+ break;
+ case 2:
+ y = 0x2;
+ break;
+ default:
+ WARN_ON(1);
+ return;
+ }
+
+ mutex_lock(&fu->mutex);
+ val = dpu_fu_read(fu, FRAMERESAMPLING);
+ val &= ~(DELTAX_MASK | DELTAY_MASK);
+ val |= DELTAX(x) | DELTAY(y);
+ dpu_fu_write(fu, FRAMERESAMPLING, val);
+
+ val = dpu_fu_read(fu, CONTROL);
+ val &= ~RASTERMODE_MASK;
+ val |= RASTERMODE(RASTERMODE__NORMAL);
+ dpu_fu_write(fu, CONTROL, val);
+ mutex_unlock(&fu->mutex);
+
+ for (i = 0; i < ARRAY_SIZE(dpu_pixel_format_matrix); i++) {
+ if (dpu_pixel_format_matrix[i].pixel_format == fmt) {
+ bits = dpu_pixel_format_matrix[i].bits;
+ shift = dpu_pixel_format_matrix[i].shift;
+
+ bits &= ~Y_BITS_MASK;
+ shift &= ~Y_SHIFT_MASK;
+
+ mutex_lock(&fu->mutex);
+ dpu_fu_write(fu, COLORCOMPONENTBITS0, bits);
+ dpu_fu_write(fu, COLORCOMPONENTSHIFT0, shift);
+ mutex_unlock(&fu->mutex);
+ return;
+ }
+ }
+
+ WARN_ON(1);
+}
+
+void fetcheco_layeroffset(struct dpu_fetchunit *fu, unsigned int x,
+ unsigned int y)
+{
+ u32 val;
+
+ val = LAYERXOFFSET(x) | LAYERYOFFSET(y);
+
+ mutex_lock(&fu->mutex);
+ dpu_fu_write(fu, LAYEROFFSET0, val);
+ mutex_unlock(&fu->mutex);
+}
+EXPORT_SYMBOL_GPL(fetcheco_layeroffset);
+
+void fetcheco_clipoffset(struct dpu_fetchunit *fu, unsigned int x,
+ unsigned int y)
+{
+ u32 val;
+
+ val = CLIPWINDOWXOFFSET(x) | CLIPWINDOWYOFFSET(y);
+
+ mutex_lock(&fu->mutex);
+ dpu_fu_write(fu, CLIPWINDOWOFFSET0, val);
+ mutex_unlock(&fu->mutex);
+}
+EXPORT_SYMBOL_GPL(fetcheco_clipoffset);
+
+void fetcheco_clipdimensions(struct dpu_fetchunit *fu, unsigned int w,
+ unsigned int h)
+{
+ u32 val;
+
+ val = CLIPWINDOWWIDTH(w) | CLIPWINDOWHEIGHT(h);
+
+ mutex_lock(&fu->mutex);
+ dpu_fu_write(fu, CLIPWINDOWDIMENSIONS0, val);
+ mutex_unlock(&fu->mutex);
+}
+EXPORT_SYMBOL_GPL(fetcheco_clipdimensions);
+
+static void
+fetcheco_set_framedimensions(struct dpu_fetchunit *fu,
+ unsigned int w, unsigned int h,
+ bool deinterlace)
+{
+ u32 val;
+
+ if (deinterlace)
+ h /= 2;
+
+ val = FRAMEWIDTH(w) | FRAMEHEIGHT(h);
+
+ mutex_lock(&fu->mutex);
+ dpu_fu_write(fu, FRAMEDIMENSIONS, val);
+ mutex_unlock(&fu->mutex);
+}
+
+void fetcheco_frameresampling(struct dpu_fetchunit *fu, unsigned int x,
+ unsigned int y)
+{
+ u32 val;
+
+ mutex_lock(&fu->mutex);
+ val = dpu_fu_read(fu, FRAMERESAMPLING);
+ val &= ~(DELTAX_MASK | DELTAY_MASK);
+ val |= DELTAX(x) | DELTAY(y);
+ dpu_fu_write(fu, FRAMERESAMPLING, val);
+ mutex_unlock(&fu->mutex);
+}
+EXPORT_SYMBOL_GPL(fetcheco_frameresampling);
+
+static void fetcheco_set_controltrigger(struct dpu_fetchunit *fu)
+{
+ mutex_lock(&fu->mutex);
+ dpu_fu_write(fu, CONTROLTRIGGER, SHDTOKGEN);
+ mutex_unlock(&fu->mutex);
+}
+
+int fetcheco_fetchtype(struct dpu_fetchunit *fu, fetchtype_t *type)
+{
+ struct dpu_soc *dpu = fu->dpu;
+ u32 val;
+
+ mutex_lock(&fu->mutex);
+ val = dpu_fu_read(fu, FETCHTYPE);
+ val &= FETCHTYPE_MASK;
+ mutex_unlock(&fu->mutex);
+
+ switch (val) {
+ case FETCHTYPE__DECODE:
+ case FETCHTYPE__LAYER:
+ case FETCHTYPE__WARP:
+ case FETCHTYPE__ECO:
+ case FETCHTYPE__PERSP:
+ case FETCHTYPE__ROT:
+ case FETCHTYPE__DECODEL:
+ case FETCHTYPE__LAYERL:
+ case FETCHTYPE__ROTL:
+ break;
+ default:
+ dev_warn(dpu->dev, "Invalid fetch type %u for FetchEco%d\n",
+ val, fu->id);
+ return -EINVAL;
+ }
+
+ *type = val;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(fetcheco_fetchtype);
+
+dpu_block_id_t fetcheco_get_block_id(struct dpu_fetchunit *fu)
+{
+ switch (fu->id) {
+ case 0:
+ return ID_FETCHECO0;
+ case 1:
+ return ID_FETCHECO1;
+ case 2:
+ return ID_FETCHECO2;
+ case 9:
+ return ID_FETCHECO9;
+ default:
+ WARN_ON(1);
+ }
+
+ return ID_NONE;
+}
+EXPORT_SYMBOL_GPL(fetcheco_get_block_id);
+
+struct dpu_fetchunit *dpu_fe_get(struct dpu_soc *dpu, int id)
+{
+ struct dpu_fetchunit *fu;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(fe_ids); i++)
+ if (fe_ids[i] == id)
+ break;
+
+ if (i == ARRAY_SIZE(fe_ids))
+ return ERR_PTR(-EINVAL);
+
+ fu = dpu->fe_priv[i];
+
+ mutex_lock(&fu->mutex);
+
+ if (fu->inuse) {
+ mutex_unlock(&fu->mutex);
+ return ERR_PTR(-EBUSY);
+ }
+
+ fu->inuse = true;
+
+ mutex_unlock(&fu->mutex);
+
+ return fu;
+}
+EXPORT_SYMBOL_GPL(dpu_fe_get);
+
+void dpu_fe_put(struct dpu_fetchunit *fu)
+{
+ mutex_lock(&fu->mutex);
+
+ fu->inuse = false;
+
+ mutex_unlock(&fu->mutex);
+}
+EXPORT_SYMBOL_GPL(dpu_fe_put);
+
+static const struct dpu_fetchunit_ops fe_ops = {
+ .set_burstlength = fetchunit_set_burstlength,
+ .set_baseaddress = fetchunit_set_baseaddress,
+ .set_src_bpp = fetchunit_set_src_bpp,
+ .set_src_stride = fetchunit_set_src_stride,
+ .set_src_buf_dimensions = fetcheco_set_src_buf_dimensions,
+ .set_fmt = fetcheco_set_fmt,
+ .enable_src_buf = fetchunit_enable_src_buf,
+ .disable_src_buf = fetchunit_disable_src_buf,
+ .is_enabled = fetchunit_is_enabled,
+ .set_framedimensions = fetcheco_set_framedimensions,
+ .set_controltrigger = fetcheco_set_controltrigger,
+ .get_stream_id = fetchunit_get_stream_id,
+ .set_stream_id = fetchunit_set_stream_id,
+};
+
+void _dpu_fe_init(struct dpu_soc *dpu, unsigned int id)
+{
+ struct dpu_fetchunit *fu;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(fe_ids); i++)
+ if (fe_ids[i] == id)
+ break;
+
+ if (WARN_ON(i == ARRAY_SIZE(fe_ids)))
+ return;
+
+ fu = dpu->fe_priv[i];
+
+ fetchunit_shden(fu, true);
+
+ mutex_lock(&fu->mutex);
+ dpu_fu_write(fu, BURSTBUFFERMANAGEMENT,
+ SETNUMBUFFERS(16) | SETBURSTLENGTH(16));
+ mutex_unlock(&fu->mutex);
+}
+
+int dpu_fe_init(struct dpu_soc *dpu, unsigned int id,
+ unsigned long pec_base, unsigned long base)
+{
+ struct dpu_fetcheco *fe;
+ struct dpu_fetchunit *fu;
+ int i;
+
+ fe = devm_kzalloc(dpu->dev, sizeof(*fe), GFP_KERNEL);
+ if (!fe)
+ return -ENOMEM;
+
+ for (i = 0; i < ARRAY_SIZE(fe_ids); i++)
+ if (fe_ids[i] == id)
+ break;
+
+ if (i == ARRAY_SIZE(fe_ids))
+ return -EINVAL;
+
+ fu = &fe->fu;
+ dpu->fe_priv[i] = fu;
+
+ fu->pec_base = devm_ioremap(dpu->dev, pec_base, SZ_16);
+ if (!fu->pec_base)
+ return -ENOMEM;
+
+ fu->base = devm_ioremap(dpu->dev, base, SZ_128);
+ if (!fu->base)
+ return -ENOMEM;
+
+ fu->dpu = dpu;
+ fu->id = id;
+ fu->type = FU_T_FE;
+ fu->ops = &fe_ops;
+ fu->name = "fetcheco";
+
+ mutex_init(&fu->mutex);
+
+ _dpu_fe_init(dpu, id);
+
+ return 0;
+}
diff --git a/drivers/gpu/imx/dpu/dpu-fetchlayer.c b/drivers/gpu/imx/dpu/dpu-fetchlayer.c
new file mode 100644
index 000000000000..3a72a8e65a17
--- /dev/null
+++ b/drivers/gpu/imx/dpu/dpu-fetchlayer.c
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2016 Freescale Semiconductor, Inc.
+ * Copyright 2017-2019 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include <video/dpu.h>
+#include "dpu-prv.h"
+
+#define PIXENGCFG_STATUS 0x8
+#define BASEADDRESS(n) (0x10 + (n) * 0x28)
+#define SOURCEBUFFERATTRIBUTES(n) (0x14 + (n) * 0x28)
+#define SOURCEBUFFERDIMENSION(n) (0x18 + (n) * 0x28)
+#define COLORCOMPONENTBITS(n) (0x1C + (n) * 0x28)
+#define COLORCOMPONENTSHIFT(n) (0x20 + (n) * 0x28)
+#define LAYEROFFSET(n) (0x24 + (n) * 0x28)
+#define CLIPWINDOWOFFSET(n) (0x28 + (n) * 0x28)
+#define CLIPWINDOWDIMENSIONS(n) (0x2C + (n) * 0x28)
+#define CONSTANTCOLOR(n) (0x30 + (n) * 0x28)
+#define LAYERPROPERTY(n) (0x34 + (n) * 0x28)
+#define FRAMEDIMENSIONS 0x150
+#define FRAMERESAMPLING 0x154
+#define CONTROL 0x158
+#define TRIGGERENABLE 0x15C
+#define SHDLDREQ(lm) ((lm) & 0xFF)
+#define CONTROLTRIGGER 0x160
+#define START 0x164
+#define FETCHTYPE 0x168
+#define BURSTBUFFERPROPERTIES 0x16C
+#define STATUS 0x170
+#define HIDDENSTATUS 0x174
+
+struct dpu_fetchlayer {
+ struct dpu_fetchunit fu;
+ fetchtype_t fetchtype;
+};
+
+static void
+fetchlayer_set_src_buf_dimensions(struct dpu_fetchunit *fu,
+ unsigned int w, unsigned int h,
+ u32 unused1, bool unused2)
+{
+ u32 val;
+
+ val = LINEWIDTH(w) | LINECOUNT(h);
+
+ mutex_lock(&fu->mutex);
+ dpu_fu_write(fu, SOURCEBUFFERDIMENSION(fu->sub_id), val);
+ mutex_unlock(&fu->mutex);
+}
+
+static void fetchlayer_set_fmt(struct dpu_fetchunit *fu,
+ u32 fmt,
+ enum drm_color_encoding color_encoding,
+ enum drm_color_range color_range,
+ bool unused)
+{
+ u32 val, bits, shift;
+ int i, sub_id = fu->sub_id;
+
+ mutex_lock(&fu->mutex);
+ val = dpu_fu_read(fu, LAYERPROPERTY(sub_id));
+ val &= ~YUVCONVERSIONMODE_MASK;
+ val |= YUVCONVERSIONMODE(YUVCONVERSIONMODE__OFF);
+ dpu_fu_write(fu, LAYERPROPERTY(sub_id), val);
+ mutex_unlock(&fu->mutex);
+
+ for (i = 0; i < ARRAY_SIZE(dpu_pixel_format_matrix); i++) {
+ if (dpu_pixel_format_matrix[i].pixel_format == fmt) {
+ bits = dpu_pixel_format_matrix[i].bits;
+ shift = dpu_pixel_format_matrix[i].shift;
+
+ mutex_lock(&fu->mutex);
+ dpu_fu_write(fu, COLORCOMPONENTBITS(sub_id), bits);
+ dpu_fu_write(fu, COLORCOMPONENTSHIFT(sub_id), shift);
+ mutex_unlock(&fu->mutex);
+ return;
+ }
+ }
+
+ WARN_ON(1);
+}
+
+static void
+fetchlayer_set_framedimensions(struct dpu_fetchunit *fu, unsigned int w,
+ unsigned int h, bool unused)
+{
+ u32 val;
+
+ val = FRAMEWIDTH(w) | FRAMEHEIGHT(h);
+
+ mutex_lock(&fu->mutex);
+ dpu_fu_write(fu, FRAMEDIMENSIONS, val);
+ mutex_unlock(&fu->mutex);
+}
+
+void fetchlayer_rgb_constantcolor(struct dpu_fetchunit *fu,
+ u8 r, u8 g, u8 b, u8 a)
+{
+ u32 val;
+
+ val = rgb_color(r, g, b, a);
+
+ mutex_lock(&fu->mutex);
+ dpu_fu_write(fu, CONSTANTCOLOR(fu->id), val);
+ mutex_unlock(&fu->mutex);
+}
+EXPORT_SYMBOL_GPL(fetchlayer_rgb_constantcolor);
+
+void fetchlayer_yuv_constantcolor(struct dpu_fetchunit *fu, u8 y, u8 u, u8 v)
+{
+ u32 val;
+
+ val = yuv_color(y, u, v);
+
+ mutex_lock(&fu->mutex);
+ dpu_fu_write(fu, CONSTANTCOLOR(fu->id), val);
+ mutex_unlock(&fu->mutex);
+}
+EXPORT_SYMBOL_GPL(fetchlayer_yuv_constantcolor);
+
+static void fetchlayer_set_controltrigger(struct dpu_fetchunit *fu)
+{
+ mutex_lock(&fu->mutex);
+ dpu_fu_write(fu, CONTROLTRIGGER, SHDTOKGEN);
+ mutex_unlock(&fu->mutex);
+}
+
+int fetchlayer_fetchtype(struct dpu_fetchunit *fu, fetchtype_t *type)
+{
+ struct dpu_soc *dpu = fu->dpu;
+ u32 val;
+
+ mutex_lock(&fu->mutex);
+ val = dpu_fu_read(fu, FETCHTYPE);
+ val &= FETCHTYPE_MASK;
+ mutex_unlock(&fu->mutex);
+
+ switch (val) {
+ case FETCHTYPE__DECODE:
+ case FETCHTYPE__LAYER:
+ case FETCHTYPE__WARP:
+ case FETCHTYPE__ECO:
+ case FETCHTYPE__PERSP:
+ case FETCHTYPE__ROT:
+ case FETCHTYPE__DECODEL:
+ case FETCHTYPE__LAYERL:
+ case FETCHTYPE__ROTL:
+ break;
+ default:
+ dev_warn(dpu->dev, "Invalid fetch type %u for FetchLayer%d\n",
+ val, fu->id);
+ return -EINVAL;
+ }
+
+ *type = val;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(fetchlayer_fetchtype);
+
+struct dpu_fetchunit *dpu_fl_get(struct dpu_soc *dpu, int id)
+{
+ struct dpu_fetchunit *fu;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(fl_ids); i++)
+ if (fl_ids[i] == id)
+ break;
+
+ if (i == ARRAY_SIZE(fl_ids))
+ return ERR_PTR(-EINVAL);
+
+ fu = dpu->fl_priv[i];
+
+ mutex_lock(&fu->mutex);
+
+ if (fu->inuse) {
+ mutex_unlock(&fu->mutex);
+ return ERR_PTR(-EBUSY);
+ }
+
+ fu->inuse = true;
+
+ mutex_unlock(&fu->mutex);
+
+ return fu;
+}
+EXPORT_SYMBOL_GPL(dpu_fl_get);
+
+void dpu_fl_put(struct dpu_fetchunit *fu)
+{
+ mutex_lock(&fu->mutex);
+
+ fu->inuse = false;
+
+ mutex_unlock(&fu->mutex);
+}
+EXPORT_SYMBOL_GPL(dpu_fl_put);
+
+static const struct dpu_fetchunit_ops fl_ops = {
+ .set_burstlength = fetchunit_set_burstlength,
+ .set_baseaddress = fetchunit_set_baseaddress,
+ .set_src_bpp = fetchunit_set_src_bpp,
+ .set_src_stride = fetchunit_set_src_stride,
+ .set_src_buf_dimensions = fetchlayer_set_src_buf_dimensions,
+ .set_fmt = fetchlayer_set_fmt,
+ .set_pixel_blend_mode = fetchunit_set_pixel_blend_mode,
+ .enable_src_buf = fetchunit_enable_src_buf,
+ .disable_src_buf = fetchunit_disable_src_buf,
+ .is_enabled = fetchunit_is_enabled,
+ .set_framedimensions = fetchlayer_set_framedimensions,
+ .set_controltrigger = fetchlayer_set_controltrigger,
+ .get_stream_id = fetchunit_get_stream_id,
+ .set_stream_id = fetchunit_set_stream_id,
+};
+
+void _dpu_fl_init(struct dpu_soc *dpu, unsigned int id)
+{
+ struct dpu_fetchunit *fu;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(fl_ids); i++)
+ if (fl_ids[i] == id)
+ break;
+
+ if (WARN_ON(i == ARRAY_SIZE(fl_ids)))
+ return;
+
+ fu = dpu->fl_priv[i];
+
+ fetchunit_baddr_autoupdate(fu, 0x0);
+ fetchunit_shden(fu, true);
+ fetchunit_shdldreq_sticky(fu, 0xFF);
+ fetchunit_disable_src_buf(fu);
+
+ mutex_lock(&fu->mutex);
+ dpu_fu_write(fu, BURSTBUFFERMANAGEMENT,
+ SETNUMBUFFERS(16) | SETBURSTLENGTH(16));
+ mutex_unlock(&fu->mutex);
+}
+
+int dpu_fl_init(struct dpu_soc *dpu, unsigned int id,
+ unsigned long pec_base, unsigned long base)
+{
+ struct dpu_fetchlayer *fl;
+ struct dpu_fetchunit *fu;
+ int ret;
+
+ fl = devm_kzalloc(dpu->dev, sizeof(*fl), GFP_KERNEL);
+ if (!fl)
+ return -ENOMEM;
+
+ fu = &fl->fu;
+ dpu->fl_priv[id] = fu;
+
+ fu->pec_base = devm_ioremap(dpu->dev, base, SZ_16);
+ if (!fu->pec_base)
+ return -ENOMEM;
+
+ fu->base = devm_ioremap(dpu->dev, base, SZ_512);
+ if (!fu->base)
+ return -ENOMEM;
+
+ fu->dpu = dpu;
+ fu->id = id;
+ fu->sub_id = 0;
+ fu->type = FU_T_FL;
+ fu->ops = &fl_ops;
+ fu->name = "fetchlayer";
+
+ mutex_init(&fu->mutex);
+
+ ret = fetchlayer_fetchtype(fu, &fl->fetchtype);
+ if (ret < 0)
+ return ret;
+
+ _dpu_fl_init(dpu, id);
+
+ return 0;
+}
diff --git a/drivers/gpu/imx/dpu/dpu-fetchunit.c b/drivers/gpu/imx/dpu/dpu-fetchunit.c
new file mode 100644
index 000000000000..e6a49874ac02
--- /dev/null
+++ b/drivers/gpu/imx/dpu/dpu-fetchunit.c
@@ -0,0 +1,346 @@
+/*
+ * Copyright 2018-2019,2021 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <drm/drm_blend.h>
+#include <video/dpu.h>
+#include "dpu-prv.h"
+
+#define BASEADDRESS(n) (0x10 + (n) * 0x28)
+#define SOURCEBUFFERATTRIBUTES(n) (0x14 + (n) * 0x28)
+#define SOURCEBUFFERDIMENSION(n) (0x18 + (n) * 0x28)
+#define COLORCOMPONENTBITS(n) (0x1C + (n) * 0x28)
+#define COLORCOMPONENTSHIFT(n) (0x20 + (n) * 0x28)
+#define LAYEROFFSET(n) (0x24 + (n) * 0x28)
+#define CLIPWINDOWOFFSET(n) (0x28 + (n) * 0x28)
+#define CLIPWINDOWDIMENSIONS(n) (0x2C + (n) * 0x28)
+#define CONSTANTCOLOR(n) (0x30 + (n) * 0x28)
+#define LAYERPROPERTY(n) (0x34 + (n) * 0x28)
+
+/* base address has to align to burst size */
+unsigned int fetchunit_burst_size_fixup_tkt343664(dma_addr_t baddr)
+{
+ unsigned int burst_size;
+
+ burst_size = 1 << (ffs(baddr) - 1);
+ burst_size = round_up(burst_size, 8);
+ burst_size = min(burst_size, 128U);
+
+ return burst_size;
+}
+EXPORT_SYMBOL_GPL(fetchunit_burst_size_fixup_tkt343664);
+
+/* fixup for burst size vs stride mismatch */
+unsigned int
+fetchunit_stride_fixup_tkt339017(unsigned int stride, unsigned int burst_size,
+ dma_addr_t baddr, bool nonzero_mod)
+{
+ if (nonzero_mod)
+ stride = round_up(stride + round_up(baddr % 8, 8), burst_size);
+ else
+ stride = round_up(stride, burst_size);
+
+ return stride;
+}
+EXPORT_SYMBOL_GPL(fetchunit_stride_fixup_tkt339017);
+
+void fetchunit_get_dprc(struct dpu_fetchunit *fu, void *data)
+{
+ if (WARN_ON(!fu))
+ return;
+
+ fu->dprc = data;
+}
+EXPORT_SYMBOL_GPL(fetchunit_get_dprc);
+
+void fetchunit_shden(struct dpu_fetchunit *fu, bool enable)
+{
+ u32 val;
+
+ mutex_lock(&fu->mutex);
+ val = dpu_fu_read(fu, STATICCONTROL);
+ if (enable)
+ val |= SHDEN;
+ else
+ val &= ~SHDEN;
+ dpu_fu_write(fu, STATICCONTROL, val);
+ mutex_unlock(&fu->mutex);
+}
+EXPORT_SYMBOL_GPL(fetchunit_shden);
+
+void fetchunit_baddr_autoupdate(struct dpu_fetchunit *fu, u8 layer_mask)
+{
+ u32 val;
+
+ mutex_lock(&fu->mutex);
+ val = dpu_fu_read(fu, STATICCONTROL);
+ val &= ~BASEADDRESSAUTOUPDATE_MASK;
+ val |= BASEADDRESSAUTOUPDATE(layer_mask);
+ dpu_fu_write(fu, STATICCONTROL, val);
+ mutex_unlock(&fu->mutex);
+}
+EXPORT_SYMBOL_GPL(fetchunit_baddr_autoupdate);
+
+void fetchunit_shdldreq_sticky(struct dpu_fetchunit *fu, u8 layer_mask)
+{
+ u32 val;
+
+ mutex_lock(&fu->mutex);
+ val = dpu_fu_read(fu, STATICCONTROL);
+ val &= ~SHDLDREQSTICKY_MASK;
+ val |= SHDLDREQSTICKY(layer_mask);
+ dpu_fu_write(fu, STATICCONTROL, val);
+ mutex_unlock(&fu->mutex);
+}
+EXPORT_SYMBOL_GPL(fetchunit_shdldreq_sticky);
+
+void fetchunit_set_burstlength(struct dpu_fetchunit *fu,
+ unsigned int x_offset, unsigned int mt_w,
+ int bpp, dma_addr_t baddr, bool use_prefetch)
+{
+ struct dpu_soc *dpu = fu->dpu;
+ unsigned int burst_size, burst_length;
+ bool nonzero_mod = !!mt_w;
+ u32 val;
+
+ if (use_prefetch) {
+ /* consider PRG x offset to calculate buffer address */
+ if (nonzero_mod)
+ baddr += (dma_addr_t)(x_offset % mt_w) * (bpp / 8);
+
+ burst_size = fetchunit_burst_size_fixup_tkt343664(baddr);
+ burst_length = burst_size / 8;
+ } else {
+ burst_length = 16;
+ }
+
+ mutex_lock(&fu->mutex);
+ val = dpu_fu_read(fu, BURSTBUFFERMANAGEMENT);
+ val &= ~SETBURSTLENGTH_MASK;
+ val |= SETBURSTLENGTH(burst_length);
+ dpu_fu_write(fu, BURSTBUFFERMANAGEMENT, val);
+ mutex_unlock(&fu->mutex);
+
+ dev_dbg(dpu->dev, "%s%d burst length is %u\n",
+ fu->name, fu->id, burst_length);
+}
+EXPORT_SYMBOL_GPL(fetchunit_set_burstlength);
+
+void fetchunit_set_baseaddress(struct dpu_fetchunit *fu, unsigned int width,
+ unsigned int x_offset, unsigned int y_offset,
+ unsigned int mt_w, unsigned int mt_h,
+ int bpp, dma_addr_t baddr)
+{
+ unsigned int burst_size, stride;
+ bool nonzero_mod = !!mt_w;
+
+ if (nonzero_mod) {
+ /* consider PRG x offset to calculate buffer address */
+ baddr += (dma_addr_t)(x_offset % mt_w) * (bpp / 8);
+
+ burst_size = fetchunit_burst_size_fixup_tkt343664(baddr);
+
+ stride = width * (bpp / 8);
+ stride = fetchunit_stride_fixup_tkt339017(stride, burst_size,
+ baddr, nonzero_mod);
+
+ /* consider PRG y offset to calculate buffer address */
+ baddr += (dma_addr_t)(y_offset % mt_h) * stride;
+ }
+
+ mutex_lock(&fu->mutex);
+ dpu_fu_write(fu, BASEADDRESS(fu->sub_id), baddr);
+ mutex_unlock(&fu->mutex);
+}
+EXPORT_SYMBOL_GPL(fetchunit_set_baseaddress);
+
+void fetchunit_set_src_bpp(struct dpu_fetchunit *fu, int bpp)
+{
+ u32 val;
+
+ mutex_lock(&fu->mutex);
+ val = dpu_fu_read(fu, SOURCEBUFFERATTRIBUTES(fu->sub_id));
+ val &= ~0x3f0000;
+ val |= BITSPERPIXEL(bpp);
+ dpu_fu_write(fu, SOURCEBUFFERATTRIBUTES(fu->sub_id), val);
+ mutex_unlock(&fu->mutex);
+}
+EXPORT_SYMBOL_GPL(fetchunit_set_src_bpp);
+
+/*
+ * The arguments width and bpp are valid only when use_prefetch is true.
+ * For fetcheco, since the pixel format has to be NV12 or NV21 when
+ * use_prefetch is true, we assume width stands for how many UV we have
+ * in bytes for one line, while bpp should be 8bits for every U or V component.
+ */
+void fetchunit_set_src_stride(struct dpu_fetchunit *fu,
+ unsigned int width, unsigned int x_offset,
+ unsigned int mt_w, int bpp, unsigned int stride,
+ dma_addr_t baddr, bool use_prefetch)
+{
+ unsigned int burst_size;
+ bool nonzero_mod = !!mt_w;
+ u32 val;
+
+ if (use_prefetch) {
+ /* consider PRG x offset to calculate buffer address */
+ if (nonzero_mod)
+ baddr += (dma_addr_t)(x_offset % mt_w) * (bpp / 8);
+
+ burst_size = fetchunit_burst_size_fixup_tkt343664(baddr);
+
+ stride = width * (bpp / 8);
+ stride = fetchunit_stride_fixup_tkt339017(stride, burst_size,
+ baddr, nonzero_mod);
+ }
+
+ mutex_lock(&fu->mutex);
+ val = dpu_fu_read(fu, SOURCEBUFFERATTRIBUTES(fu->sub_id));
+ val &= ~0xffff;
+ val |= STRIDE(stride);
+ dpu_fu_write(fu, SOURCEBUFFERATTRIBUTES(fu->sub_id), val);
+ mutex_unlock(&fu->mutex);
+}
+EXPORT_SYMBOL_GPL(fetchunit_set_src_stride);
+
+void fetchunit_set_pixel_blend_mode(struct dpu_fetchunit *fu,
+ unsigned int pixel_blend_mode, u16 alpha,
+ u32 fb_format)
+{
+ u32 mode = 0, val;
+
+ if (pixel_blend_mode == DRM_MODE_BLEND_PREMULTI ||
+ pixel_blend_mode == DRM_MODE_BLEND_COVERAGE) {
+ mode = ALPHACONSTENABLE;
+
+ switch (fb_format) {
+ case DRM_FORMAT_ARGB8888:
+ case DRM_FORMAT_ABGR8888:
+ case DRM_FORMAT_RGBA8888:
+ case DRM_FORMAT_BGRA8888:
+ mode |= ALPHASRCENABLE;
+ break;
+ }
+ }
+
+ mutex_lock(&fu->mutex);
+ val = dpu_fu_read(fu, LAYERPROPERTY(fu->sub_id));
+ val &= ~(PREMULCONSTRGB | ALPHA_ENABLE_MASK | RGB_ENABLE_MASK);
+ val |= mode;
+ dpu_fu_write(fu, LAYERPROPERTY(fu->sub_id), val);
+
+ val = dpu_fu_read(fu, CONSTANTCOLOR(fu->sub_id));
+ val &= ~CONSTANTALPHA_MASK;
+ val |= CONSTANTALPHA(alpha >> 8);
+ dpu_fu_write(fu, CONSTANTCOLOR(fu->sub_id), val);
+ mutex_unlock(&fu->mutex);
+}
+EXPORT_SYMBOL_GPL(fetchunit_set_pixel_blend_mode);
+
+void fetchunit_enable_src_buf(struct dpu_fetchunit *fu)
+{
+ u32 val;
+
+ mutex_lock(&fu->mutex);
+ val = dpu_fu_read(fu, LAYERPROPERTY(fu->sub_id));
+ val |= SOURCEBUFFERENABLE;
+ dpu_fu_write(fu, LAYERPROPERTY(fu->sub_id), val);
+ mutex_unlock(&fu->mutex);
+}
+EXPORT_SYMBOL_GPL(fetchunit_enable_src_buf);
+
+void fetchunit_disable_src_buf(struct dpu_fetchunit *fu)
+{
+ u32 val;
+
+ mutex_lock(&fu->mutex);
+ val = dpu_fu_read(fu, LAYERPROPERTY(fu->sub_id));
+ val &= ~SOURCEBUFFERENABLE;
+ dpu_fu_write(fu, LAYERPROPERTY(fu->sub_id), val);
+ mutex_unlock(&fu->mutex);
+}
+EXPORT_SYMBOL_GPL(fetchunit_disable_src_buf);
+
+bool fetchunit_is_enabled(struct dpu_fetchunit *fu)
+{
+ u32 val;
+
+ mutex_lock(&fu->mutex);
+ val = dpu_fu_read(fu, LAYERPROPERTY(fu->sub_id));
+ mutex_unlock(&fu->mutex);
+
+ return !!(val & SOURCEBUFFERENABLE);
+}
+EXPORT_SYMBOL_GPL(fetchunit_is_enabled);
+
+unsigned int fetchunit_get_stream_id(struct dpu_fetchunit *fu)
+{
+ if (WARN_ON(!fu))
+ return DPU_PLANE_SRC_DISABLED;
+
+ return fu->stream_id;
+}
+EXPORT_SYMBOL_GPL(fetchunit_get_stream_id);
+
+void fetchunit_set_stream_id(struct dpu_fetchunit *fu, unsigned int id)
+{
+ if (WARN_ON(!fu))
+ return;
+
+ switch (id) {
+ case DPU_PLANE_SRC_TO_DISP_STREAM0:
+ case DPU_PLANE_SRC_TO_DISP_STREAM1:
+ case DPU_PLANE_SRC_DISABLED:
+ fu->stream_id = id;
+ break;
+ default:
+ WARN_ON(1);
+ }
+}
+EXPORT_SYMBOL_GPL(fetchunit_set_stream_id);
+
+bool fetchunit_is_fetchdecode(struct dpu_fetchunit *fu)
+{
+ if (WARN_ON(!fu))
+ return false;
+
+ return fu->type == FU_T_FD;
+}
+EXPORT_SYMBOL_GPL(fetchunit_is_fetchdecode);
+
+bool fetchunit_is_fetcheco(struct dpu_fetchunit *fu)
+{
+ if (WARN_ON(!fu))
+ return false;
+
+ return fu->type == FU_T_FE;
+}
+EXPORT_SYMBOL_GPL(fetchunit_is_fetcheco);
+
+bool fetchunit_is_fetchlayer(struct dpu_fetchunit *fu)
+{
+ if (WARN_ON(!fu))
+ return false;
+
+ return fu->type == FU_T_FL;
+}
+EXPORT_SYMBOL_GPL(fetchunit_is_fetchlayer);
+
+bool fetchunit_is_fetchwarp(struct dpu_fetchunit *fu)
+{
+ if (WARN_ON(!fu))
+ return false;
+
+ return fu->type == FU_T_FW;
+}
+EXPORT_SYMBOL_GPL(fetchunit_is_fetchwarp);
diff --git a/drivers/gpu/imx/dpu/dpu-fetchwarp.c b/drivers/gpu/imx/dpu/dpu-fetchwarp.c
new file mode 100644
index 000000000000..953368e3fade
--- /dev/null
+++ b/drivers/gpu/imx/dpu/dpu-fetchwarp.c
@@ -0,0 +1,305 @@
+/*
+ * Copyright 2018-2019 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include <video/dpu.h>
+#include "dpu-prv.h"
+
+#define PIXENGCFG_STATUS 0x8
+#define BASEADDRESS(n) (0x10 + (n) * 0x28)
+#define SOURCEBUFFERATTRIBUTES(n) (0x14 + (n) * 0x28)
+#define SOURCEBUFFERDIMENSION(n) (0x18 + (n) * 0x28)
+#define COLORCOMPONENTBITS(n) (0x1C + (n) * 0x28)
+#define COLORCOMPONENTSHIFT(n) (0x20 + (n) * 0x28)
+#define LAYEROFFSET(n) (0x24 + (n) * 0x28)
+#define CLIPWINDOWOFFSET(n) (0x28 + (n) * 0x28)
+#define CLIPWINDOWDIMENSIONS(n) (0x2C + (n) * 0x28)
+#define CONSTANTCOLOR(n) (0x30 + (n) * 0x28)
+#define LAYERPROPERTY(n) (0x34 + (n) * 0x28)
+#define FRAMEDIMENSIONS 0x150
+#define FRAMERESAMPLING 0x154
+#define WARPCONTROL 0x158
+#define ARBSTARTX 0x15c
+#define ARBSTARTY 0x160
+#define ARBDELTA 0x164
+#define FIRPOSITIONS 0x168
+#define FIRCOEFFICIENTS 0x16c
+#define CONTROL 0x170
+#define TRIGGERENABLE 0x174
+#define SHDLDREQ(lm) ((lm) & 0xFF)
+#define CONTROLTRIGGER 0x178
+#define START 0x17c
+#define FETCHTYPE 0x180
+#define BURSTBUFFERPROPERTIES 0x184
+#define STATUS 0x188
+#define HIDDENSTATUS 0x18c
+
+struct dpu_fetchwarp {
+ struct dpu_fetchunit fu;
+ fetchtype_t fetchtype;
+};
+
+static void
+fetchwarp_set_src_buf_dimensions(struct dpu_fetchunit *fu,
+ unsigned int w, unsigned int h,
+ u32 unused1, bool unused2)
+{
+ u32 val;
+
+ val = LINEWIDTH(w) | LINECOUNT(h);
+
+ mutex_lock(&fu->mutex);
+ dpu_fu_write(fu, SOURCEBUFFERDIMENSION(fu->sub_id), val);
+ mutex_unlock(&fu->mutex);
+}
+
+static void fetchwarp_set_fmt(struct dpu_fetchunit *fu,
+ u32 fmt,
+ enum drm_color_encoding color_encoding,
+ enum drm_color_range color_range,
+ bool unused)
+{
+ u32 val, bits, shift;
+ int i, sub_id = fu->sub_id;
+
+ mutex_lock(&fu->mutex);
+ val = dpu_fu_read(fu, LAYERPROPERTY(sub_id));
+ val &= ~YUVCONVERSIONMODE_MASK;
+ dpu_fu_write(fu, LAYERPROPERTY(sub_id), val);
+ mutex_unlock(&fu->mutex);
+
+ for (i = 0; i < ARRAY_SIZE(dpu_pixel_format_matrix); i++) {
+ if (dpu_pixel_format_matrix[i].pixel_format == fmt) {
+ bits = dpu_pixel_format_matrix[i].bits;
+ shift = dpu_pixel_format_matrix[i].shift;
+
+ mutex_lock(&fu->mutex);
+ dpu_fu_write(fu, COLORCOMPONENTBITS(sub_id), bits);
+ dpu_fu_write(fu, COLORCOMPONENTSHIFT(sub_id), shift);
+ mutex_unlock(&fu->mutex);
+ return;
+ }
+ }
+
+ WARN_ON(1);
+}
+
+static void
+fetchwarp_set_framedimensions(struct dpu_fetchunit *fu,
+ unsigned int w, unsigned int h, bool unused)
+{
+ u32 val;
+
+ val = FRAMEWIDTH(w) | FRAMEHEIGHT(h);
+
+ mutex_lock(&fu->mutex);
+ dpu_fu_write(fu, FRAMEDIMENSIONS, val);
+ mutex_unlock(&fu->mutex);
+}
+
+void fetchwarp_rgb_constantcolor(struct dpu_fetchunit *fu,
+ u8 r, u8 g, u8 b, u8 a)
+{
+ u32 val;
+
+ val = rgb_color(r, g, b, a);
+
+ mutex_lock(&fu->mutex);
+ dpu_fu_write(fu, CONSTANTCOLOR(fu->id), val);
+ mutex_unlock(&fu->mutex);
+}
+EXPORT_SYMBOL_GPL(fetchwarp_rgb_constantcolor);
+
+void fetchwarp_yuv_constantcolor(struct dpu_fetchunit *fu, u8 y, u8 u, u8 v)
+{
+ u32 val;
+
+ val = yuv_color(y, u, v);
+
+ mutex_lock(&fu->mutex);
+ dpu_fu_write(fu, CONSTANTCOLOR(fu->id), val);
+ mutex_unlock(&fu->mutex);
+}
+EXPORT_SYMBOL_GPL(fetchwarp_yuv_constantcolor);
+
+static void fetchwarp_set_controltrigger(struct dpu_fetchunit *fu)
+{
+ mutex_lock(&fu->mutex);
+ dpu_fu_write(fu, CONTROLTRIGGER, SHDTOKGEN);
+ mutex_unlock(&fu->mutex);
+}
+
+int fetchwarp_fetchtype(struct dpu_fetchunit *fu, fetchtype_t *type)
+{
+ struct dpu_soc *dpu = fu->dpu;
+ u32 val;
+
+ mutex_lock(&fu->mutex);
+ val = dpu_fu_read(fu, FETCHTYPE);
+ val &= FETCHTYPE_MASK;
+ mutex_unlock(&fu->mutex);
+
+ switch (val) {
+ case FETCHTYPE__DECODE:
+ case FETCHTYPE__LAYER:
+ case FETCHTYPE__WARP:
+ case FETCHTYPE__ECO:
+ case FETCHTYPE__PERSP:
+ case FETCHTYPE__ROT:
+ case FETCHTYPE__DECODEL:
+ case FETCHTYPE__LAYERL:
+ case FETCHTYPE__ROTL:
+ break;
+ default:
+ dev_warn(dpu->dev, "Invalid fetch type %u for FetchWarp%d\n",
+ val, fu->id);
+ return -EINVAL;
+ }
+
+ *type = val;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(fetchwarp_fetchtype);
+
+struct dpu_fetchunit *dpu_fw_get(struct dpu_soc *dpu, int id)
+{
+ struct dpu_fetchunit *fu;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(fw_ids); i++)
+ if (fw_ids[i] == id)
+ break;
+
+ if (i == ARRAY_SIZE(fw_ids))
+ return ERR_PTR(-EINVAL);
+
+ fu = dpu->fw_priv[i];
+
+ mutex_lock(&fu->mutex);
+
+ if (fu->inuse) {
+ mutex_unlock(&fu->mutex);
+ return ERR_PTR(-EBUSY);
+ }
+
+ fu->inuse = true;
+
+ mutex_unlock(&fu->mutex);
+
+ return fu;
+}
+EXPORT_SYMBOL_GPL(dpu_fw_get);
+
+void dpu_fw_put(struct dpu_fetchunit *fu)
+{
+ mutex_lock(&fu->mutex);
+
+ fu->inuse = false;
+
+ mutex_unlock(&fu->mutex);
+}
+EXPORT_SYMBOL_GPL(dpu_fw_put);
+
+static const struct dpu_fetchunit_ops fw_ops = {
+ .set_burstlength = fetchunit_set_burstlength,
+ .set_baseaddress = fetchunit_set_baseaddress,
+ .set_src_bpp = fetchunit_set_src_bpp,
+ .set_src_stride = fetchunit_set_src_stride,
+ .set_src_buf_dimensions = fetchwarp_set_src_buf_dimensions,
+ .set_fmt = fetchwarp_set_fmt,
+ .set_pixel_blend_mode = fetchunit_set_pixel_blend_mode,
+ .enable_src_buf = fetchunit_enable_src_buf,
+ .disable_src_buf = fetchunit_disable_src_buf,
+ .is_enabled = fetchunit_is_enabled,
+ .set_framedimensions = fetchwarp_set_framedimensions,
+ .set_controltrigger = fetchwarp_set_controltrigger,
+ .get_stream_id = fetchunit_get_stream_id,
+ .set_stream_id = fetchunit_set_stream_id,
+};
+
+void _dpu_fw_init(struct dpu_soc *dpu, unsigned int id)
+{
+ struct dpu_fetchunit *fu;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(fw_ids); i++)
+ if (fw_ids[i] == id)
+ break;
+
+ if (WARN_ON(i == ARRAY_SIZE(fw_ids)))
+ return;
+
+ fu = dpu->fw_priv[i];
+
+ fetchunit_baddr_autoupdate(fu, 0x0);
+ fetchunit_shden(fu, true);
+ fetchunit_shdldreq_sticky(fu, 0xFF);
+ fetchunit_disable_src_buf(fu);
+
+ mutex_lock(&fu->mutex);
+ dpu_fu_write(fu, BURSTBUFFERMANAGEMENT,
+ SETNUMBUFFERS(16) | SETBURSTLENGTH(16));
+ mutex_unlock(&fu->mutex);
+}
+
+int dpu_fw_init(struct dpu_soc *dpu, unsigned int id,
+ unsigned long pec_base, unsigned long base)
+{
+ struct dpu_fetchwarp *fw;
+ struct dpu_fetchunit *fu;
+ int i, ret;
+
+ fw = devm_kzalloc(dpu->dev, sizeof(*fw), GFP_KERNEL);
+ if (!fw)
+ return -ENOMEM;
+
+ for (i = 0; i < ARRAY_SIZE(fw_ids); i++)
+ if (fw_ids[i] == id)
+ break;
+
+ if (i == ARRAY_SIZE(fw_ids))
+ return -EINVAL;
+
+ fu = &fw->fu;
+ dpu->fw_priv[i] = fu;
+
+ fu->pec_base = devm_ioremap(dpu->dev, base, SZ_16);
+ if (!fu->pec_base)
+ return -ENOMEM;
+
+ fu->base = devm_ioremap(dpu->dev, base, SZ_512);
+ if (!fu->base)
+ return -ENOMEM;
+
+ fu->dpu = dpu;
+ fu->id = id;
+ fu->sub_id = 0;
+ fu->type = FU_T_FW;
+ fu->ops = &fw_ops;
+ fu->name = "fetchwarp";
+
+ mutex_init(&fu->mutex);
+
+ ret = fetchwarp_fetchtype(fu, &fw->fetchtype);
+ if (ret < 0)
+ return ret;
+
+ _dpu_fw_init(dpu, id);
+
+ return 0;
+}
diff --git a/drivers/gpu/imx/dpu/dpu-framegen.c b/drivers/gpu/imx/dpu/dpu-framegen.c
new file mode 100644
index 000000000000..50d7d50e1ff8
--- /dev/null
+++ b/drivers/gpu/imx/dpu/dpu-framegen.c
@@ -0,0 +1,601 @@
+/*
+ * Copyright (C) 2016 Freescale Semiconductor, Inc.
+ * Copyright 2017-2020 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include <drm/drm_mode.h>
+#include <video/dpu.h>
+#include "dpu-prv.h"
+
+#define FGSTCTRL 0x8
+#define FGSYNCMODE_MASK 0x6
+#define HTCFG1 0xC
+#define HTOTAL(n) ((((n) - 1) & 0x3FFF) << 16)
+#define HACT(n) ((n) & 0x3FFF)
+#define HTCFG2 0x10
+#define HSEN BIT(31)
+#define HSBP(n) ((((n) - 1) & 0x3FFF) << 16)
+#define HSYNC(n) (((n) - 1) & 0x3FFF)
+#define VTCFG1 0x14
+#define VTOTAL(n) ((((n) - 1) & 0x3FFF) << 16)
+#define VACT(n) ((n) & 0x3FFF)
+#define VTCFG2 0x18
+#define VSEN BIT(31)
+#define VSBP(n) ((((n) - 1) & 0x3FFF) << 16)
+#define VSYNC(n) (((n) - 1) & 0x3FFF)
+#define INTCONFIG(n) (0x1C + 4 * (n))
+#define EN BIT(31)
+#define ROW(n) (((n) & 0x3FFF) << 16)
+#define COL(n) ((n) & 0x3FFF)
+#define PKICKCONFIG 0x2C
+#define SKICKCONFIG 0x30
+#define SECSTATCONFIG 0x34
+#define FGSRCR1 0x38
+#define FGSRCR2 0x3C
+#define FGSRCR3 0x40
+#define FGSRCR4 0x44
+#define FGSRCR5 0x48
+#define FGSRCR6 0x4C
+#define FGKSDR 0x50
+#define PACFG 0x54
+#define STARTX(n) (((n) + 1) & 0x3FFF)
+#define STARTY(n) (((((n) + 1) & 0x3FFF)) << 16)
+#define SACFG 0x58
+#define FGINCTRL 0x5C
+#define FGDM_MASK 0x7
+#define ENPRIMALPHA BIT(3)
+#define ENSECALPHA BIT(4)
+#define FGINCTRLPANIC 0x60
+#define FGCCR 0x64
+#define CCALPHA(a) (((a) & 0x1) << 30)
+#define CCRED(r) (((r) & 0x3FF) << 20)
+#define CCGREEN(g) (((g) & 0x3FF) << 10)
+#define CCBLUE(b) ((b) & 0x3FF)
+#define FGENABLE 0x68
+#define FGEN BIT(0)
+#define FGSLR 0x6C
+#define FGENSTS 0x70
+#define ENSTS BIT(0)
+#define FGTIMESTAMP 0x74
+#define LINEINDEX_MASK 0x3FFF
+#define LINEINDEX_SHIFT 0
+#define FRAMEINDEX_MASK 0xFFFFC000
+#define FRAMEINDEX_SHIFT 14
+#define FGCHSTAT 0x78
+#define SECSYNCSTAT BIT(24)
+#define SFIFOEMPTY BIT(16)
+#define FGCHSTATCLR 0x7C
+#define CLRSECSTAT BIT(16)
+#define FGSKEWMON 0x80
+#define FGSFIFOMIN 0x84
+#define FGSFIFOMAX 0x88
+#define FGSFIFOFILLCLR 0x8C
+#define FGSREPD 0x90
+#define FGSRFTD 0x94
+
+#define KHZ 1000
+#define PLL_MIN_FREQ_HZ 648000000
+
+struct dpu_framegen {
+ void __iomem *base;
+ struct clk *clk_pll;
+ struct clk *clk_bypass;
+ struct clk *clk_disp;
+ struct clk *clk_disp_lpcg;
+ struct mutex mutex;
+ int id;
+ unsigned int encoder_type;
+ bool inuse;
+ bool use_bypass_clk;
+ bool side_by_side;
+ struct dpu_soc *dpu;
+};
+
+static inline u32 dpu_fg_read(struct dpu_framegen *fg, unsigned int offset)
+{
+ return readl(fg->base + offset);
+}
+
+static inline void dpu_fg_write(struct dpu_framegen *fg,
+ unsigned int offset, u32 value)
+{
+ writel(value, fg->base + offset);
+}
+
+void framegen_enable(struct dpu_framegen *fg)
+{
+ dpu_fg_write(fg, FGENABLE, FGEN);
+}
+EXPORT_SYMBOL_GPL(framegen_enable);
+
+void framegen_disable(struct dpu_framegen *fg)
+{
+ dpu_fg_write(fg, FGENABLE, 0);
+}
+EXPORT_SYMBOL_GPL(framegen_disable);
+
+void framegen_enable_pixel_link(struct dpu_framegen *fg)
+{
+ struct dpu_soc *dpu = fg->dpu;
+ const struct dpu_data *data = dpu->data;
+
+ if (!(data->has_dual_ldb && fg->encoder_type == DRM_MODE_ENCODER_LVDS))
+ dpu_pxlink_set_mst_enable(fg->dpu, fg->id, true);
+
+ if (fg->encoder_type == DRM_MODE_ENCODER_DPI) {
+ dpu_pxlink_set_mst_valid(fg->dpu, fg->id, true);
+ dpu_pxlink_set_sync_ctrl(fg->dpu, fg->id, true);
+ }
+}
+EXPORT_SYMBOL_GPL(framegen_enable_pixel_link);
+
+void framegen_disable_pixel_link(struct dpu_framegen *fg)
+{
+ struct dpu_soc *dpu = fg->dpu;
+ const struct dpu_data *data = dpu->data;
+
+ if (fg->encoder_type == DRM_MODE_ENCODER_DPI) {
+ dpu_pxlink_set_mst_valid(fg->dpu, fg->id, false);
+ dpu_pxlink_set_sync_ctrl(fg->dpu, fg->id, false);
+ }
+
+ if (!(data->has_dual_ldb && fg->encoder_type == DRM_MODE_ENCODER_LVDS))
+ dpu_pxlink_set_mst_enable(fg->dpu, fg->id, false);
+}
+EXPORT_SYMBOL_GPL(framegen_disable_pixel_link);
+
+void framegen_shdtokgen(struct dpu_framegen *fg)
+{
+ dpu_fg_write(fg, FGSLR, SHDTOKGEN);
+}
+EXPORT_SYMBOL_GPL(framegen_shdtokgen);
+
+void framegen_syncmode(struct dpu_framegen *fg, fgsyncmode_t mode)
+{
+ u32 val;
+
+ val = dpu_fg_read(fg, FGSTCTRL);
+ val &= ~FGSYNCMODE_MASK;
+ val |= mode;
+ dpu_fg_write(fg, FGSTCTRL, val);
+
+ dpu_pxlink_set_dc_sync_mode(fg->dpu, mode != FGSYNCMODE__OFF);
+}
+EXPORT_SYMBOL_GPL(framegen_syncmode);
+
+void framegen_cfg_videomode(struct dpu_framegen *fg, struct drm_display_mode *m,
+ bool side_by_side, unsigned int encoder_type)
+{
+ struct dpu_soc *dpu = fg->dpu;
+ u32 hact, htotal, hsync, hsbp;
+ u32 vact, vtotal, vsync, vsbp;
+ u32 kick_row, kick_col;
+ u32 val;
+ unsigned long disp_clock_rate, pll_clock_rate = 0;
+ int div = 0;
+
+ fg->side_by_side = side_by_side;
+ fg->encoder_type = encoder_type;
+
+ hact = m->crtc_hdisplay;
+ htotal = m->crtc_htotal;
+ hsync = m->crtc_hsync_end - m->crtc_hsync_start;
+ hsbp = m->crtc_htotal - m->crtc_hsync_start;
+
+ if (side_by_side) {
+ hact /= 2;
+ htotal /= 2;
+ hsync /= 2;
+ hsbp /= 2;
+ }
+
+ vact = m->crtc_vdisplay;
+ vtotal = m->crtc_vtotal;
+ vsync = m->crtc_vsync_end - m->crtc_vsync_start;
+ vsbp = m->crtc_vtotal - m->crtc_vsync_start;
+
+ /* video mode */
+ dpu_fg_write(fg, HTCFG1, HACT(hact) | HTOTAL(htotal));
+ dpu_fg_write(fg, HTCFG2, HSYNC(hsync) | HSBP(hsbp) | HSEN);
+ dpu_fg_write(fg, VTCFG1, VACT(vact) | VTOTAL(vtotal));
+ dpu_fg_write(fg, VTCFG2, VSYNC(vsync) | VSBP(vsbp) | VSEN);
+
+ kick_col = hact + 1;
+ kick_row = vact;
+ /*
+ * FrameGen as slave needs to be kicked later for
+ * one line comparing to the master.
+ */
+ if (side_by_side && framegen_is_slave(fg))
+ kick_row++;
+
+ /* pkickconfig */
+ dpu_fg_write(fg, PKICKCONFIG, COL(kick_col) | ROW(kick_row) | EN);
+
+ /* skikconfig */
+ dpu_fg_write(fg, SKICKCONFIG, COL(kick_col) | ROW(kick_row) | EN);
+
+ /* primary and secondary area position config */
+ dpu_fg_write(fg, PACFG, STARTX(0) | STARTY(0));
+ dpu_fg_write(fg, SACFG, STARTX(0) | STARTY(0));
+
+ /* alpha */
+ val = dpu_fg_read(fg, FGINCTRL);
+ val &= ~(ENPRIMALPHA | ENSECALPHA);
+ dpu_fg_write(fg, FGINCTRL, val);
+
+ val = dpu_fg_read(fg, FGINCTRLPANIC);
+ val &= ~(ENPRIMALPHA | ENSECALPHA);
+ dpu_fg_write(fg, FGINCTRLPANIC, val);
+
+ /* constant color */
+ dpu_fg_write(fg, FGCCR, 0);
+
+ disp_clock_rate = m->crtc_clock * 1000;
+
+ if (encoder_type == DRM_MODE_ENCODER_TMDS) {
+ clk_set_parent(fg->clk_disp, fg->clk_bypass);
+ if (side_by_side) {
+ dpu_pxlink_set_mst_addr(dpu, fg->id, fg->id ? 2 : 1);
+ clk_set_rate(fg->clk_bypass, disp_clock_rate / 2);
+ clk_set_rate(fg->clk_disp, disp_clock_rate / 2);
+ } else {
+ dpu_pxlink_set_mst_addr(dpu, fg->id, 1);
+ clk_set_rate(fg->clk_bypass, disp_clock_rate);
+ clk_set_rate(fg->clk_disp, disp_clock_rate);
+ }
+
+ fg->use_bypass_clk = true;
+ } else {
+ dpu_pxlink_set_mst_addr(dpu, fg->id,
+ encoder_type == DRM_MODE_ENCODER_DPI ? 1 : 0);
+
+ clk_set_parent(fg->clk_disp, fg->clk_pll);
+
+ /* find an even divisor for PLL */
+ do {
+ div += 2;
+ pll_clock_rate = disp_clock_rate * div;
+ } while (pll_clock_rate < PLL_MIN_FREQ_HZ);
+
+ clk_set_rate(fg->clk_pll, pll_clock_rate);
+ clk_set_rate(fg->clk_disp, disp_clock_rate);
+
+ fg->use_bypass_clk = false;
+ }
+}
+EXPORT_SYMBOL_GPL(framegen_cfg_videomode);
+
+void framegen_pkickconfig(struct dpu_framegen *fg, bool enable)
+{
+ u32 val;
+
+ val = dpu_fg_read(fg, PKICKCONFIG);
+ if (enable)
+ val |= EN;
+ else
+ val &= ~EN;
+ dpu_fg_write(fg, PKICKCONFIG, val);
+}
+EXPORT_SYMBOL_GPL(framegen_pkickconfig);
+
+void framegen_syncmode_fixup(struct dpu_framegen *fg, bool enable)
+{
+ u32 val;
+
+ val = dpu_fg_read(fg, SECSTATCONFIG);
+ if (enable)
+ val |= BIT(7);
+ else
+ val &= ~BIT(7);
+ dpu_fg_write(fg, SECSTATCONFIG, val);
+}
+EXPORT_SYMBOL_GPL(framegen_syncmode_fixup);
+
+void framegen_displaymode(struct dpu_framegen *fg, fgdm_t mode)
+{
+ u32 val;
+
+ val = dpu_fg_read(fg, FGINCTRL);
+ val &= ~FGDM_MASK;
+ val |= mode;
+ dpu_fg_write(fg, FGINCTRL, val);
+}
+EXPORT_SYMBOL_GPL(framegen_displaymode);
+
+void framegen_panic_displaymode(struct dpu_framegen *fg, fgdm_t mode)
+{
+ u32 val;
+
+ val = dpu_fg_read(fg, FGINCTRLPANIC);
+ val &= ~FGDM_MASK;
+ val |= mode;
+ dpu_fg_write(fg, FGINCTRLPANIC, val);
+}
+EXPORT_SYMBOL_GPL(framegen_panic_displaymode);
+
+void framegen_wait_done(struct dpu_framegen *fg, struct drm_display_mode *m)
+{
+ unsigned long timeout, pending_framedur_jiffies;
+ int frame_size = m->crtc_htotal * m->crtc_vtotal;
+ int dotclock, pending_framedur_ns;
+ u32 val;
+
+ dotclock = clk_get_rate(fg->clk_disp) / KHZ;
+ if (dotclock == 0) {
+ /* fall back to display mode's clock */
+ dotclock = m->crtc_clock;
+ }
+
+ /*
+ * The SoC designer indicates that there are two pending frames
+ * to complete in the worst case.
+ * So, three pending frames are enough for sure.
+ */
+ pending_framedur_ns = div_u64((u64) 3 * frame_size * 1000000, dotclock);
+ pending_framedur_jiffies = nsecs_to_jiffies(pending_framedur_ns);
+ if (pending_framedur_jiffies > (3 * HZ)) {
+ pending_framedur_jiffies = 3 * HZ;
+
+ dev_warn(fg->dpu->dev,
+ "truncate FrameGen%d pending frame duration to 3sec\n",
+ fg->id);
+ }
+ timeout = jiffies + pending_framedur_jiffies;
+
+ do {
+ val = dpu_fg_read(fg, FGENSTS);
+ } while ((val & ENSTS) && time_before(jiffies, timeout));
+
+ dev_dbg(fg->dpu->dev, "FrameGen%d pending frame duration is %ums\n",
+ fg->id, jiffies_to_msecs(pending_framedur_jiffies));
+
+ if (val & ENSTS)
+ dev_err(fg->dpu->dev, "failed to wait for FrameGen%d done\n",
+ fg->id);
+}
+EXPORT_SYMBOL_GPL(framegen_wait_done);
+
+static inline u32 framegen_frame_index(u32 stamp)
+{
+ return (stamp & FRAMEINDEX_MASK) >> FRAMEINDEX_SHIFT;
+}
+
+static inline u32 framegen_line_index(u32 stamp)
+{
+ return (stamp & LINEINDEX_MASK) >> LINEINDEX_SHIFT;
+}
+
+void framegen_read_timestamp(struct dpu_framegen *fg,
+ u32 *frame_index, u32 *line_index)
+{
+ u32 stamp;
+
+ stamp = dpu_fg_read(fg, FGTIMESTAMP);
+ *frame_index = framegen_frame_index(stamp);
+ *line_index = framegen_line_index(stamp);
+}
+EXPORT_SYMBOL_GPL(framegen_read_timestamp);
+
+void framegen_wait_for_frame_counter_moving(struct dpu_framegen *fg)
+{
+ u32 frame_index, line_index, last_frame_index;
+ unsigned long timeout = jiffies + msecs_to_jiffies(100);
+
+ framegen_read_timestamp(fg, &frame_index, &line_index);
+ do {
+ last_frame_index = frame_index;
+ framegen_read_timestamp(fg, &frame_index, &line_index);
+ } while (last_frame_index == frame_index &&
+ time_before(jiffies, timeout));
+
+ if (last_frame_index == frame_index)
+ dev_err(fg->dpu->dev,
+ "failed to wait for FrameGen%d frame counter moving\n",
+ fg->id);
+ else
+ dev_dbg(fg->dpu->dev,
+ "FrameGen%d frame counter moves - last %u, curr %d\n",
+ fg->id, last_frame_index, frame_index);
+}
+EXPORT_SYMBOL_GPL(framegen_wait_for_frame_counter_moving);
+
+bool framegen_secondary_requests_to_read_empty_fifo(struct dpu_framegen *fg)
+{
+ u32 val;
+ bool empty;
+
+ val = dpu_fg_read(fg, FGCHSTAT);
+
+ empty = !!(val & SFIFOEMPTY);
+
+ if (empty)
+ dev_dbg(fg->dpu->dev,
+ "FrameGen%d secondary requests to read empty FIFO\n",
+ fg->id);
+
+ return empty;
+}
+EXPORT_SYMBOL_GPL(framegen_secondary_requests_to_read_empty_fifo);
+
+void framegen_secondary_clear_channel_status(struct dpu_framegen *fg)
+{
+ dpu_fg_write(fg, FGCHSTATCLR, CLRSECSTAT);
+}
+EXPORT_SYMBOL_GPL(framegen_secondary_clear_channel_status);
+
+bool framegen_secondary_is_syncup(struct dpu_framegen *fg)
+{
+ u32 val = dpu_fg_read(fg, FGCHSTAT);
+
+ return val & SECSYNCSTAT;
+}
+EXPORT_SYMBOL_GPL(framegen_secondary_is_syncup);
+
+void framegen_wait_for_secondary_syncup(struct dpu_framegen *fg)
+{
+ unsigned long timeout = jiffies + msecs_to_jiffies(100);
+ bool syncup;
+
+ do {
+ syncup = framegen_secondary_is_syncup(fg);
+ } while (!syncup && time_before(jiffies, timeout));
+
+ if (syncup)
+ dev_dbg(fg->dpu->dev, "FrameGen%d secondary syncup\n", fg->id);
+ else
+ dev_err(fg->dpu->dev,
+ "failed to wait for FrameGen%d secondary syncup\n",
+ fg->id);
+}
+EXPORT_SYMBOL_GPL(framegen_wait_for_secondary_syncup);
+
+void framegen_enable_clock(struct dpu_framegen *fg)
+{
+ if (!fg->use_bypass_clk)
+ clk_prepare_enable(fg->clk_pll);
+ clk_prepare_enable(fg->clk_disp);
+ clk_prepare_enable(fg->clk_disp_lpcg);
+}
+EXPORT_SYMBOL_GPL(framegen_enable_clock);
+
+void framegen_disable_clock(struct dpu_framegen *fg)
+{
+ if (!fg->use_bypass_clk)
+ clk_disable_unprepare(fg->clk_pll);
+ clk_disable_unprepare(fg->clk_disp);
+ clk_disable_unprepare(fg->clk_disp_lpcg);
+}
+EXPORT_SYMBOL_GPL(framegen_disable_clock);
+
+bool framegen_is_master(struct dpu_framegen *fg)
+{
+ const struct dpu_data *data = fg->dpu->data;
+
+ return fg->id == data->master_stream_id;
+}
+EXPORT_SYMBOL_GPL(framegen_is_master);
+
+bool framegen_is_slave(struct dpu_framegen *fg)
+{
+ return !framegen_is_master(fg);
+}
+EXPORT_SYMBOL_GPL(framegen_is_slave);
+
+struct dpu_framegen *dpu_fg_get(struct dpu_soc *dpu, int id)
+{
+ struct dpu_framegen *fg;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(fg_ids); i++)
+ if (fg_ids[i] == id)
+ break;
+
+ if (i == ARRAY_SIZE(fg_ids))
+ return ERR_PTR(-EINVAL);
+
+ fg = dpu->fg_priv[i];
+
+ mutex_lock(&fg->mutex);
+
+ if (fg->inuse) {
+ mutex_unlock(&fg->mutex);
+ return ERR_PTR(-EBUSY);
+ }
+
+ fg->inuse = true;
+
+ mutex_unlock(&fg->mutex);
+
+ return fg;
+}
+EXPORT_SYMBOL_GPL(dpu_fg_get);
+
+void dpu_fg_put(struct dpu_framegen *fg)
+{
+ mutex_lock(&fg->mutex);
+
+ fg->inuse = false;
+
+ mutex_unlock(&fg->mutex);
+}
+EXPORT_SYMBOL_GPL(dpu_fg_put);
+
+struct dpu_framegen *dpu_aux_fg_peek(struct dpu_framegen *fg)
+{
+ return fg->dpu->fg_priv[fg->id ^ 1];
+}
+EXPORT_SYMBOL_GPL(dpu_aux_fg_peek);
+
+void _dpu_fg_init(struct dpu_soc *dpu, unsigned int id)
+{
+ struct dpu_framegen *fg;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(fg_ids); i++)
+ if (fg_ids[i] == id)
+ break;
+
+ if (WARN_ON(i == ARRAY_SIZE(fg_ids)))
+ return;
+
+ fg = dpu->fg_priv[i];
+
+ framegen_syncmode(fg, FGSYNCMODE__OFF);
+}
+
+int dpu_fg_init(struct dpu_soc *dpu, unsigned int id,
+ unsigned long unused, unsigned long base)
+{
+ struct dpu_framegen *fg;
+
+ fg = devm_kzalloc(dpu->dev, sizeof(*fg), GFP_KERNEL);
+ if (!fg)
+ return -ENOMEM;
+
+ dpu->fg_priv[id] = fg;
+
+ fg->base = devm_ioremap(dpu->dev, base, SZ_256);
+ if (!fg->base)
+ return -ENOMEM;
+
+ fg->clk_pll = devm_clk_get(dpu->dev, id ? "pll1" : "pll0");
+ if (IS_ERR(fg->clk_pll))
+ return PTR_ERR(fg->clk_pll);
+
+ fg->clk_bypass = devm_clk_get(dpu->dev, "bypass0");
+ if (IS_ERR(fg->clk_bypass))
+ return PTR_ERR(fg->clk_bypass);
+
+ fg->clk_disp = devm_clk_get(dpu->dev, id ? "disp1" : "disp0");
+ if (IS_ERR(fg->clk_disp))
+ return PTR_ERR(fg->clk_disp);
+
+ fg->clk_disp_lpcg = devm_clk_get(dpu->dev, id ? "disp1_lpcg" : "disp0_lpcg");
+ if (IS_ERR(fg->clk_disp_lpcg))
+ return PTR_ERR(fg->clk_disp_lpcg);
+
+ fg->dpu = dpu;
+ fg->id = id;
+ mutex_init(&fg->mutex);
+
+ _dpu_fg_init(dpu, id);
+
+ return 0;
+}
diff --git a/drivers/gpu/imx/dpu/dpu-hscaler.c b/drivers/gpu/imx/dpu/dpu-hscaler.c
new file mode 100644
index 000000000000..9e69c619bd3f
--- /dev/null
+++ b/drivers/gpu/imx/dpu/dpu-hscaler.c
@@ -0,0 +1,386 @@
+/*
+ * Copyright 2017-2019 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include <video/dpu.h>
+#include "dpu-prv.h"
+
+#define PIXENGCFG_DYNAMIC 0x8
+#define PIXENGCFG_DYNAMIC_SRC_SEL_MASK 0x3F
+
+#define SETUP1 0xC
+#define SCALE_FACTOR_MASK 0xFFFFF
+#define SCALE_FACTOR(n) ((n) & 0xFFFFF)
+#define SETUP2 0x10
+#define PHASE_OFFSET_MASK 0x1FFFFF
+#define PHASE_OFFSET(n) ((n) & 0x1FFFFF)
+#define CONTROL 0x14
+#define OUTPUT_SIZE_MASK 0x3FFF0000
+#define OUTPUT_SIZE(n) ((((n) - 1) << 16) & OUTPUT_SIZE_MASK)
+#define FILTER_MODE 0x100
+#define SCALE_MODE 0x10
+#define MODE 0x1
+
+static const hs_src_sel_t src_sels[3][6] = {
+ {
+ HS_SRC_SEL__DISABLE,
+ HS_SRC_SEL__FETCHDECODE0,
+ HS_SRC_SEL__MATRIX4,
+ HS_SRC_SEL__VSCALER4,
+ }, {
+ HS_SRC_SEL__DISABLE,
+ HS_SRC_SEL__FETCHDECODE1,
+ HS_SRC_SEL__MATRIX5,
+ HS_SRC_SEL__VSCALER5,
+ }, {
+ HS_SRC_SEL__DISABLE,
+ HS_SRC_SEL__MATRIX9,
+ HS_SRC_SEL__VSCALER9,
+ HS_SRC_SEL__FILTER9,
+ },
+};
+
+struct dpu_hscaler {
+ void __iomem *pec_base;
+ void __iomem *base;
+ struct mutex mutex;
+ int id;
+ bool inuse;
+ struct dpu_soc *dpu;
+ /* see DPU_PLANE_SRC_xxx */
+ unsigned int stream_id;
+};
+
+static inline u32 dpu_pec_hs_read(struct dpu_hscaler *hs,
+ unsigned int offset)
+{
+ return readl(hs->pec_base + offset);
+}
+
+static inline void dpu_pec_hs_write(struct dpu_hscaler *hs,
+ unsigned int offset, u32 value)
+{
+ writel(value, hs->pec_base + offset);
+}
+
+static inline u32 dpu_hs_read(struct dpu_hscaler *hs, unsigned int offset)
+{
+ return readl(hs->base + offset);
+}
+
+static inline void dpu_hs_write(struct dpu_hscaler *hs,
+ unsigned int offset, u32 value)
+{
+ writel(value, hs->base + offset);
+}
+
+int hscaler_pixengcfg_dynamic_src_sel(struct dpu_hscaler *hs, hs_src_sel_t src)
+{
+ struct dpu_soc *dpu = hs->dpu;
+ const unsigned int hs_id_array[] = {4, 5, 9};
+ int i, j;
+ u32 val;
+
+ for (i = 0; i < ARRAY_SIZE(hs_id_array); i++)
+ if (hs_id_array[i] == hs->id)
+ break;
+
+ if (WARN_ON(i == ARRAY_SIZE(hs_id_array)))
+ return -EINVAL;
+
+ mutex_lock(&hs->mutex);
+ for (j = 0; j < ARRAY_SIZE(src_sels[0]); j++) {
+ if (src_sels[i][j] == src) {
+ val = dpu_pec_hs_read(hs, PIXENGCFG_DYNAMIC);
+ val &= ~PIXENGCFG_DYNAMIC_SRC_SEL_MASK;
+ val |= src;
+ dpu_pec_hs_write(hs, PIXENGCFG_DYNAMIC, val);
+ mutex_unlock(&hs->mutex);
+ return 0;
+ }
+ }
+ mutex_unlock(&hs->mutex);
+
+ dev_err(dpu->dev, "Invalid source for HScaler%d\n", hs->id);
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(hscaler_pixengcfg_dynamic_src_sel);
+
+void hscaler_pixengcfg_clken(struct dpu_hscaler *hs, pixengcfg_clken_t clken)
+{
+ u32 val;
+
+ mutex_lock(&hs->mutex);
+ val = dpu_pec_hs_read(hs, PIXENGCFG_DYNAMIC);
+ val &= ~CLKEN_MASK;
+ val |= clken << CLKEN_MASK_SHIFT;
+ dpu_pec_hs_write(hs, PIXENGCFG_DYNAMIC, val);
+ mutex_unlock(&hs->mutex);
+}
+EXPORT_SYMBOL_GPL(hscaler_pixengcfg_clken);
+
+void hscaler_shden(struct dpu_hscaler *hs, bool enable)
+{
+ u32 val;
+
+ mutex_lock(&hs->mutex);
+ val = dpu_hs_read(hs, STATICCONTROL);
+ if (enable)
+ val |= SHDEN;
+ else
+ val &= ~SHDEN;
+ dpu_hs_write(hs, STATICCONTROL, val);
+ mutex_unlock(&hs->mutex);
+}
+EXPORT_SYMBOL_GPL(hscaler_shden);
+
+void hscaler_setup1(struct dpu_hscaler *hs, u32 src, u32 dst)
+{
+ struct dpu_soc *dpu = hs->dpu;
+ u32 scale_factor;
+ u64 tmp64;
+
+ if (src == dst) {
+ scale_factor = 0x80000;
+ } else {
+ if (src > dst) {
+ tmp64 = (u64)((u64)dst * 0x80000);
+ do_div(tmp64, src);
+
+ } else {
+ tmp64 = (u64)((u64)src * 0x80000);
+ do_div(tmp64, dst);
+ }
+ scale_factor = (u32)tmp64;
+ }
+
+ WARN_ON(scale_factor > 0x80000);
+
+ mutex_lock(&hs->mutex);
+ dpu_hs_write(hs, SETUP1, SCALE_FACTOR(scale_factor));
+ mutex_unlock(&hs->mutex);
+
+ dev_dbg(dpu->dev, "Hscaler%d scale factor 0x%08x\n",
+ hs->id, scale_factor);
+}
+EXPORT_SYMBOL_GPL(hscaler_setup1);
+
+void hscaler_setup2(struct dpu_hscaler *hs, u32 phase_offset)
+{
+ mutex_lock(&hs->mutex);
+ dpu_hs_write(hs, SETUP2, PHASE_OFFSET(phase_offset));
+ mutex_unlock(&hs->mutex);
+}
+EXPORT_SYMBOL_GPL(hscaler_setup2);
+
+void hscaler_output_size(struct dpu_hscaler *hs, u32 line_num)
+{
+ u32 val;
+
+ mutex_lock(&hs->mutex);
+ val = dpu_hs_read(hs, CONTROL);
+ val &= ~OUTPUT_SIZE_MASK;
+ val |= OUTPUT_SIZE(line_num);
+ dpu_hs_write(hs, CONTROL, val);
+ mutex_unlock(&hs->mutex);
+}
+EXPORT_SYMBOL_GPL(hscaler_output_size);
+
+void hscaler_filter_mode(struct dpu_hscaler *hs, scaler_filter_mode_t m)
+{
+ u32 val;
+
+ mutex_lock(&hs->mutex);
+ val = dpu_hs_read(hs, CONTROL);
+ val &= ~FILTER_MODE;
+ val |= m;
+ dpu_hs_write(hs, CONTROL, val);
+ mutex_unlock(&hs->mutex);
+}
+EXPORT_SYMBOL_GPL(hscaler_filter_mode);
+
+void hscaler_scale_mode(struct dpu_hscaler *hs, scaler_scale_mode_t m)
+{
+ u32 val;
+
+ mutex_lock(&hs->mutex);
+ val = dpu_hs_read(hs, CONTROL);
+ val &= ~SCALE_MODE;
+ val |= m;
+ dpu_hs_write(hs, CONTROL, val);
+ mutex_unlock(&hs->mutex);
+}
+EXPORT_SYMBOL_GPL(hscaler_scale_mode);
+
+void hscaler_mode(struct dpu_hscaler *hs, scaler_mode_t m)
+{
+ u32 val;
+
+ mutex_lock(&hs->mutex);
+ val = dpu_hs_read(hs, CONTROL);
+ val &= ~MODE;
+ val |= m;
+ dpu_hs_write(hs, CONTROL, val);
+ mutex_unlock(&hs->mutex);
+}
+EXPORT_SYMBOL_GPL(hscaler_mode);
+
+bool hscaler_is_enabled(struct dpu_hscaler *hs)
+{
+ u32 val;
+
+ mutex_lock(&hs->mutex);
+ val = dpu_hs_read(hs, CONTROL);
+ mutex_unlock(&hs->mutex);
+
+ return (val & MODE) == SCALER_ACTIVE;
+}
+EXPORT_SYMBOL_GPL(hscaler_is_enabled);
+
+dpu_block_id_t hscaler_get_block_id(struct dpu_hscaler *hs)
+{
+ switch (hs->id) {
+ case 4:
+ return ID_HSCALER4;
+ case 5:
+ return ID_HSCALER5;
+ case 9:
+ return ID_HSCALER9;
+ default:
+ WARN_ON(1);
+ }
+
+ return ID_NONE;
+}
+EXPORT_SYMBOL_GPL(hscaler_get_block_id);
+
+unsigned int hscaler_get_stream_id(struct dpu_hscaler *hs)
+{
+ return hs->stream_id;
+}
+EXPORT_SYMBOL_GPL(hscaler_get_stream_id);
+
+void hscaler_set_stream_id(struct dpu_hscaler *hs, unsigned int id)
+{
+ switch (id) {
+ case DPU_PLANE_SRC_TO_DISP_STREAM0:
+ case DPU_PLANE_SRC_TO_DISP_STREAM1:
+ case DPU_PLANE_SRC_DISABLED:
+ hs->stream_id = id;
+ break;
+ default:
+ WARN_ON(1);
+ }
+}
+EXPORT_SYMBOL_GPL(hscaler_set_stream_id);
+
+struct dpu_hscaler *dpu_hs_get(struct dpu_soc *dpu, int id)
+{
+ struct dpu_hscaler *hs;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(hs_ids); i++)
+ if (hs_ids[i] == id)
+ break;
+
+ if (i == ARRAY_SIZE(hs_ids))
+ return ERR_PTR(-EINVAL);
+
+ hs = dpu->hs_priv[i];
+
+ mutex_lock(&hs->mutex);
+
+ if (hs->inuse) {
+ mutex_unlock(&hs->mutex);
+ return ERR_PTR(-EBUSY);
+ }
+
+ hs->inuse = true;
+
+ mutex_unlock(&hs->mutex);
+
+ return hs;
+}
+EXPORT_SYMBOL_GPL(dpu_hs_get);
+
+void dpu_hs_put(struct dpu_hscaler *hs)
+{
+ mutex_lock(&hs->mutex);
+
+ hs->inuse = false;
+
+ mutex_unlock(&hs->mutex);
+}
+EXPORT_SYMBOL_GPL(dpu_hs_put);
+
+void _dpu_hs_init(struct dpu_soc *dpu, unsigned int id)
+{
+ struct dpu_hscaler *hs;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(hs_ids); i++)
+ if (hs_ids[i] == id)
+ break;
+
+ if (WARN_ON(i == ARRAY_SIZE(hs_ids)))
+ return;
+
+ hs = dpu->hs_priv[i];
+
+ hscaler_shden(hs, true);
+ hscaler_setup2(hs, 0);
+ hscaler_pixengcfg_dynamic_src_sel(hs, HS_SRC_SEL__DISABLE);
+}
+
+int dpu_hs_init(struct dpu_soc *dpu, unsigned int id,
+ unsigned long pec_base, unsigned long base)
+{
+ struct dpu_hscaler *hs;
+ int i;
+
+ hs = devm_kzalloc(dpu->dev, sizeof(*hs), GFP_KERNEL);
+ if (!hs)
+ return -ENOMEM;
+
+ for (i = 0; i < ARRAY_SIZE(hs_ids); i++)
+ if (hs_ids[i] == id)
+ break;
+
+ if (i == ARRAY_SIZE(hs_ids))
+ return -EINVAL;
+
+ dpu->hs_priv[i] = hs;
+
+ hs->pec_base = devm_ioremap(dpu->dev, pec_base, SZ_8);
+ if (!hs->pec_base)
+ return -ENOMEM;
+
+ hs->base = devm_ioremap(dpu->dev, base, SZ_1K);
+ if (!hs->base)
+ return -ENOMEM;
+
+ hs->dpu = dpu;
+ hs->id = id;
+
+ mutex_init(&hs->mutex);
+
+ _dpu_hs_init(dpu, id);
+
+ return 0;
+}
diff --git a/drivers/gpu/imx/dpu/dpu-layerblend.c b/drivers/gpu/imx/dpu/dpu-layerblend.c
new file mode 100644
index 000000000000..c19fcbdb169b
--- /dev/null
+++ b/drivers/gpu/imx/dpu/dpu-layerblend.c
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2016 Freescale Semiconductor, Inc.
+ * Copyright 2017-2019 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <drm/drm_blend.h>
+#include <linux/io.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include <video/dpu.h>
+#include "dpu-prv.h"
+
+#define PIXENGCFG_DYNAMIC 0x8
+#define PIXENGCFG_DYNAMIC_PRIM_SEL_MASK 0x3F
+#define PIXENGCFG_DYNAMIC_SEC_SEL_MASK 0x3F00
+#define PIXENGCFG_DYNAMIC_SEC_SEL_SHIFT 8
+
+static const lb_prim_sel_t prim_sels[] = {
+ LB_PRIM_SEL__DISABLE,
+ LB_PRIM_SEL__BLITBLEND9,
+ LB_PRIM_SEL__CONSTFRAME0,
+ LB_PRIM_SEL__CONSTFRAME1,
+ LB_PRIM_SEL__CONSTFRAME4,
+ LB_PRIM_SEL__CONSTFRAME5,
+ LB_PRIM_SEL__MATRIX4,
+ LB_PRIM_SEL__HSCALER4,
+ LB_PRIM_SEL__VSCALER4,
+ LB_PRIM_SEL__MATRIX5,
+ LB_PRIM_SEL__HSCALER5,
+ LB_PRIM_SEL__VSCALER5,
+ LB_PRIM_SEL__LAYERBLEND0,
+ LB_PRIM_SEL__LAYERBLEND1,
+ LB_PRIM_SEL__LAYERBLEND2,
+ LB_PRIM_SEL__LAYERBLEND3,
+};
+
+#define PIXENGCFG_STATUS 0xC
+#define SHDTOKSEL (0x3 << 3)
+#define SHDTOKSEL_SHIFT 3
+#define SHDLDSEL (0x3 << 1)
+#define SHDLDSEL_SHIFT 1
+#define CONTROL 0xC
+#define OPERATION_MODE_MASK BIT(0)
+#define BLENDCONTROL 0x10
+#define ALPHA(a) (((a) & 0xFF) << 16)
+#define PRIM_C_BLD_FUNC__ONE_MINUS_CONST_ALPHA 0x7
+#define PRIM_C_BLD_FUNC__ONE_MINUS_SEC_ALPHA 0x5
+#define PRIM_C_BLD_FUNC__ZERO 0x0
+#define SEC_C_BLD_FUNC__CONST_ALPHA (0x6 << 4)
+#define SEC_C_BLD_FUNC__SEC_ALPHA (0x4 << 4)
+#define PRIM_A_BLD_FUNC__ZERO (0x0 << 8)
+#define SEC_A_BLD_FUNC__ZERO (0x0 << 12)
+#define POSITION 0x14
+#define XPOS(x) ((x) & 0x7FFF)
+#define YPOS(y) (((y) & 0x7FFF) << 16)
+#define PRIMCONTROLWORD 0x18
+#define SECCONTROLWORD 0x1C
+
+struct dpu_layerblend {
+ void __iomem *pec_base;
+ void __iomem *base;
+ struct mutex mutex;
+ int id;
+ bool inuse;
+ struct dpu_soc *dpu;
+};
+
+static inline u32 dpu_pec_lb_read(struct dpu_layerblend *lb,
+ unsigned int offset)
+{
+ return readl(lb->pec_base + offset);
+}
+
+static inline void dpu_pec_lb_write(struct dpu_layerblend *lb,
+ unsigned int offset, u32 value)
+{
+ writel(value, lb->pec_base + offset);
+}
+
+static inline u32 dpu_lb_read(struct dpu_layerblend *lb, unsigned int offset)
+{
+ return readl(lb->base + offset);
+}
+
+static inline void dpu_lb_write(struct dpu_layerblend *lb,
+ unsigned int offset, u32 value)
+{
+ writel(value, lb->base + offset);
+}
+
+int layerblend_pixengcfg_dynamic_prim_sel(struct dpu_layerblend *lb,
+ lb_prim_sel_t prim)
+{
+ struct dpu_soc *dpu = lb->dpu;
+ int fixed_sels_num = ARRAY_SIZE(prim_sels) - 4;
+ int i;
+ u32 val;
+
+ mutex_lock(&lb->mutex);
+ for (i = 0; i < fixed_sels_num + lb->id; i++) {
+ if (prim_sels[i] == prim) {
+ val = dpu_pec_lb_read(lb, PIXENGCFG_DYNAMIC);
+ val &= ~PIXENGCFG_DYNAMIC_PRIM_SEL_MASK;
+ val |= prim;
+ dpu_pec_lb_write(lb, PIXENGCFG_DYNAMIC, val);
+ mutex_unlock(&lb->mutex);
+ return 0;
+ }
+ }
+ mutex_unlock(&lb->mutex);
+
+ dev_err(dpu->dev, "Invalid primary source for LayerBlend%d\n", lb->id);
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(layerblend_pixengcfg_dynamic_prim_sel);
+
+void layerblend_pixengcfg_dynamic_sec_sel(struct dpu_layerblend *lb,
+ lb_sec_sel_t sec)
+{
+ u32 val;
+
+ mutex_lock(&lb->mutex);
+ val = dpu_pec_lb_read(lb, PIXENGCFG_DYNAMIC);
+ val &= ~PIXENGCFG_DYNAMIC_SEC_SEL_MASK;
+ val |= sec << PIXENGCFG_DYNAMIC_SEC_SEL_SHIFT;
+ dpu_pec_lb_write(lb, PIXENGCFG_DYNAMIC, val);
+ mutex_unlock(&lb->mutex);
+}
+EXPORT_SYMBOL_GPL(layerblend_pixengcfg_dynamic_sec_sel);
+
+void layerblend_pixengcfg_clken(struct dpu_layerblend *lb,
+ pixengcfg_clken_t clken)
+{
+ u32 val;
+
+ mutex_lock(&lb->mutex);
+ val = dpu_pec_lb_read(lb, PIXENGCFG_DYNAMIC);
+ val &= ~CLKEN_MASK;
+ val |= clken << CLKEN_MASK_SHIFT;
+ dpu_pec_lb_write(lb, PIXENGCFG_DYNAMIC, val);
+ mutex_unlock(&lb->mutex);
+}
+EXPORT_SYMBOL_GPL(layerblend_pixengcfg_clken);
+
+void layerblend_shden(struct dpu_layerblend *lb, bool enable)
+{
+ u32 val;
+
+ mutex_lock(&lb->mutex);
+ val = dpu_lb_read(lb, STATICCONTROL);
+ if (enable)
+ val |= SHDEN;
+ else
+ val &= ~SHDEN;
+ dpu_lb_write(lb, STATICCONTROL, val);
+ mutex_unlock(&lb->mutex);
+}
+EXPORT_SYMBOL_GPL(layerblend_shden);
+
+void layerblend_shdtoksel(struct dpu_layerblend *lb, lb_shadow_sel_t sel)
+{
+ u32 val;
+
+ mutex_lock(&lb->mutex);
+ val = dpu_lb_read(lb, STATICCONTROL);
+ val &= ~SHDTOKSEL;
+ val |= (sel << SHDTOKSEL_SHIFT);
+ dpu_lb_write(lb, STATICCONTROL, val);
+ mutex_unlock(&lb->mutex);
+}
+EXPORT_SYMBOL_GPL(layerblend_shdtoksel);
+
+void layerblend_shdldsel(struct dpu_layerblend *lb, lb_shadow_sel_t sel)
+{
+ u32 val;
+
+ mutex_lock(&lb->mutex);
+ val = dpu_lb_read(lb, STATICCONTROL);
+ val &= ~SHDLDSEL;
+ val |= (sel << SHDLDSEL_SHIFT);
+ dpu_lb_write(lb, STATICCONTROL, val);
+ mutex_unlock(&lb->mutex);
+}
+EXPORT_SYMBOL_GPL(layerblend_shdldsel);
+
+void layerblend_control(struct dpu_layerblend *lb, lb_mode_t mode)
+{
+ u32 val;
+
+ mutex_lock(&lb->mutex);
+ val = dpu_lb_read(lb, CONTROL);
+ val &= ~OPERATION_MODE_MASK;
+ val |= mode;
+ dpu_lb_write(lb, CONTROL, val);
+ mutex_unlock(&lb->mutex);
+}
+EXPORT_SYMBOL_GPL(layerblend_control);
+
+void layerblend_blendcontrol(struct dpu_layerblend *lb, unsigned int zpos,
+ unsigned int pixel_blend_mode, u16 alpha)
+{
+ u32 val = PRIM_A_BLD_FUNC__ZERO | SEC_A_BLD_FUNC__ZERO;
+
+ if (zpos == 0) {
+ val |= PRIM_C_BLD_FUNC__ZERO | SEC_C_BLD_FUNC__CONST_ALPHA;
+ alpha = DRM_BLEND_ALPHA_OPAQUE;
+ } else {
+ switch (pixel_blend_mode) {
+ case DRM_MODE_BLEND_PIXEL_NONE:
+ val |= PRIM_C_BLD_FUNC__ONE_MINUS_CONST_ALPHA |
+ SEC_C_BLD_FUNC__CONST_ALPHA;
+ break;
+ case DRM_MODE_BLEND_PREMULTI:
+ val |= PRIM_C_BLD_FUNC__ONE_MINUS_SEC_ALPHA |
+ SEC_C_BLD_FUNC__CONST_ALPHA;
+ break;
+ case DRM_MODE_BLEND_COVERAGE:
+ val |= PRIM_C_BLD_FUNC__ONE_MINUS_SEC_ALPHA |
+ SEC_C_BLD_FUNC__SEC_ALPHA;
+ break;
+ default:
+ break;
+ }
+ }
+
+ val |= ALPHA(alpha >> 8);
+
+ mutex_lock(&lb->mutex);
+ dpu_lb_write(lb, BLENDCONTROL, val);
+ mutex_unlock(&lb->mutex);
+}
+EXPORT_SYMBOL_GPL(layerblend_blendcontrol);
+
+void layerblend_position(struct dpu_layerblend *lb, int x, int y)
+{
+ mutex_lock(&lb->mutex);
+ dpu_lb_write(lb, POSITION, XPOS(x) | YPOS(y));
+ mutex_unlock(&lb->mutex);
+}
+EXPORT_SYMBOL_GPL(layerblend_position);
+
+struct dpu_layerblend *dpu_lb_get(struct dpu_soc *dpu, int id)
+{
+ struct dpu_layerblend *lb;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(lb_ids); i++)
+ if (lb_ids[i] == id)
+ break;
+
+ if (i == ARRAY_SIZE(lb_ids))
+ return ERR_PTR(-EINVAL);
+
+ lb = dpu->lb_priv[i];
+
+ mutex_lock(&lb->mutex);
+
+ if (lb->inuse) {
+ mutex_unlock(&lb->mutex);
+ return ERR_PTR(-EBUSY);
+ }
+
+ lb->inuse = true;
+
+ mutex_unlock(&lb->mutex);
+
+ return lb;
+}
+EXPORT_SYMBOL_GPL(dpu_lb_get);
+
+void dpu_lb_put(struct dpu_layerblend *lb)
+{
+ mutex_lock(&lb->mutex);
+
+ lb->inuse = false;
+
+ mutex_unlock(&lb->mutex);
+}
+EXPORT_SYMBOL_GPL(dpu_lb_put);
+
+void _dpu_lb_init(struct dpu_soc *dpu, unsigned int id)
+{
+ struct dpu_layerblend *lb;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(lb_ids); i++)
+ if (lb_ids[i] == id)
+ break;
+
+ if (WARN_ON(i == ARRAY_SIZE(lb_ids)))
+ return;
+
+ lb = dpu->lb_priv[i];
+
+ layerblend_pixengcfg_dynamic_prim_sel(lb, LB_PRIM_SEL__DISABLE);
+ layerblend_pixengcfg_dynamic_sec_sel(lb, LB_SEC_SEL__DISABLE);
+ layerblend_pixengcfg_clken(lb, CLKEN__AUTOMATIC);
+ layerblend_shdldsel(lb, BOTH);
+ layerblend_shdtoksel(lb, BOTH);
+ layerblend_shden(lb, true);
+}
+
+int dpu_lb_init(struct dpu_soc *dpu, unsigned int id,
+ unsigned long pec_base, unsigned long base)
+{
+ struct dpu_layerblend *lb;
+ int ret;
+
+ lb = devm_kzalloc(dpu->dev, sizeof(*lb), GFP_KERNEL);
+ if (!lb)
+ return -ENOMEM;
+
+ dpu->lb_priv[id] = lb;
+
+ lb->pec_base = devm_ioremap(dpu->dev, pec_base, SZ_16);
+ if (!lb->pec_base)
+ return -ENOMEM;
+
+ lb->base = devm_ioremap(dpu->dev, base, SZ_32);
+ if (!lb->base)
+ return -ENOMEM;
+
+ lb->dpu = dpu;
+ lb->id = id;
+ mutex_init(&lb->mutex);
+
+ ret = layerblend_pixengcfg_dynamic_prim_sel(lb, LB_PRIM_SEL__DISABLE);
+ if (ret < 0)
+ return ret;
+
+ _dpu_lb_init(dpu, id);
+
+ return 0;
+}
diff --git a/drivers/gpu/imx/dpu/dpu-prv.h b/drivers/gpu/imx/dpu/dpu-prv.h
new file mode 100644
index 000000000000..19aac30a7fdc
--- /dev/null
+++ b/drivers/gpu/imx/dpu/dpu-prv.h
@@ -0,0 +1,475 @@
+/*
+ * Copyright (C) 2016 Freescale Semiconductor, Inc.
+ * Copyright 2017-2020,2022 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#ifndef __DPU_PRV_H__
+#define __DPU_PRV_H__
+
+#include <linux/firmware/imx/sci.h>
+#include <drm/drm_fourcc.h>
+#include <video/dpu.h>
+
+#define STATICCONTROL 0x8
+#define SHDLDREQSTICKY(lm) (((lm) & 0xFF) << 24)
+#define SHDLDREQSTICKY_MASK (0xFF << 24)
+#define BASEADDRESSAUTOUPDATE(lm) (((lm) & 0xFF) << 16)
+#define BASEADDRESSAUTOUPDATE_MASK (0xFF << 16)
+#define SHDEN BIT(0)
+#define BURSTBUFFERMANAGEMENT 0xC
+#define SETNUMBUFFERS(n) ((n) & 0xFF)
+#define SETBURSTLENGTH(n) (((n) & 0x1F) << 8)
+#define SETBURSTLENGTH_MASK 0x1F00
+#define LINEMODE_MASK 0x80000000U
+#define LINEMODE_SHIFT 31U
+enum linemode {
+ /*
+ * Mandatory setting for operation in the Display Controller.
+ * Works also for Blit Engine with marginal performance impact.
+ */
+ LINEMODE__DISPLAY = 0,
+ /* Recommended setting for operation in the Blit Engine. */
+ LINEMODE__BLIT = 1 << LINEMODE_SHIFT,
+};
+
+#define BITSPERPIXEL(bpp) (((bpp) & 0x3F) << 16)
+#define STRIDE(n) (((n) - 1) & 0xFFFF)
+#define LINEWIDTH(w) (((w) - 1) & 0x3FFF)
+#define LINECOUNT(h) ((((h) - 1) & 0x3FFF) << 16)
+#define ITUFORMAT BIT(31)
+#define R_BITS(n) (((n) & 0xF) << 24)
+#define G_BITS(n) (((n) & 0xF) << 16)
+#define B_BITS(n) (((n) & 0xF) << 8)
+#define A_BITS(n) ((n) & 0xF)
+#define R_SHIFT(n) (((n) & 0x1F) << 24)
+#define G_SHIFT(n) (((n) & 0x1F) << 16)
+#define B_SHIFT(n) (((n) & 0x1F) << 8)
+#define A_SHIFT(n) ((n) & 0x1F)
+#define Y_BITS(n) R_BITS(n)
+#define Y_BITS_MASK 0xF000000
+#define U_BITS(n) G_BITS(n)
+#define U_BITS_MASK 0xF0000
+#define V_BITS(n) B_BITS(n)
+#define V_BITS_MASK 0xF00
+#define Y_SHIFT(n) R_SHIFT(n)
+#define Y_SHIFT_MASK 0x1F000000
+#define U_SHIFT(n) G_SHIFT(n)
+#define U_SHIFT_MASK 0x1F0000
+#define V_SHIFT(n) B_SHIFT(n)
+#define V_SHIFT_MASK 0x1F00
+#define LAYERXOFFSET(x) ((x) & 0x7FFF)
+#define LAYERYOFFSET(y) (((y) & 0x7FFF) << 16)
+#define CLIPWINDOWXOFFSET(x) ((x) & 0x7FFF)
+#define CLIPWINDOWYOFFSET(y) (((y) & 0x7FFF) << 16)
+#define CLIPWINDOWWIDTH(w) (((w) - 1) & 0x3FFF)
+#define CLIPWINDOWHEIGHT(h) ((((h) - 1) & 0x3FFF) << 16)
+#define CONSTANTALPHA_MASK 0xFF
+#define CONSTANTALPHA(n) ((n) & CONSTANTALPHA_MASK)
+#define PALETTEENABLE BIT(0)
+typedef enum {
+ TILE_FILL_ZERO,
+ TILE_FILL_CONSTANT,
+ TILE_PAD,
+ TILE_PAD_ZERO,
+} tilemode_t;
+#define ALPHASRCENABLE BIT(8)
+#define ALPHACONSTENABLE BIT(9)
+#define ALPHAMASKENABLE BIT(10)
+#define ALPHATRANSENABLE BIT(11)
+#define ALPHA_ENABLE_MASK (ALPHASRCENABLE | ALPHACONSTENABLE | \
+ ALPHAMASKENABLE | ALPHATRANSENABLE)
+#define RGBALPHASRCENABLE BIT(12)
+#define RGBALPHACONSTENABLE BIT(13)
+#define RGBALPHAMASKENABLE BIT(14)
+#define RGBALPHATRANSENABLE BIT(15)
+#define RGB_ENABLE_MASK (RGBALPHASRCENABLE | \
+ RGBALPHACONSTENABLE | \
+ RGBALPHAMASKENABLE | \
+ RGBALPHATRANSENABLE)
+#define PREMULCONSTRGB BIT(16)
+typedef enum {
+ YUVCONVERSIONMODE__OFF,
+ YUVCONVERSIONMODE__ITU601,
+ YUVCONVERSIONMODE__ITU601_FR,
+ YUVCONVERSIONMODE__ITU709,
+} yuvconversionmode_t;
+#define YUVCONVERSIONMODE_MASK 0x60000
+#define YUVCONVERSIONMODE(m) (((m) & 0x3) << 17)
+#define GAMMAREMOVEENABLE BIT(20)
+#define CLIPWINDOWENABLE BIT(30)
+#define SOURCEBUFFERENABLE BIT(31)
+#define EMPTYFRAME BIT(31)
+#define FRAMEWIDTH(w) (((w) - 1) & 0x3FFF)
+#define FRAMEHEIGHT(h) ((((h) - 1) & 0x3FFF) << 16)
+#define DELTAX_MASK 0x3F000
+#define DELTAY_MASK 0xFC0000
+#define DELTAX(x) (((x) & 0x3F) << 12)
+#define DELTAY(y) (((y) & 0x3F) << 18)
+#define YUV422UPSAMPLINGMODE_MASK BIT(5)
+#define YUV422UPSAMPLINGMODE(m) (((m) & 0x1) << 5)
+typedef enum {
+ YUV422UPSAMPLINGMODE__REPLICATE,
+ YUV422UPSAMPLINGMODE__INTERPOLATE,
+} yuv422upsamplingmode_t;
+#define INPUTSELECT_MASK 0x18
+#define INPUTSELECT(s) (((s) & 0x3) << 3)
+typedef enum {
+ INPUTSELECT__INACTIVE,
+ INPUTSELECT__COMPPACK,
+ INPUTSELECT__ALPHAMASK,
+ INPUTSELECT__COORDINATE,
+} inputselect_t;
+#define RASTERMODE_MASK 0x7
+#define RASTERMODE(m) ((m) & 0x7)
+typedef enum {
+ RASTERMODE__NORMAL,
+ RASTERMODE__DECODE,
+ RASTERMODE__ARBITRARY,
+ RASTERMODE__PERSPECTIVE,
+ RASTERMODE__YUV422,
+ RASTERMODE__AFFINE,
+} rastermode_t;
+#define SHDTOKGEN BIT(0)
+#define FETCHTYPE_MASK 0xF
+
+#define DPU_FRAC_PLANE_LAYER_NUM 8
+
+#define DPU_VPROC_CAP_HSCALER4 BIT(0)
+#define DPU_VPROC_CAP_VSCALER4 BIT(1)
+#define DPU_VPROC_CAP_HSCALER5 BIT(2)
+#define DPU_VPROC_CAP_VSCALER5 BIT(3)
+#define DPU_VPROC_CAP_FETCHECO0 BIT(4)
+#define DPU_VPROC_CAP_FETCHECO1 BIT(5)
+
+#define DPU_VPROC_CAP_HSCALE (DPU_VPROC_CAP_HSCALER4 | \
+ DPU_VPROC_CAP_HSCALER5)
+#define DPU_VPROC_CAP_VSCALE (DPU_VPROC_CAP_VSCALER4 | \
+ DPU_VPROC_CAP_VSCALER5)
+#define DPU_VPROC_CAP_FETCHECO (DPU_VPROC_CAP_FETCHECO0 | \
+ DPU_VPROC_CAP_FETCHECO1)
+
+struct dpu_unit {
+ char *name;
+ unsigned int num;
+ const unsigned int *ids;
+ const unsigned long *pec_ofss; /* PixEngCFG */
+ const unsigned long *ofss;
+ const unsigned int *dprc_ids;
+};
+
+struct cm_reg_ofs {
+ u32 ipidentifier;
+ u32 lockunlock;
+ u32 lockstatus;
+ u32 userinterruptmask;
+ u32 interruptenable;
+ u32 interruptpreset;
+ u32 interruptclear;
+ u32 interruptstatus;
+ u32 userinterruptenable;
+ u32 userinterruptpreset;
+ u32 userinterruptclear;
+ u32 userinterruptstatus;
+ u32 generalpurpose;
+};
+
+struct dpu_data {
+ unsigned long cm_ofs; /* common */
+ const struct dpu_unit *cfs;
+ const struct dpu_unit *decs;
+ const struct dpu_unit *eds;
+ const struct dpu_unit *fds;
+ const struct dpu_unit *fes;
+ const struct dpu_unit *fgs;
+ const struct dpu_unit *fls;
+ const struct dpu_unit *fws;
+ const struct dpu_unit *hss;
+ const struct dpu_unit *lbs;
+ const struct dpu_unit *sigs;
+ const struct dpu_unit *sts;
+ const struct dpu_unit *tcons;
+ const struct dpu_unit *vss;
+ const struct cm_reg_ofs *cm_reg_ofs;
+ const unsigned long *unused_irq;
+
+ unsigned int syncmode_min_prate; /* need pixel combiner, KHz */
+ unsigned int singlemode_max_width;
+ unsigned int master_stream_id;
+
+ u32 plane_src_mask;
+
+ bool has_dual_ldb;
+};
+
+struct dpu_soc {
+ struct device *dev;
+ const struct dpu_data *data;
+ spinlock_t lock;
+ struct list_head list;
+
+ struct device *pd_dc_dev;
+ struct device *pd_pll0_dev;
+ struct device *pd_pll1_dev;
+ struct device_link *pd_dc_link;
+ struct device_link *pd_pll0_link;
+ struct device_link *pd_pll1_link;
+
+ void __iomem *cm_reg;
+
+ int id;
+ int usecount;
+
+ int irq_extdst0_shdload;
+ int irq_extdst4_shdload;
+ int irq_extdst1_shdload;
+ int irq_extdst5_shdload;
+ int irq_disengcfg_shdload0;
+ int irq_disengcfg_framecomplete0;
+ int irq_sig0_shdload;
+ int irq_sig0_valid;
+ int irq_disengcfg_shdload1;
+ int irq_disengcfg_framecomplete1;
+ int irq_sig1_shdload;
+ int irq_sig1_valid;
+ int irq_comctrl_sw0;
+ int irq_comctrl_sw1;
+ int irq_comctrl_sw2;
+ int irq_comctrl_sw3;
+ int irq_line_num;
+
+ bool irq_chip_pm_get_extdst0_shdload;
+ bool irq_chip_pm_get_extdst4_shdload;
+ bool irq_chip_pm_get_extdst1_shdload;
+ bool irq_chip_pm_get_extdst5_shdload;
+ bool irq_chip_pm_get_disengcfg_shdload0;
+ bool irq_chip_pm_get_disengcfg_framecomplete0;
+ bool irq_chip_pm_get_sig0_shdload;
+ bool irq_chip_pm_get_sig0_valid;
+ bool irq_chip_pm_get_disengcfg_shdload1;
+ bool irq_chip_pm_get_disengcfg_framecomplete1;
+ bool irq_chip_pm_get_sig1_shdload;
+ bool irq_chip_pm_get_sig1_valid;
+ bool irq_chip_pm_get_comctrl_sw0;
+ bool irq_chip_pm_get_comctrl_sw1;
+ bool irq_chip_pm_get_comctrl_sw2;
+ bool irq_chip_pm_get_comctrl_sw3;
+
+ struct irq_domain *domain;
+
+ struct imx_sc_ipc *dpu_ipc_handle;
+
+ struct dpu_constframe *cf_priv[4];
+ struct dpu_disengcfg *dec_priv[2];
+ struct dpu_extdst *ed_priv[4];
+ struct dpu_fetchunit *fd_priv[2];
+ struct dpu_fetchunit *fe_priv[4];
+ struct dpu_framegen *fg_priv[2];
+ struct dpu_fetchunit *fl_priv[1];
+ struct dpu_fetchunit *fw_priv[1];
+ struct dpu_hscaler *hs_priv[3];
+ struct dpu_layerblend *lb_priv[4];
+ struct dpu_signature *sig_priv[2];
+ struct dpu_store *st_priv[1];
+ struct dpu_tcon *tcon_priv[2];
+ struct dpu_vscaler *vs_priv[3];
+};
+
+int dpu_format_horz_chroma_subsampling(u32 format);
+int dpu_format_vert_chroma_subsampling(u32 format);
+int dpu_format_num_planes(u32 format);
+int dpu_format_plane_width(int width, u32 format, int plane);
+int dpu_format_plane_height(int height, u32 format, int plane);
+
+#define _DECLARE_DPU_UNIT_INIT_FUNC(block) \
+void _dpu_##block##_init(struct dpu_soc *dpu, unsigned int id) \
+
+_DECLARE_DPU_UNIT_INIT_FUNC(cf);
+_DECLARE_DPU_UNIT_INIT_FUNC(dec);
+_DECLARE_DPU_UNIT_INIT_FUNC(ed);
+_DECLARE_DPU_UNIT_INIT_FUNC(fd);
+_DECLARE_DPU_UNIT_INIT_FUNC(fe);
+_DECLARE_DPU_UNIT_INIT_FUNC(fg);
+_DECLARE_DPU_UNIT_INIT_FUNC(fl);
+_DECLARE_DPU_UNIT_INIT_FUNC(fw);
+_DECLARE_DPU_UNIT_INIT_FUNC(hs);
+_DECLARE_DPU_UNIT_INIT_FUNC(lb);
+_DECLARE_DPU_UNIT_INIT_FUNC(sig);
+_DECLARE_DPU_UNIT_INIT_FUNC(st);
+_DECLARE_DPU_UNIT_INIT_FUNC(tcon);
+_DECLARE_DPU_UNIT_INIT_FUNC(vs);
+
+#define DECLARE_DPU_UNIT_INIT_FUNC(block) \
+int dpu_##block##_init(struct dpu_soc *dpu, unsigned int id, \
+ unsigned long pec_base, unsigned long base)
+
+DECLARE_DPU_UNIT_INIT_FUNC(cf);
+DECLARE_DPU_UNIT_INIT_FUNC(dec);
+DECLARE_DPU_UNIT_INIT_FUNC(ed);
+DECLARE_DPU_UNIT_INIT_FUNC(fd);
+DECLARE_DPU_UNIT_INIT_FUNC(fe);
+DECLARE_DPU_UNIT_INIT_FUNC(fg);
+DECLARE_DPU_UNIT_INIT_FUNC(fl);
+DECLARE_DPU_UNIT_INIT_FUNC(fw);
+DECLARE_DPU_UNIT_INIT_FUNC(hs);
+DECLARE_DPU_UNIT_INIT_FUNC(lb);
+DECLARE_DPU_UNIT_INIT_FUNC(sig);
+DECLARE_DPU_UNIT_INIT_FUNC(st);
+DECLARE_DPU_UNIT_INIT_FUNC(tcon);
+DECLARE_DPU_UNIT_INIT_FUNC(vs);
+
+static inline u32 dpu_pec_fu_read(struct dpu_fetchunit *fu, unsigned int offset)
+{
+ return readl(fu->pec_base + offset);
+}
+
+static inline void dpu_pec_fu_write(struct dpu_fetchunit *fu,
+ unsigned int offset, u32 value)
+{
+ writel(value, fu->pec_base + offset);
+}
+
+static inline u32 dpu_fu_read(struct dpu_fetchunit *fu, unsigned int offset)
+{
+ return readl(fu->base + offset);
+}
+
+static inline void dpu_fu_write(struct dpu_fetchunit *fu,
+ unsigned int offset, u32 value)
+{
+ writel(value, fu->base + offset);
+}
+
+static inline u32 rgb_color(u8 r, u8 g, u8 b, u8 a)
+{
+ return (r << 24) | (g << 16) | (b << 8) | a;
+}
+
+static inline u32 yuv_color(u8 y, u8 u, u8 v)
+{
+ return (y << 24) | (u << 16) | (v << 8);
+}
+
+void tcon_get_pc(struct dpu_tcon *tcon, void *data);
+
+static const unsigned int cf_ids[] = {0, 1, 4, 5};
+static const unsigned int dec_ids[] = {0, 1};
+static const unsigned int ed_ids[] = {0, 1, 4, 5};
+static const unsigned int fd_ids[] = {0, 1};
+static const unsigned int fe_ids[] = {0, 1, 2, 9};
+static const unsigned int fg_ids[] = {0, 1};
+static const unsigned int fl_ids[] = {0};
+static const unsigned int fw_ids[] = {2};
+static const unsigned int hs_ids[] = {4, 5, 9};
+static const unsigned int lb_ids[] = {0, 1, 2, 3};
+static const unsigned int sig_ids[] = {0, 1};
+static const unsigned int st_ids[] = {9};
+static const unsigned int tcon_ids[] = {0, 1};
+static const unsigned int vs_ids[] = {4, 5, 9};
+
+static const unsigned int fd_dprc_ids[] = {3, 4};
+static const unsigned int fl_dprc_ids[] = {2};
+static const unsigned int fw_dprc_ids[] = {5};
+
+struct dpu_pixel_format {
+ u32 pixel_format;
+ u32 bits;
+ u32 shift;
+};
+
+static const struct dpu_pixel_format dpu_pixel_format_matrix[] = {
+ {
+ DRM_FORMAT_ARGB8888,
+ R_BITS(8) | G_BITS(8) | B_BITS(8) | A_BITS(8),
+ R_SHIFT(16) | G_SHIFT(8) | B_SHIFT(0) | A_SHIFT(24),
+ }, {
+ DRM_FORMAT_XRGB8888,
+ R_BITS(8) | G_BITS(8) | B_BITS(8) | A_BITS(0),
+ R_SHIFT(16) | G_SHIFT(8) | B_SHIFT(0) | A_SHIFT(0),
+ }, {
+ DRM_FORMAT_ABGR8888,
+ R_BITS(8) | G_BITS(8) | B_BITS(8) | A_BITS(8),
+ R_SHIFT(0) | G_SHIFT(8) | B_SHIFT(16) | A_SHIFT(24),
+ }, {
+ DRM_FORMAT_XBGR8888,
+ R_BITS(8) | G_BITS(8) | B_BITS(8) | A_BITS(0),
+ R_SHIFT(0) | G_SHIFT(8) | B_SHIFT(16) | A_SHIFT(0),
+ }, {
+ DRM_FORMAT_RGBA8888,
+ R_BITS(8) | G_BITS(8) | B_BITS(8) | A_BITS(8),
+ R_SHIFT(24) | G_SHIFT(16) | B_SHIFT(8) | A_SHIFT(0),
+ }, {
+ DRM_FORMAT_RGBX8888,
+ R_BITS(8) | G_BITS(8) | B_BITS(8) | A_BITS(0),
+ R_SHIFT(24) | G_SHIFT(16) | B_SHIFT(8) | A_SHIFT(0),
+ }, {
+ DRM_FORMAT_BGRA8888,
+ R_BITS(8) | G_BITS(8) | B_BITS(8) | A_BITS(8),
+ R_SHIFT(8) | G_SHIFT(16) | B_SHIFT(24) | A_SHIFT(0),
+ }, {
+ DRM_FORMAT_BGRX8888,
+ R_BITS(8) | G_BITS(8) | B_BITS(8) | A_BITS(0),
+ R_SHIFT(8) | G_SHIFT(16) | B_SHIFT(24) | A_SHIFT(0),
+ }, {
+ DRM_FORMAT_RGB888,
+ R_BITS(8) | G_BITS(8) | B_BITS(8) | A_BITS(0),
+ R_SHIFT(16) | G_SHIFT(8) | B_SHIFT(0) | A_SHIFT(0),
+ }, {
+ DRM_FORMAT_BGR888,
+ R_BITS(8) | G_BITS(8) | B_BITS(8) | A_BITS(0),
+ R_SHIFT(0) | G_SHIFT(8) | B_SHIFT(16) | A_SHIFT(0),
+ }, {
+ DRM_FORMAT_RGB565,
+ R_BITS(5) | G_BITS(6) | B_BITS(5) | A_BITS(0),
+ R_SHIFT(11) | G_SHIFT(5) | B_SHIFT(0) | A_SHIFT(0),
+ }, {
+ DRM_FORMAT_YUYV,
+ Y_BITS(8) | U_BITS(8) | V_BITS(8) | A_BITS(0),
+ Y_SHIFT(0) | U_SHIFT(8) | V_SHIFT(8) | A_SHIFT(0),
+ }, {
+ DRM_FORMAT_UYVY,
+ Y_BITS(8) | U_BITS(8) | V_BITS(8) | A_BITS(0),
+ Y_SHIFT(8) | U_SHIFT(0) | V_SHIFT(0) | A_SHIFT(0),
+ }, {
+ DRM_FORMAT_NV12,
+ Y_BITS(8) | U_BITS(8) | V_BITS(8) | A_BITS(0),
+ Y_SHIFT(0) | U_SHIFT(0) | V_SHIFT(8) | A_SHIFT(0),
+ }, {
+ DRM_FORMAT_NV21,
+ Y_BITS(8) | U_BITS(8) | V_BITS(8) | A_BITS(0),
+ Y_SHIFT(0) | U_SHIFT(8) | V_SHIFT(0) | A_SHIFT(0),
+ }, {
+ DRM_FORMAT_NV16,
+ Y_BITS(8) | U_BITS(8) | V_BITS(8) | A_BITS(0),
+ Y_SHIFT(0) | U_SHIFT(0) | V_SHIFT(8) | A_SHIFT(0),
+ }, {
+ DRM_FORMAT_NV61,
+ Y_BITS(8) | U_BITS(8) | V_BITS(8) | A_BITS(0),
+ Y_SHIFT(0) | U_SHIFT(8) | V_SHIFT(0) | A_SHIFT(0),
+ }, {
+ DRM_FORMAT_NV24,
+ Y_BITS(8) | U_BITS(8) | V_BITS(8) | A_BITS(0),
+ Y_SHIFT(0) | U_SHIFT(0) | V_SHIFT(8) | A_SHIFT(0),
+ }, {
+ DRM_FORMAT_NV42,
+ Y_BITS(8) | U_BITS(8) | V_BITS(8) | A_BITS(0),
+ Y_SHIFT(0) | U_SHIFT(8) | V_SHIFT(0) | A_SHIFT(0),
+ },
+};
+
+int dpu_sc_misc_get_handle(struct dpu_soc *dpu);
+int dpu_pxlink_set_mst_addr(struct dpu_soc *dpu, int disp_id, u32 val);
+int dpu_pxlink_set_mst_enable(struct dpu_soc *dpu, int disp_id, bool enable);
+int dpu_pxlink_set_mst_valid(struct dpu_soc *dpu, int disp_id, bool enable);
+int dpu_pxlink_set_sync_ctrl(struct dpu_soc *dpu, int disp_id, bool enable);
+int dpu_pxlink_set_dc_sync_mode(struct dpu_soc *dpu, bool enable);
+int dpu_sc_misc_init(struct dpu_soc *dpu);
+#endif /* __DPU_PRV_H__ */
diff --git a/drivers/gpu/imx/dpu/dpu-sc-misc.c b/drivers/gpu/imx/dpu/dpu-sc-misc.c
new file mode 100644
index 000000000000..20f600cb5a68
--- /dev/null
+++ b/drivers/gpu/imx/dpu/dpu-sc-misc.c
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2019 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <dt-bindings/firmware/imx/rsrc.h>
+#include "dpu-prv.h"
+
+static inline int
+dpu_sc_misc_set_ctrl(struct dpu_soc *dpu, u32 rsc, u8 ctrl, u32 val)
+{
+ return imx_sc_misc_set_control(dpu->dpu_ipc_handle, rsc, ctrl, val);
+}
+
+int dpu_sc_misc_get_handle(struct dpu_soc *dpu)
+{
+ return imx_scu_get_handle(&dpu->dpu_ipc_handle);
+}
+
+int dpu_pxlink_set_mst_addr(struct dpu_soc *dpu, int disp_id, u32 val)
+{
+ u32 rsc = dpu->id ? IMX_SC_R_DC_1 : IMX_SC_R_DC_0;
+ u8 ctrl = disp_id ?
+ IMX_SC_C_PXL_LINK_MST2_ADDR : IMX_SC_C_PXL_LINK_MST1_ADDR;
+
+ return dpu_sc_misc_set_ctrl(dpu, rsc, ctrl, val);
+}
+
+int dpu_pxlink_set_mst_enable(struct dpu_soc *dpu, int disp_id, bool enable)
+{
+ u32 rsc = dpu->id ? IMX_SC_R_DC_1 : IMX_SC_R_DC_0;
+ u8 ctrl = disp_id ?
+ IMX_SC_C_PXL_LINK_MST2_ENB: IMX_SC_C_PXL_LINK_MST1_ENB;
+
+ return dpu_sc_misc_set_ctrl(dpu, rsc, ctrl, enable);
+}
+
+int dpu_pxlink_set_mst_valid(struct dpu_soc *dpu, int disp_id, bool enable)
+{
+ u32 rsc = dpu->id ? IMX_SC_R_DC_1 : IMX_SC_R_DC_0;
+ u8 ctrl = disp_id ?
+ IMX_SC_C_PXL_LINK_MST2_VLD : IMX_SC_C_PXL_LINK_MST1_VLD;
+
+ return dpu_sc_misc_set_ctrl(dpu, rsc, ctrl, enable);
+}
+
+int dpu_pxlink_set_sync_ctrl(struct dpu_soc *dpu, int disp_id, bool enable)
+{
+ u32 rsc = dpu->id ? IMX_SC_R_DC_1 : IMX_SC_R_DC_0;
+ u8 ctrl = disp_id ? IMX_SC_C_SYNC_CTRL1 : IMX_SC_C_SYNC_CTRL0;
+
+ return dpu_sc_misc_set_ctrl(dpu, rsc, ctrl, enable);
+}
+
+int dpu_pxlink_set_dc_sync_mode(struct dpu_soc *dpu, bool enable)
+{
+ u32 rsc = dpu->id ? IMX_SC_R_DC_1 : IMX_SC_R_DC_0;
+
+ return dpu_sc_misc_set_ctrl(dpu, rsc, IMX_SC_C_MODE, enable);
+}
+
+/* KACHUNK_CNT is needed for blit engine */
+int dpu_sc_misc_set_kachunk_cnt(struct dpu_soc *dpu, u32 cnt)
+{
+ u32 rsc = dpu->id ? IMX_SC_R_DC_1 : IMX_SC_R_DC_0;
+
+ return dpu_sc_misc_set_ctrl(dpu, rsc, IMX_SC_C_KACHUNK_CNT, cnt);
+}
+
+int dpu_sc_misc_init(struct dpu_soc *dpu)
+{
+ int disp_id, ret = 0;
+
+ for (disp_id = 0; disp_id < 2; disp_id++) {
+ ret |= dpu_pxlink_set_mst_addr(dpu, disp_id, 0);
+ ret |= dpu_pxlink_set_mst_enable(dpu, disp_id, false);
+ ret |= dpu_pxlink_set_mst_valid(dpu, disp_id, false);
+ ret |= dpu_pxlink_set_sync_ctrl(dpu, disp_id, false);
+ }
+
+ ret |= dpu_sc_misc_set_kachunk_cnt(dpu, 32);
+
+ return ret;
+}
diff --git a/drivers/gpu/imx/dpu/dpu-signature.c b/drivers/gpu/imx/dpu/dpu-signature.c
new file mode 100644
index 000000000000..5fca7a1c4343
--- /dev/null
+++ b/drivers/gpu/imx/dpu/dpu-signature.c
@@ -0,0 +1,392 @@
+/*
+ * Copyright 2019,2020 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include "dpu-prv.h"
+
+#define STATICCONTROL 0x8
+#define SHDLDSEL BIT(4)
+#define LOCAL 0
+#define GLOBAL BIT(4)
+#define PANICCOLOR 0xC
+#define EVALCONTRL(n) (0x10 + (n) * 0x24)
+#define ENGLOBALPANIC BIT(17)
+#define ENLOCALPANIC BIT(16)
+#define ALPHAINV BIT(9)
+#define ALPHAMASK BIT(8)
+#define ENCRC BIT(1)
+#define ENEVALWIN BIT(0)
+#define EVALUPPERLEFT(n) (0x14 + (n) * 0x24)
+#define EVALLOWERRIGHT(n) (0x18 + (n) * 0x24)
+#define YEVAL(y) (((y) & 0x3FFF) << 16)
+#define XEVAL(x) ((x) & 0x3FFF)
+#define SIGCRCREDREF(n) (0x1C + (n) * 0x24)
+#define SIGCRCGREENREF(n) (0x20 + (n) * 0x24)
+#define SIGCRCBLUEREF(n) (0x24 + (n) * 0x24)
+#define SIGCRCRED(n) (0x28 + (n) * 0x24)
+#define SIGCRCGREEN(n) (0x2C + (n) * 0x24)
+#define SIGCRCBLUE(n) (0x30 + (n) * 0x24)
+#define SHADOWLOAD 0x130
+#define SHDLDREQ(n) BIT(n)
+#define CONTINUOUSMODE 0x134
+#define ENCONT BIT(0)
+#define SOFTWAREKICK 0x138
+#define KICK BIT(0)
+#define STATUS 0x13C
+#define STSSIGIDLE BIT(20)
+#define STSSIGVALID BIT(16)
+#define STSSIGERROR(n) BIT(n)
+#define STSSIGERROR_MASK 0xFF
+
+struct dpu_signature {
+ void __iomem *base;
+ struct mutex mutex;
+ int id;
+ bool inuse;
+ struct dpu_soc *dpu;
+};
+
+static inline u32 dpu_sig_read(struct dpu_signature *sig, unsigned int offset)
+{
+ return readl(sig->base + offset);
+}
+
+static inline void dpu_sig_write(struct dpu_signature *sig,
+ unsigned int offset, u32 value)
+{
+ writel(value, sig->base + offset);
+}
+
+void signature_shden(struct dpu_signature *sig, bool enable)
+{
+ u32 val;
+
+ mutex_lock(&sig->mutex);
+ val = dpu_sig_read(sig, STATICCONTROL);
+ if (enable)
+ val |= SHDEN;
+ else
+ val &= ~SHDEN;
+ dpu_sig_write(sig, STATICCONTROL, val);
+ mutex_unlock(&sig->mutex);
+}
+EXPORT_SYMBOL_GPL(signature_shden);
+
+void signature_shdldsel_local(struct dpu_signature *sig)
+{
+ u32 val;
+
+ mutex_lock(&sig->mutex);
+ val = dpu_sig_read(sig, STATICCONTROL);
+ val &= ~GLOBAL;
+ dpu_sig_write(sig, STATICCONTROL, val);
+ mutex_unlock(&sig->mutex);
+}
+EXPORT_SYMBOL_GPL(signature_shdldsel_local);
+
+void signature_shdldsel_global(struct dpu_signature *sig)
+{
+ u32 val;
+
+ mutex_lock(&sig->mutex);
+ val = dpu_sig_read(sig, STATICCONTROL);
+ dpu_sig_write(sig, STATICCONTROL, val | GLOBAL);
+ mutex_unlock(&sig->mutex);
+}
+EXPORT_SYMBOL_GPL(signature_shdldsel_global);
+
+void
+signature_global_panic(struct dpu_signature *sig, unsigned int win, bool enable)
+{
+ u32 val;
+
+ mutex_lock(&sig->mutex);
+ val = dpu_sig_read(sig, EVALCONTRL(win));
+ if (enable)
+ val |= ENGLOBALPANIC;
+ else
+ val &= ~ENGLOBALPANIC;
+ dpu_sig_write(sig, EVALCONTRL(win), val);
+ mutex_unlock(&sig->mutex);
+}
+EXPORT_SYMBOL_GPL(signature_global_panic);
+
+void
+signature_local_panic(struct dpu_signature *sig, unsigned int win, bool enable)
+{
+ u32 val;
+
+ mutex_lock(&sig->mutex);
+ val = dpu_sig_read(sig, EVALCONTRL(win));
+ if (enable)
+ val |= ENLOCALPANIC;
+ else
+ val &= ~ENLOCALPANIC;
+ dpu_sig_write(sig, EVALCONTRL(win), val);
+ mutex_unlock(&sig->mutex);
+}
+EXPORT_SYMBOL_GPL(signature_local_panic);
+
+void
+signature_alpha_mask(struct dpu_signature *sig, unsigned int win, bool enable)
+{
+ u32 val;
+
+ mutex_lock(&sig->mutex);
+ val = dpu_sig_read(sig, EVALCONTRL(win));
+ if (enable)
+ val |= ALPHAMASK;
+ else
+ val &= ~ALPHAMASK;
+ dpu_sig_write(sig, EVALCONTRL(win), val);
+ mutex_unlock(&sig->mutex);
+}
+EXPORT_SYMBOL_GPL(signature_alpha_mask);
+
+void signature_crc(struct dpu_signature *sig, unsigned int win, bool enable)
+{
+ u32 val;
+
+ mutex_lock(&sig->mutex);
+ val = dpu_sig_read(sig, EVALCONTRL(win));
+ if (enable)
+ val |= ENCRC;
+ else
+ val &= ~ENCRC;
+ dpu_sig_write(sig, EVALCONTRL(win), val);
+ mutex_unlock(&sig->mutex);
+}
+EXPORT_SYMBOL_GPL(signature_crc);
+
+void
+signature_eval_win(struct dpu_signature *sig, unsigned int win, bool enable)
+{
+ u32 val;
+
+ mutex_lock(&sig->mutex);
+ val = dpu_sig_read(sig, EVALCONTRL(win));
+ if (enable)
+ val |= ENEVALWIN;
+ else
+ val &= ~ENEVALWIN;
+ dpu_sig_write(sig, EVALCONTRL(win), val);
+ mutex_unlock(&sig->mutex);
+}
+EXPORT_SYMBOL_GPL(signature_eval_win);
+
+void signature_win(struct dpu_signature *sig, unsigned int win,
+ int xul, int yul, int xlr, int ylr)
+{
+ mutex_lock(&sig->mutex);
+ dpu_sig_write(sig, EVALUPPERLEFT(win), XEVAL(xul) | YEVAL(yul));
+ dpu_sig_write(sig, EVALLOWERRIGHT(win), XEVAL(--xlr) | YEVAL(--ylr));
+ mutex_unlock(&sig->mutex);
+}
+EXPORT_SYMBOL_GPL(signature_win);
+
+void signature_crc_value(struct dpu_signature *sig, unsigned int win,
+ u32 *red, u32 *green, u32 *blue)
+{
+ mutex_lock(&sig->mutex);
+ *red = dpu_sig_read(sig, SIGCRCRED(win));
+ *green = dpu_sig_read(sig, SIGCRCGREEN(win));
+ *blue = dpu_sig_read(sig, SIGCRCBLUE(win));
+ mutex_unlock(&sig->mutex);
+}
+EXPORT_SYMBOL_GPL(signature_crc_value);
+
+void signature_shdldreq(struct dpu_signature *sig, u8 win_mask)
+{
+ mutex_lock(&sig->mutex);
+ dpu_sig_write(sig, SHADOWLOAD, win_mask);
+ mutex_unlock(&sig->mutex);
+}
+EXPORT_SYMBOL_GPL(signature_shdldreq);
+
+void signature_continuous_mode(struct dpu_signature *sig, bool enable)
+{
+ u32 val;
+
+ mutex_lock(&sig->mutex);
+ val = dpu_sig_read(sig, CONTINUOUSMODE);
+ if (enable)
+ val |= ENCONT;
+ else
+ val &= ~ENCONT;
+ dpu_sig_write(sig, CONTINUOUSMODE, val);
+ mutex_unlock(&sig->mutex);
+}
+EXPORT_SYMBOL_GPL(signature_continuous_mode);
+
+void signature_kick(struct dpu_signature *sig)
+{
+ mutex_lock(&sig->mutex);
+ dpu_sig_write(sig, SOFTWAREKICK, KICK);
+ mutex_unlock(&sig->mutex);
+}
+EXPORT_SYMBOL_GPL(signature_kick);
+
+bool signature_is_idle(struct dpu_signature *sig)
+{
+ u32 val;
+
+ mutex_lock(&sig->mutex);
+ val = dpu_sig_read(sig, STATUS);
+ mutex_unlock(&sig->mutex);
+
+ return !!(val & STSSIGIDLE);
+}
+EXPORT_SYMBOL_GPL(signature_is_idle);
+
+void signature_wait_for_idle(struct dpu_signature *sig)
+{
+ unsigned long timeout = jiffies + msecs_to_jiffies(100);
+ bool idle;
+
+ do {
+ idle = signature_is_idle(sig);
+ } while (!idle && time_before(jiffies, timeout));
+
+ if (idle)
+ dev_dbg(sig->dpu->dev, "Signature%d is idle\n", sig->id);
+ else
+ dev_err(sig->dpu->dev,
+ "failed to wait for Signature%d idle\n", sig->id);
+}
+EXPORT_SYMBOL_GPL(signature_wait_for_idle);
+
+bool signature_is_valid(struct dpu_signature *sig)
+{
+ u32 val;
+
+ mutex_lock(&sig->mutex);
+ val = dpu_sig_read(sig, STATUS);
+ mutex_unlock(&sig->mutex);
+
+ return !!(val & STSSIGVALID);
+}
+EXPORT_SYMBOL_GPL(signature_is_valid);
+
+bool signature_is_error(struct dpu_signature *sig, u8 *err_win_mask)
+{
+ u32 val;
+
+ mutex_lock(&sig->mutex);
+ val = dpu_sig_read(sig, STATUS);
+ mutex_unlock(&sig->mutex);
+
+ *err_win_mask = val & STSSIGERROR_MASK;
+
+ return !!(*err_win_mask);
+}
+EXPORT_SYMBOL_GPL(signature_is_error);
+
+struct dpu_signature *dpu_sig_get(struct dpu_soc *dpu, int id)
+{
+ struct dpu_signature *sig;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(sig_ids); i++)
+ if (sig_ids[i] == id)
+ break;
+
+ if (i == ARRAY_SIZE(sig_ids))
+ return ERR_PTR(-EINVAL);
+
+ sig = dpu->sig_priv[i];
+
+ mutex_lock(&sig->mutex);
+
+ if (sig->inuse) {
+ mutex_unlock(&sig->mutex);
+ return ERR_PTR(-EBUSY);
+ }
+
+ sig->inuse = true;
+
+ mutex_unlock(&sig->mutex);
+
+ return sig;
+}
+EXPORT_SYMBOL_GPL(dpu_sig_get);
+
+void dpu_sig_put(struct dpu_signature *sig)
+{
+ mutex_lock(&sig->mutex);
+
+ sig->inuse = false;
+
+ mutex_unlock(&sig->mutex);
+}
+EXPORT_SYMBOL_GPL(dpu_sig_put);
+
+struct dpu_signature *dpu_aux_sig_peek(struct dpu_signature *sig)
+{
+ return sig->dpu->sig_priv[sig->id ^ 1];
+}
+EXPORT_SYMBOL_GPL(dpu_aux_sig_peek);
+
+void _dpu_sig_init(struct dpu_soc *dpu, unsigned int id)
+{
+ struct dpu_signature *sig;
+ int i, j;
+
+ for (i = 0; i < ARRAY_SIZE(sig_ids); i++)
+ if (sig_ids[i] == id)
+ break;
+
+ if (WARN_ON(i == ARRAY_SIZE(sig_ids)))
+ return;
+
+ sig = dpu->sig_priv[i];
+
+ signature_shden(sig, true);
+ signature_shdldsel_local(sig);
+ for (j = 0; j < MAX_DPU_SIGNATURE_WIN_NUM; j++) {
+ signature_global_panic(sig, j, false);
+ signature_local_panic(sig, j, false);
+ signature_alpha_mask(sig, j, false);
+ signature_crc(sig, j, false);
+ signature_eval_win(sig, j, false);
+ signature_continuous_mode(sig, false);
+ }
+}
+
+int dpu_sig_init(struct dpu_soc *dpu, unsigned int id,
+ unsigned long unused, unsigned long base)
+{
+ struct dpu_signature *sig;
+
+ sig = devm_kzalloc(dpu->dev, sizeof(*sig), GFP_KERNEL);
+ if (!sig)
+ return -ENOMEM;
+
+ dpu->sig_priv[id] = sig;
+
+ sig->base = devm_ioremap(dpu->dev, base, SZ_512);
+ if (!sig->base)
+ return -ENOMEM;
+
+ sig->dpu = dpu;
+ sig->id = id;
+ mutex_init(&sig->mutex);
+
+ _dpu_sig_init(dpu, id);
+
+ return 0;
+}
diff --git a/drivers/gpu/imx/dpu/dpu-store.c b/drivers/gpu/imx/dpu/dpu-store.c
new file mode 100644
index 000000000000..cbd06b83581b
--- /dev/null
+++ b/drivers/gpu/imx/dpu/dpu-store.c
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2018-2019 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include "dpu-prv.h"
+
+#define PIXENGCFG_STATIC 0x8
+#define DIV(n) (((n) & 0xFF) << 16)
+#define DIV_RESET 0x80
+
+struct dpu_store {
+ void __iomem *pec_base;
+ void __iomem *base;
+ struct mutex mutex;
+ int id;
+ bool inuse;
+ struct dpu_soc *dpu;
+};
+
+static inline u32 dpu_pec_st_read(struct dpu_store *st, unsigned int offset)
+{
+ return readl(st->pec_base + offset);
+}
+
+static inline void dpu_pec_st_write(struct dpu_store *st,
+ unsigned int offset, u32 value)
+{
+ writel(value, st->pec_base + offset);
+}
+
+void store_pixengcfg_syncmode_fixup(struct dpu_store *st, bool enable)
+{
+ struct dpu_soc *dpu;
+ u32 val;
+
+ if (!st)
+ return;
+
+ dpu = st->dpu;
+
+ mutex_lock(&st->mutex);
+ val = dpu_pec_st_read(st, PIXENGCFG_STATIC);
+ if (enable)
+ val |= BIT(16);
+ else
+ val &= ~BIT(16);
+ dpu_pec_st_write(st, PIXENGCFG_STATIC, val);
+ mutex_unlock(&st->mutex);
+}
+EXPORT_SYMBOL_GPL(store_pixengcfg_syncmode_fixup);
+
+struct dpu_store *dpu_st_get(struct dpu_soc *dpu, int id)
+{
+ struct dpu_store *st;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(st_ids); i++)
+ if (st_ids[i] == id)
+ break;
+
+ if (i == ARRAY_SIZE(st_ids))
+ return ERR_PTR(-EINVAL);
+
+ st = dpu->st_priv[i];
+
+ mutex_lock(&st->mutex);
+
+ if (st->inuse) {
+ mutex_unlock(&st->mutex);
+ return ERR_PTR(-EBUSY);
+ }
+
+ st->inuse = true;
+
+ mutex_unlock(&st->mutex);
+
+ return st;
+}
+EXPORT_SYMBOL_GPL(dpu_st_get);
+
+void dpu_st_put(struct dpu_store *st)
+{
+ mutex_lock(&st->mutex);
+
+ st->inuse = false;
+
+ mutex_unlock(&st->mutex);
+}
+EXPORT_SYMBOL_GPL(dpu_st_put);
+
+void _dpu_st_init(struct dpu_soc *dpu, unsigned int id)
+{
+ struct dpu_store *st;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(st_ids); i++)
+ if (st_ids[i] == id)
+ break;
+
+ if (WARN_ON(i == ARRAY_SIZE(st_ids)))
+ return;
+
+ st = dpu->st_priv[i];
+
+ dpu_pec_st_write(st, PIXENGCFG_STATIC, SHDEN | DIV(DIV_RESET));
+}
+
+int dpu_st_init(struct dpu_soc *dpu, unsigned int id,
+ unsigned long pec_base, unsigned long base)
+{
+ struct dpu_store *st;
+ int i;
+
+ st = devm_kzalloc(dpu->dev, sizeof(*st), GFP_KERNEL);
+ if (!st)
+ return -ENOMEM;
+
+ for (i = 0; i < ARRAY_SIZE(st_ids); i++)
+ if (st_ids[i] == id)
+ break;
+
+ if (i == ARRAY_SIZE(st_ids))
+ return -EINVAL;
+
+ dpu->st_priv[i] = st;
+
+ st->pec_base = devm_ioremap(dpu->dev, pec_base, SZ_32);
+ if (!st->pec_base)
+ return -ENOMEM;
+
+ st->base = devm_ioremap(dpu->dev, base, SZ_256);
+ if (!st->base)
+ return -ENOMEM;
+
+ st->dpu = dpu;
+ st->id = id;
+ mutex_init(&st->mutex);
+
+ _dpu_st_init(dpu, id);
+
+ return 0;
+}
diff --git a/drivers/gpu/imx/dpu/dpu-tcon.c b/drivers/gpu/imx/dpu/dpu-tcon.c
new file mode 100644
index 000000000000..bbecc2c720ba
--- /dev/null
+++ b/drivers/gpu/imx/dpu/dpu-tcon.c
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2016 Freescale Semiconductor, Inc.
+ * Copyright 2017-2020 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <linux/io.h>
+#include <linux/media-bus-format.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include <video/dpu.h>
+#include <video/imx8-pc.h>
+#include "dpu-prv.h"
+
+#define SSQCNTS 0
+#define SSQCYCLE 0x8
+#define SWRESET 0xC
+#define TCON_CTRL 0x10
+#define BYPASS BIT(3)
+#define RSDSINVCTRL 0x14
+#define MAPBIT3_0 0x18
+#define MAPBIT7_4 0x1C
+#define MAPBIT11_8 0x20
+#define MAPBIT15_12 0x24
+#define MAPBIT19_16 0x28
+#define MAPBIT23_20 0x2C
+#define MAPBIT27_24 0x30
+#define MAPBIT31_28 0x34
+#define MAPBIT34_32 0x38
+#define MAPBIT3_0_DUAL 0x3C
+#define MAPBIT7_4_DUAL 0x40
+#define MAPBIT11_8_DUAL 0x44
+#define MAPBIT15_12_DUAL 0x48
+#define MAPBIT19_16_DUAL 0x4C
+#define MAPBIT23_20_DUAL 0x50
+#define MAPBIT27_24_DUAL 0x54
+#define MAPBIT31_28_DUAL 0x58
+#define MAPBIT34_32_DUAL 0x5C
+#define SPGPOSON(n) (0x60 + (n) * 16)
+#define X(n) (((n) & 0x7FFF) << 16)
+#define Y(n) ((n) & 0x7FFF)
+#define SPGMASKON(n) (0x64 + (n) * 16)
+#define SPGPOSOFF(n) (0x68 + (n) * 16)
+#define SPGMASKOFF(n) (0x6C + (n) * 16)
+#define SMXSIGS(n) (0x120 + (n) * 8)
+#define SMXFCTTABLE(n) (0x124 + (n) * 8)
+#define RESET_OVER_UNFERFLOW 0x180
+#define DUAL_DEBUG 0x184
+
+struct dpu_tcon {
+ void __iomem *base;
+ struct mutex mutex;
+ int id;
+ bool inuse;
+ struct dpu_soc *dpu;
+ struct pc *pc;
+};
+
+static inline u32 dpu_tcon_read(struct dpu_tcon *tcon, unsigned int offset)
+{
+ return readl(tcon->base + offset);
+}
+
+static inline void dpu_tcon_write(struct dpu_tcon *tcon,
+ unsigned int offset, u32 value)
+{
+ writel(value, tcon->base + offset);
+}
+
+int tcon_set_fmt(struct dpu_tcon *tcon, u32 bus_format)
+{
+ switch (bus_format) {
+ case MEDIA_BUS_FMT_RGB888_1X24:
+ dpu_tcon_write(tcon, MAPBIT3_0, 0x19181716);
+ dpu_tcon_write(tcon, MAPBIT7_4, 0x1d1c1b1a);
+ dpu_tcon_write(tcon, MAPBIT11_8, 0x0f0e0d0c);
+ dpu_tcon_write(tcon, MAPBIT15_12, 0x13121110);
+ dpu_tcon_write(tcon, MAPBIT19_16, 0x05040302);
+ dpu_tcon_write(tcon, MAPBIT23_20, 0x09080706);
+ break;
+ case MEDIA_BUS_FMT_RGB101010_1X30:
+ case MEDIA_BUS_FMT_RGB888_1X30_PADLO:
+ case MEDIA_BUS_FMT_RGB666_1X30_PADLO:
+ case MEDIA_BUS_FMT_RGB565_1X30_PADLO:
+ dpu_tcon_write(tcon, MAPBIT3_0, 0x17161514);
+ dpu_tcon_write(tcon, MAPBIT7_4, 0x1b1a1918);
+ dpu_tcon_write(tcon, MAPBIT11_8, 0x0b0a1d1c);
+ dpu_tcon_write(tcon, MAPBIT15_12, 0x0f0e0d0c);
+ dpu_tcon_write(tcon, MAPBIT19_16, 0x13121110);
+ dpu_tcon_write(tcon, MAPBIT23_20, 0x03020100);
+ dpu_tcon_write(tcon, MAPBIT27_24, 0x07060504);
+ dpu_tcon_write(tcon, MAPBIT31_28, 0x00000908);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(tcon_set_fmt);
+
+/* This function is used to workaround TKT320590 which is related to DPR/PRG. */
+void tcon_set_operation_mode(struct dpu_tcon *tcon)
+{
+ u32 val;
+
+ val = dpu_tcon_read(tcon, TCON_CTRL);
+ val &= ~BYPASS;
+ dpu_tcon_write(tcon, TCON_CTRL, val);
+}
+EXPORT_SYMBOL_GPL(tcon_set_operation_mode);
+
+void tcon_cfg_videomode(struct dpu_tcon *tcon,
+ struct drm_display_mode *m, bool side_by_side)
+{
+ u32 val;
+ int hdisplay, hsync_start, hsync_end;
+ int vdisplay, vsync_start, vsync_end;
+ int y;
+
+ hdisplay = m->hdisplay;
+ vdisplay = m->vdisplay;
+ hsync_start = m->hsync_start;
+ vsync_start = m->vsync_start;
+ hsync_end = m->hsync_end;
+ vsync_end = m->vsync_end;
+
+ if (side_by_side) {
+ hdisplay /= 2;
+ hsync_start /= 2;
+ hsync_end /= 2;
+ }
+
+ /*
+ * TKT320590:
+ * Turn TCON into operation mode later after the first dumb frame is
+ * generated by DPU. This makes DPR/PRG be able to evade the frame.
+ */
+ val = dpu_tcon_read(tcon, TCON_CTRL);
+ val |= BYPASS;
+ dpu_tcon_write(tcon, TCON_CTRL, val);
+
+ /* dsp_control[0]: hsync */
+ dpu_tcon_write(tcon, SPGPOSON(0), X(hsync_start));
+ dpu_tcon_write(tcon, SPGMASKON(0), 0xffff);
+
+ dpu_tcon_write(tcon, SPGPOSOFF(0), X(hsync_end));
+ dpu_tcon_write(tcon, SPGMASKOFF(0), 0xffff);
+
+ dpu_tcon_write(tcon, SMXSIGS(0), 0x2);
+ dpu_tcon_write(tcon, SMXFCTTABLE(0), 0x1);
+
+ /* dsp_control[1]: vsync */
+ dpu_tcon_write(tcon, SPGPOSON(1), X(hsync_start) | Y(vsync_start - 1));
+ dpu_tcon_write(tcon, SPGMASKON(1), 0x0);
+
+ dpu_tcon_write(tcon, SPGPOSOFF(1), X(hsync_start) | Y(vsync_end - 1));
+ dpu_tcon_write(tcon, SPGMASKOFF(1), 0x0);
+
+ dpu_tcon_write(tcon, SMXSIGS(1), 0x3);
+ dpu_tcon_write(tcon, SMXFCTTABLE(1), 0x1);
+
+ /* dsp_control[2]: data enable */
+ /* horizontal */
+ dpu_tcon_write(tcon, SPGPOSON(2), 0x0);
+ dpu_tcon_write(tcon, SPGMASKON(2), 0xffff);
+
+ dpu_tcon_write(tcon, SPGPOSOFF(2), X(hdisplay));
+ dpu_tcon_write(tcon, SPGMASKOFF(2), 0xffff);
+
+ /* vertical */
+ dpu_tcon_write(tcon, SPGPOSON(3), 0x0);
+ dpu_tcon_write(tcon, SPGMASKON(3), 0x7fff0000);
+
+ dpu_tcon_write(tcon, SPGPOSOFF(3), Y(vdisplay));
+ dpu_tcon_write(tcon, SPGMASKOFF(3), 0x7fff0000);
+
+ dpu_tcon_write(tcon, SMXSIGS(2), 0x2c);
+ dpu_tcon_write(tcon, SMXFCTTABLE(2), 0x8);
+
+ /* dsp_control[3]: kachuck */
+ y = vdisplay + 1;
+ /*
+ * If sync mode fixup is present, the kachuck signal from slave tcon
+ * should be one line later than the one from master tcon.
+ */
+ if (side_by_side && tcon_is_slave(tcon))
+ y++;
+
+ dpu_tcon_write(tcon, SPGPOSON(4), X(0x0) | Y(y));
+ dpu_tcon_write(tcon, SPGMASKON(4), 0x0);
+
+ dpu_tcon_write(tcon, SPGPOSOFF(4), X(0x20) | Y(y));
+ dpu_tcon_write(tcon, SPGMASKOFF(4), 0x0);
+
+ dpu_tcon_write(tcon, SMXSIGS(3), 0x6);
+ dpu_tcon_write(tcon, SMXFCTTABLE(3), 0x2);
+}
+EXPORT_SYMBOL_GPL(tcon_cfg_videomode);
+
+bool tcon_is_master(struct dpu_tcon *tcon)
+{
+ const struct dpu_data *data = tcon->dpu->data;
+
+ return tcon->id == data->master_stream_id;
+}
+EXPORT_SYMBOL_GPL(tcon_is_master);
+
+bool tcon_is_slave(struct dpu_tcon *tcon)
+{
+ return !tcon_is_master(tcon);
+}
+EXPORT_SYMBOL_GPL(tcon_is_slave);
+
+void tcon_configure_pc(struct dpu_tcon *tcon, unsigned int di,
+ unsigned int frame_width, u32 mode, u32 format)
+{
+ if (WARN_ON(!tcon || !tcon->pc))
+ return;
+
+ pc_configure(tcon->pc, di, frame_width, mode, format);
+}
+EXPORT_SYMBOL_GPL(tcon_configure_pc);
+
+void tcon_enable_pc(struct dpu_tcon *tcon)
+{
+ if (WARN_ON(!tcon || !tcon->pc))
+ return;
+
+ pc_enable(tcon->pc);
+}
+EXPORT_SYMBOL_GPL(tcon_enable_pc);
+
+void tcon_disable_pc(struct dpu_tcon *tcon)
+{
+ if (WARN_ON(!tcon || !tcon->pc))
+ return;
+
+ pc_disable(tcon->pc);
+}
+EXPORT_SYMBOL_GPL(tcon_disable_pc);
+
+struct dpu_tcon *dpu_tcon_get(struct dpu_soc *dpu, int id)
+{
+ struct dpu_tcon *tcon;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(tcon_ids); i++)
+ if (tcon_ids[i] == id)
+ break;
+
+ if (i == ARRAY_SIZE(tcon_ids))
+ return ERR_PTR(-EINVAL);
+
+ tcon = dpu->tcon_priv[i];
+
+ mutex_lock(&tcon->mutex);
+
+ if (tcon->inuse) {
+ mutex_unlock(&tcon->mutex);
+ return ERR_PTR(-EBUSY);
+ }
+
+ tcon->inuse = true;
+
+ mutex_unlock(&tcon->mutex);
+
+ return tcon;
+}
+EXPORT_SYMBOL_GPL(dpu_tcon_get);
+
+void dpu_tcon_put(struct dpu_tcon *tcon)
+{
+ mutex_lock(&tcon->mutex);
+
+ tcon->inuse = false;
+
+ mutex_unlock(&tcon->mutex);
+}
+EXPORT_SYMBOL_GPL(dpu_tcon_put);
+
+struct dpu_tcon *dpu_aux_tcon_peek(struct dpu_tcon *tcon)
+{
+ return tcon->dpu->tcon_priv[tcon->id ^ 1];
+}
+EXPORT_SYMBOL_GPL(dpu_aux_tcon_peek);
+
+void _dpu_tcon_init(struct dpu_soc *dpu, unsigned int id)
+{
+}
+
+int dpu_tcon_init(struct dpu_soc *dpu, unsigned int id,
+ unsigned long unused, unsigned long base)
+{
+ struct dpu_tcon *tcon;
+
+ tcon = devm_kzalloc(dpu->dev, sizeof(*tcon), GFP_KERNEL);
+ if (!tcon)
+ return -ENOMEM;
+
+ dpu->tcon_priv[id] = tcon;
+
+ tcon->base = devm_ioremap(dpu->dev, base, SZ_512);
+ if (!tcon->base)
+ return -ENOMEM;
+
+ tcon->dpu = dpu;
+ mutex_init(&tcon->mutex);
+
+ return 0;
+}
+
+void tcon_get_pc(struct dpu_tcon *tcon, void *data)
+{
+ if (WARN_ON(!tcon))
+ return;
+
+ tcon->pc = data;
+}
diff --git a/drivers/gpu/imx/dpu/dpu-vscaler.c b/drivers/gpu/imx/dpu/dpu-vscaler.c
new file mode 100644
index 000000000000..b1bdcd596392
--- /dev/null
+++ b/drivers/gpu/imx/dpu/dpu-vscaler.c
@@ -0,0 +1,438 @@
+/*
+ * Copyright 2017-2019 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include <video/dpu.h>
+#include "dpu-prv.h"
+
+#define PIXENGCFG_DYNAMIC 0x8
+#define PIXENGCFG_DYNAMIC_SRC_SEL_MASK 0x3F
+
+#define SETUP1 0xC
+#define SCALE_FACTOR_MASK 0xFFFFF
+#define SCALE_FACTOR(n) ((n) & 0xFFFFF)
+#define SETUP2 0x10
+#define SETUP3 0x14
+#define SETUP4 0x18
+#define SETUP5 0x1C
+#define PHASE_OFFSET_MASK 0x1FFFFF
+#define PHASE_OFFSET(n) ((n) & 0x1FFFFF)
+#define CONTROL 0x20
+#define OUTPUT_SIZE_MASK 0x3FFF0000
+#define OUTPUT_SIZE(n) ((((n) - 1) << 16) & OUTPUT_SIZE_MASK)
+#define FIELD_MODE 0x3000
+#define FILTER_MODE 0x100
+#define SCALE_MODE 0x10
+#define MODE 0x1
+
+static const vs_src_sel_t src_sels[3][6] = {
+ {
+ VS_SRC_SEL__DISABLE,
+ VS_SRC_SEL__FETCHDECODE0,
+ VS_SRC_SEL__MATRIX4,
+ VS_SRC_SEL__HSCALER4,
+ }, {
+ VS_SRC_SEL__DISABLE,
+ VS_SRC_SEL__FETCHDECODE1,
+ VS_SRC_SEL__MATRIX5,
+ VS_SRC_SEL__HSCALER5,
+ }, {
+ VS_SRC_SEL__DISABLE,
+ VS_SRC_SEL__MATRIX9,
+ VS_SRC_SEL__HSCALER9,
+ },
+};
+
+struct dpu_vscaler {
+ void __iomem *pec_base;
+ void __iomem *base;
+ struct mutex mutex;
+ int id;
+ bool inuse;
+ struct dpu_soc *dpu;
+ /* see DPU_PLANE_SRC_xxx */
+ unsigned int stream_id;
+};
+
+static inline u32 dpu_pec_vs_read(struct dpu_vscaler *vs,
+ unsigned int offset)
+{
+ return readl(vs->pec_base + offset);
+}
+
+static inline void dpu_pec_vs_write(struct dpu_vscaler *vs,
+ unsigned int offset, u32 value)
+{
+ writel(value, vs->pec_base + offset);
+}
+
+static inline u32 dpu_vs_read(struct dpu_vscaler *vs, unsigned int offset)
+{
+ return readl(vs->base + offset);
+}
+
+static inline void dpu_vs_write(struct dpu_vscaler *vs,
+ unsigned int offset, u32 value)
+{
+ writel(value, vs->base + offset);
+}
+
+int vscaler_pixengcfg_dynamic_src_sel(struct dpu_vscaler *vs, vs_src_sel_t src)
+{
+ struct dpu_soc *dpu = vs->dpu;
+ const unsigned int vs_id_array[] = {4, 5, 9};
+ int i, j;
+ u32 val;
+
+ for (i = 0; i < ARRAY_SIZE(vs_id_array); i++)
+ if (vs_id_array[i] == vs->id)
+ break;
+
+ if (WARN_ON(i == ARRAY_SIZE(vs_id_array)))
+ return -EINVAL;
+
+ mutex_lock(&vs->mutex);
+ for (j = 0; j < ARRAY_SIZE(src_sels[0]); j++) {
+ if (src_sels[i][j] == src) {
+ val = dpu_pec_vs_read(vs, PIXENGCFG_DYNAMIC);
+ val &= ~PIXENGCFG_DYNAMIC_SRC_SEL_MASK;
+ val |= src;
+ dpu_pec_vs_write(vs, PIXENGCFG_DYNAMIC, val);
+ mutex_unlock(&vs->mutex);
+ return 0;
+ }
+ }
+ mutex_unlock(&vs->mutex);
+
+ dev_err(dpu->dev, "Invalid source for VScaler%d\n", vs->id);
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(vscaler_pixengcfg_dynamic_src_sel);
+
+void vscaler_pixengcfg_clken(struct dpu_vscaler *vs, pixengcfg_clken_t clken)
+{
+ u32 val;
+
+ mutex_lock(&vs->mutex);
+ val = dpu_pec_vs_read(vs, PIXENGCFG_DYNAMIC);
+ val &= ~CLKEN_MASK;
+ val |= clken << CLKEN_MASK_SHIFT;
+ dpu_pec_vs_write(vs, PIXENGCFG_DYNAMIC, val);
+ mutex_unlock(&vs->mutex);
+}
+EXPORT_SYMBOL_GPL(vscaler_pixengcfg_clken);
+
+void vscaler_shden(struct dpu_vscaler *vs, bool enable)
+{
+ u32 val;
+
+ mutex_lock(&vs->mutex);
+ val = dpu_vs_read(vs, STATICCONTROL);
+ if (enable)
+ val |= SHDEN;
+ else
+ val &= ~SHDEN;
+ dpu_vs_write(vs, STATICCONTROL, val);
+ mutex_unlock(&vs->mutex);
+}
+EXPORT_SYMBOL_GPL(vscaler_shden);
+
+void vscaler_setup1(struct dpu_vscaler *vs, u32 src, u32 dst, bool deinterlace)
+{
+ struct dpu_soc *dpu = vs->dpu;
+ u32 scale_factor;
+ u64 tmp64;
+
+ if (deinterlace)
+ dst *= 2;
+
+ if (src == dst) {
+ scale_factor = 0x80000;
+ } else {
+ if (src > dst) {
+ tmp64 = (u64)((u64)dst * 0x80000);
+ do_div(tmp64, src);
+
+ } else {
+ tmp64 = (u64)((u64)src * 0x80000);
+ do_div(tmp64, dst);
+ }
+ scale_factor = (u32)tmp64;
+ }
+
+ WARN_ON(scale_factor > 0x80000);
+
+ mutex_lock(&vs->mutex);
+ dpu_vs_write(vs, SETUP1, SCALE_FACTOR(scale_factor));
+ mutex_unlock(&vs->mutex);
+
+ dev_dbg(dpu->dev, "Vscaler%d scale factor 0x%08x\n",
+ vs->id, scale_factor);
+}
+EXPORT_SYMBOL_GPL(vscaler_setup1);
+
+void vscaler_setup2(struct dpu_vscaler *vs, bool deinterlace)
+{
+ /* 0x20000: +0.25 phase offset for deinterlace */
+ u32 phase_offset = deinterlace ? 0x20000 : 0;
+
+ mutex_lock(&vs->mutex);
+ dpu_vs_write(vs, SETUP2, PHASE_OFFSET(phase_offset));
+ mutex_unlock(&vs->mutex);
+}
+EXPORT_SYMBOL_GPL(vscaler_setup2);
+
+void vscaler_setup3(struct dpu_vscaler *vs, bool deinterlace)
+{
+ /* 0x1e0000: -0.25 phase offset for deinterlace */
+ u32 phase_offset = deinterlace ? 0x1e0000 : 0;
+
+ mutex_lock(&vs->mutex);
+ dpu_vs_write(vs, SETUP3, PHASE_OFFSET(phase_offset));
+ mutex_unlock(&vs->mutex);
+}
+EXPORT_SYMBOL_GPL(vscaler_setup3);
+
+void vscaler_setup4(struct dpu_vscaler *vs, u32 phase_offset)
+{
+ mutex_lock(&vs->mutex);
+ dpu_vs_write(vs, SETUP4, PHASE_OFFSET(phase_offset));
+ mutex_unlock(&vs->mutex);
+}
+EXPORT_SYMBOL_GPL(vscaler_setup4);
+
+void vscaler_setup5(struct dpu_vscaler *vs, u32 phase_offset)
+{
+ mutex_lock(&vs->mutex);
+ dpu_vs_write(vs, SETUP5, PHASE_OFFSET(phase_offset));
+ mutex_unlock(&vs->mutex);
+}
+EXPORT_SYMBOL_GPL(vscaler_setup5);
+
+void vscaler_output_size(struct dpu_vscaler *vs, u32 line_num)
+{
+ u32 val;
+
+ mutex_lock(&vs->mutex);
+ val = dpu_vs_read(vs, CONTROL);
+ val &= ~OUTPUT_SIZE_MASK;
+ val |= OUTPUT_SIZE(line_num);
+ dpu_vs_write(vs, CONTROL, val);
+ mutex_unlock(&vs->mutex);
+}
+EXPORT_SYMBOL_GPL(vscaler_output_size);
+
+void vscaler_field_mode(struct dpu_vscaler *vs, scaler_field_mode_t m)
+{
+ u32 val;
+
+ mutex_lock(&vs->mutex);
+ val = dpu_vs_read(vs, CONTROL);
+ val &= ~FIELD_MODE;
+ val |= m;
+ dpu_vs_write(vs, CONTROL, val);
+ mutex_unlock(&vs->mutex);
+}
+EXPORT_SYMBOL_GPL(vscaler_field_mode);
+
+void vscaler_filter_mode(struct dpu_vscaler *vs, scaler_filter_mode_t m)
+{
+ u32 val;
+
+ mutex_lock(&vs->mutex);
+ val = dpu_vs_read(vs, CONTROL);
+ val &= ~FILTER_MODE;
+ val |= m;
+ dpu_vs_write(vs, CONTROL, val);
+ mutex_unlock(&vs->mutex);
+}
+EXPORT_SYMBOL_GPL(vscaler_filter_mode);
+
+void vscaler_scale_mode(struct dpu_vscaler *vs, scaler_scale_mode_t m)
+{
+ u32 val;
+
+ mutex_lock(&vs->mutex);
+ val = dpu_vs_read(vs, CONTROL);
+ val &= ~SCALE_MODE;
+ val |= m;
+ dpu_vs_write(vs, CONTROL, val);
+ mutex_unlock(&vs->mutex);
+}
+EXPORT_SYMBOL_GPL(vscaler_scale_mode);
+
+void vscaler_mode(struct dpu_vscaler *vs, scaler_mode_t m)
+{
+ u32 val;
+
+ mutex_lock(&vs->mutex);
+ val = dpu_vs_read(vs, CONTROL);
+ val &= ~MODE;
+ val |= m;
+ dpu_vs_write(vs, CONTROL, val);
+ mutex_unlock(&vs->mutex);
+}
+EXPORT_SYMBOL_GPL(vscaler_mode);
+
+bool vscaler_is_enabled(struct dpu_vscaler *vs)
+{
+ u32 val;
+
+ mutex_lock(&vs->mutex);
+ val = dpu_vs_read(vs, CONTROL);
+ mutex_unlock(&vs->mutex);
+
+ return (val & MODE) == SCALER_ACTIVE;
+}
+EXPORT_SYMBOL_GPL(vscaler_is_enabled);
+
+dpu_block_id_t vscaler_get_block_id(struct dpu_vscaler *vs)
+{
+ switch (vs->id) {
+ case 4:
+ return ID_VSCALER4;
+ case 5:
+ return ID_VSCALER5;
+ case 9:
+ return ID_VSCALER9;
+ default:
+ WARN_ON(1);
+ }
+
+ return ID_NONE;
+}
+EXPORT_SYMBOL_GPL(vscaler_get_block_id);
+
+unsigned int vscaler_get_stream_id(struct dpu_vscaler *vs)
+{
+ return vs->stream_id;
+}
+EXPORT_SYMBOL_GPL(vscaler_get_stream_id);
+
+void vscaler_set_stream_id(struct dpu_vscaler *vs, unsigned int id)
+{
+ switch (id) {
+ case DPU_PLANE_SRC_TO_DISP_STREAM0:
+ case DPU_PLANE_SRC_TO_DISP_STREAM1:
+ case DPU_PLANE_SRC_DISABLED:
+ vs->stream_id = id;
+ break;
+ default:
+ WARN_ON(1);
+ }
+}
+EXPORT_SYMBOL_GPL(vscaler_set_stream_id);
+
+struct dpu_vscaler *dpu_vs_get(struct dpu_soc *dpu, int id)
+{
+ struct dpu_vscaler *vs;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(vs_ids); i++)
+ if (vs_ids[i] == id)
+ break;
+
+ if (i == ARRAY_SIZE(vs_ids))
+ return ERR_PTR(-EINVAL);
+
+ vs = dpu->vs_priv[i];
+
+ mutex_lock(&vs->mutex);
+
+ if (vs->inuse) {
+ mutex_unlock(&vs->mutex);
+ return ERR_PTR(-EBUSY);
+ }
+
+ vs->inuse = true;
+
+ mutex_unlock(&vs->mutex);
+
+ return vs;
+}
+EXPORT_SYMBOL_GPL(dpu_vs_get);
+
+void dpu_vs_put(struct dpu_vscaler *vs)
+{
+ mutex_lock(&vs->mutex);
+
+ vs->inuse = false;
+
+ mutex_unlock(&vs->mutex);
+}
+EXPORT_SYMBOL_GPL(dpu_vs_put);
+
+void _dpu_vs_init(struct dpu_soc *dpu, unsigned int id)
+{
+ struct dpu_vscaler *vs;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(vs_ids); i++)
+ if (vs_ids[i] == id)
+ break;
+
+ if (WARN_ON(i == ARRAY_SIZE(vs_ids)))
+ return;
+
+ vs = dpu->vs_priv[i];
+
+ vscaler_shden(vs, true);
+ vscaler_setup2(vs, false);
+ vscaler_setup3(vs, false);
+ vscaler_setup4(vs, 0);
+ vscaler_setup5(vs, 0);
+ vscaler_pixengcfg_dynamic_src_sel(vs, VS_SRC_SEL__DISABLE);
+}
+
+int dpu_vs_init(struct dpu_soc *dpu, unsigned int id,
+ unsigned long pec_base, unsigned long base)
+{
+ struct dpu_vscaler *vs;
+ int i;
+
+ vs = devm_kzalloc(dpu->dev, sizeof(*vs), GFP_KERNEL);
+ if (!vs)
+ return -ENOMEM;
+
+ for (i = 0; i < ARRAY_SIZE(vs_ids); i++)
+ if (vs_ids[i] == id)
+ break;
+
+ if (i == ARRAY_SIZE(vs_ids))
+ return -EINVAL;
+
+ dpu->vs_priv[i] = vs;
+
+ vs->pec_base = devm_ioremap(dpu->dev, pec_base, SZ_8);
+ if (!vs->pec_base)
+ return -ENOMEM;
+
+ vs->base = devm_ioremap(dpu->dev, base, SZ_1K);
+ if (!vs->base)
+ return -ENOMEM;
+
+ vs->dpu = dpu;
+ vs->id = id;
+
+ mutex_init(&vs->mutex);
+
+ _dpu_vs_init(dpu, id);
+
+ return 0;
+}
diff --git a/drivers/gpu/imx/imx8_dprc.c b/drivers/gpu/imx/imx8_dprc.c
new file mode 100644
index 000000000000..fc7508c528a7
--- /dev/null
+++ b/drivers/gpu/imx/imx8_dprc.c
@@ -0,0 +1,893 @@
+/*
+ * Copyright 2017-2021 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <drm/drm_fourcc.h>
+#include <dt-bindings/firmware/imx/rsrc.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/firmware/imx/sci.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <video/imx8-prefetch.h>
+
+#define SET 0x4
+#define CLR 0x8
+#define TOG 0xc
+
+#define SYSTEM_CTRL0 0x00
+#define BCMD2AXI_MASTR_ID_CTRL BIT(16)
+#define SW_SHADOW_LOAD_SEL BIT(4)
+#define SHADOW_LOAD_EN BIT(3)
+#define REPEAT_EN BIT(2)
+#define SOFT_RESET BIT(1)
+#define RUN_EN BIT(0) /* self-clearing */
+
+#define IRQ_MASK 0x20
+#define IRQ_MASK_STATUS 0x30
+#define IRQ_NONMASK_STATUS 0x40
+#define DPR2RTR_FIFO_LOAD_BUF_RDY_UV_ERROR BIT(7)
+#define DPR2RTR_FIFO_LOAD_BUF_RDY_YRGB_ERROR BIT(6)
+#define DPR2RTR_UV_FIFO_OVFL BIT(5)
+#define DPR2RTR_YRGB_FIFO_OVFL BIT(4)
+#define IRQ_AXI_READ_ERROR BIT(3)
+#define IRQ_DPR_SHADOW_LOADED_MASK BIT(2)
+#define IRQ_DPR_RUN BIT(1)
+#define IRQ_DPR_CRTL_DONE BIT(0)
+#define IRQ_ERROR_MASK 0xf8
+#define IRQ_CTRL_MASK 0x7
+
+#define MODE_CTRL0 0x50
+#define PIX_COMP_SEL_MASK 0x3fc00
+#define A_COMP_SEL(byte) (((byte) & 0x3) << 16)
+#define R_COMP_SEL(byte) (((byte) & 0x3) << 14)
+#define G_COMP_SEL(byte) (((byte) & 0x3) << 12)
+#define B_COMP_SEL(byte) (((byte) & 0x3) << 10)
+#define PIX_UV_SWAP BIT(9)
+#define VU BIT(9)
+#define UV 0
+#define PIXEL_LUMA_UV_SWAP BIT(8)
+#define UYVY BIT(8)
+#define YUYV 0
+#define PIX_SIZE 0xc0
+enum {
+ PIX_SIZE_8BIT = (0 << 6),
+ PIX_SIZE_16BIT = (1 << 6),
+ PIX_SIZE_32BIT = (2 << 6),
+ PIX_SIZE_RESERVED = (3 << 6),
+};
+#define COMP_2PLANE_EN BIT(5)
+#define YUV_EN BIT(4)
+#define TILE_TYPE 0xc
+enum {
+ LINEAR_TILE = (0 << 2),
+ GPU_STANDARD_TILE = (1 << 2),
+ GPU_SUPER_TILE = (2 << 2),
+ VPU_TILE = (3 << 2),
+};
+#define RTR_4LINE_BUF_EN BIT(1)
+#define LINE4 BIT(1)
+#define LINE8 0
+#define RTR_3BUF_EN BIT(0)
+#define BUF3 BIT(0)
+#define BUF2 0
+
+#define FRAME_CTRL0 0x70
+#define PITCH(n) (((n) & 0xffff) << 16)
+#define ROT_FLIP_ORDER_EN BIT(4)
+#define ROT_FIRST BIT(4)
+#define FLIP_FIRST 0
+#define ROT_ENC 0xc
+#define DEGREE(n) ((((n) / 90) & 0x3) << 2)
+#define VFLIP_EN BIT(1)
+#define HFLIP_EN BIT(0)
+
+#define FRAME_1P_CTRL0 0x90
+#define FRAME_2P_CTRL0 0xe0
+#define MAX_BYTES_PREQ 0x7
+enum {
+ BYTE_64 = 0x0,
+ BYTE_128 = 0x1,
+ BYTE_256 = 0x2,
+ BYTE_512 = 0x3,
+ BYTE_1K = 0x4,
+ BYTE_2K = 0x5,
+ BYTE_4K = 0x6,
+};
+
+#define FRAME_1P_PIX_X_CTRL 0xa0
+#define FRAME_2P_PIX_X_CTRL 0xf0
+#define NUM_X_PIX_WIDE(n) ((n) & 0xffff)
+#define FRAME_PIX_X_ULC_CTRL 0xf0
+#define CROP_ULC_X(n) ((n) & 0xffff)
+
+#define FRAME_1P_PIX_Y_CTRL 0xb0
+#define FRAME_2P_PIX_Y_CTRL 0x100
+#define NUM_Y_PIX_HIGH(n) ((n) & 0xffff)
+#define FRAME_PIX_Y_ULC_CTRL 0x100
+#define CROP_ULC_Y(n) ((n) & 0xffff)
+
+#define FRAME_1P_BASE_ADDR_CTRL0 0xc0
+#define FRAME_2P_BASE_ADDR_CTRL0 0x110
+
+#define STATUS_CTRL0 0x130
+#define STATUS_SRC_SEL 0x70000
+enum {
+ DPR_CTRL = 0x0,
+ PREFETCH_1PLANE = 0x1,
+ RESPONSE_1PLANE = 0x2,
+ PREFETCH_2PLANE = 0x3,
+ RESPONSE_2PLANE = 0x4,
+};
+#define STATUS_MUX_SEL 0x7
+
+#define STATUS_CTRL1 0x140
+
+#define RTRAM_CTRL0 0x200
+#define ABORT_SEL BIT(7)
+#define ABORT BIT(7)
+#define STALL 0
+#define THRES_LOW_MASK 0x70
+#define THRES_LOW(n) (((n) & 0x7) << 4)
+#define THRES_HIGH_MASK 0xe
+#define THRES_HIGH(n) (((n) & 0x7) << 1)
+#define NUM_ROWS_ACTIVE BIT(0)
+#define ROWS_0_6 BIT(0)
+#define ROWS_0_4 0
+
+struct dprc {
+ struct device *dev;
+ void __iomem *base;
+ struct list_head list;
+ struct clk *clk_apb;
+ struct clk *clk_b;
+ struct clk *clk_rtram;
+ struct imx_sc_ipc *ipc_handle;
+ spinlock_t spin_lock;
+ u32 sc_resource;
+ bool is_blit_chan;
+
+ /* The second one, if non-NULL, is auxiliary for UV buffer. */
+ struct prg *prgs[2];
+ bool has_aux_prg;
+ bool use_aux_prg;
+};
+
+struct dprc_format_info {
+ u32 format;
+ u8 depth;
+ u8 num_planes;
+ u8 cpp[3];
+ u8 hsub;
+ u8 vsub;
+};
+
+static const struct dprc_format_info formats[] = {
+ {
+ .format = DRM_FORMAT_RGB565,
+ .depth = 16, .num_planes = 1, .cpp = { 2, 0, 0 },
+ .hsub = 1, .vsub = 1,
+ }, {
+ .format = DRM_FORMAT_ARGB8888,
+ .depth = 32, .num_planes = 1, .cpp = { 4, 0, 0 },
+ .hsub = 1, .vsub = 1,
+ }, {
+ .format = DRM_FORMAT_XRGB8888,
+ .depth = 24, .num_planes = 1, .cpp = { 4, 0, 0 },
+ .hsub = 1, .vsub = 1,
+ }, {
+ .format = DRM_FORMAT_ABGR8888,
+ .depth = 32, .num_planes = 1, .cpp = { 4, 0, 0 },
+ .hsub = 1, .vsub = 1,
+ }, {
+ .format = DRM_FORMAT_XBGR8888,
+ .depth = 24, .num_planes = 1, .cpp = { 4, 0, 0 },
+ .hsub = 1, .vsub = 1,
+ }, {
+ .format = DRM_FORMAT_RGBA8888,
+ .depth = 32, .num_planes = 1, .cpp = { 4, 0, 0 },
+ .hsub = 1, .vsub = 1,
+ }, {
+ .format = DRM_FORMAT_RGBX8888,
+ .depth = 24, .num_planes = 1, .cpp = { 4, 0, 0 },
+ .hsub = 1, .vsub = 1,
+ }, {
+ .format = DRM_FORMAT_BGRA8888,
+ .depth = 32, .num_planes = 1, .cpp = { 4, 0, 0 },
+ .hsub = 1, .vsub = 1,
+ }, {
+ .format = DRM_FORMAT_BGRX8888,
+ .depth = 24, .num_planes = 1, .cpp = { 4, 0, 0 },
+ .hsub = 1, .vsub = 1,
+ }, {
+ .format = DRM_FORMAT_NV12,
+ .depth = 0, .num_planes = 2, .cpp = { 1, 2, 0 },
+ .hsub = 2, .vsub = 2,
+ }, {
+ .format = DRM_FORMAT_NV21,
+ .depth = 0, .num_planes = 2, .cpp = { 1, 2, 0 },
+ .hsub = 2, .vsub = 2,
+ }, {
+ .format = DRM_FORMAT_YUYV,
+ .depth = 0, .num_planes = 1, .cpp = { 2, 0, 0 },
+ .hsub = 2, .vsub = 1,
+ }, {
+ .format = DRM_FORMAT_UYVY,
+ .depth = 0, .num_planes = 1, .cpp = { 2, 0, 0 },
+ .hsub = 2, .vsub = 1,
+ }
+};
+
+static const struct dprc_format_info *dprc_format_info(u32 format)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(formats); ++i) {
+ if (formats[i].format == format)
+ return &formats[i];
+ }
+
+ return NULL;
+}
+
+static DEFINE_MUTEX(dprc_list_mutex);
+static LIST_HEAD(dprc_list);
+
+static inline u32 dprc_read(struct dprc *dprc, unsigned int offset)
+{
+ return readl(dprc->base + offset);
+}
+
+static inline void dprc_write(struct dprc *dprc, u32 value, unsigned int offset)
+{
+ writel(value, dprc->base + offset);
+}
+
+static void dprc_reset(struct dprc *dprc)
+{
+ dprc_write(dprc, SOFT_RESET, SYSTEM_CTRL0 + SET);
+
+ if (dprc->is_blit_chan)
+ usleep_range(10, 20);
+ else
+ usleep_range(1000, 2000);
+
+ dprc_write(dprc, SOFT_RESET, SYSTEM_CTRL0 + CLR);
+}
+
+void dprc_enable(struct dprc *dprc)
+{
+ if (WARN_ON(!dprc))
+ return;
+
+ prg_enable(dprc->prgs[0]);
+ if (dprc->use_aux_prg)
+ prg_enable(dprc->prgs[1]);
+}
+EXPORT_SYMBOL_GPL(dprc_enable);
+
+void dprc_disable(struct dprc *dprc)
+{
+ if (WARN_ON(!dprc))
+ return;
+
+ dprc_write(dprc, SHADOW_LOAD_EN | SW_SHADOW_LOAD_SEL, SYSTEM_CTRL0);
+
+ prg_disable(dprc->prgs[0]);
+ if (dprc->has_aux_prg)
+ prg_disable(dprc->prgs[1]);
+
+ prg_reg_update(dprc->prgs[0]);
+ if (dprc->has_aux_prg)
+ prg_reg_update(dprc->prgs[1]);
+}
+EXPORT_SYMBOL_GPL(dprc_disable);
+
+static inline void
+dprc_dpu_gpr_configure(struct dprc *dprc, unsigned int stream_id)
+{
+ int ret;
+
+ ret = imx_sc_misc_set_control(dprc->ipc_handle,
+ dprc->sc_resource, IMX_SC_C_KACHUNK_SEL, stream_id);
+ if (ret)
+ dev_warn(dprc->dev, "failed to set KACHUNK_SEL: %d\n", ret);
+}
+
+static inline void
+dprc_prg_sel_configure(struct dprc *dprc, u32 resource, bool enable)
+{
+ int ret;
+
+ ret = imx_sc_misc_set_control(dprc->ipc_handle,
+ resource, IMX_SC_C_SEL0, enable);
+ if (ret)
+ dev_warn(dprc->dev, "failed to set SEL0: %d\n", ret);
+}
+
+void dprc_configure(struct dprc *dprc, unsigned int stream_id,
+ unsigned int width, unsigned int height,
+ unsigned int x_offset, unsigned int y_offset,
+ unsigned int stride, u32 format, u64 modifier,
+ unsigned long baddr, unsigned long uv_baddr,
+ bool start, bool aux_start, bool interlace_frame)
+{
+ const struct dprc_format_info *info = dprc_format_info(format);
+ unsigned int dprc_width = width + x_offset;
+ unsigned int dprc_height;
+ unsigned int p1_w, p1_h, p2_w, p2_h;
+ unsigned int prg_stride = width * info->cpp[0];
+ unsigned int bpp = 8 * info->cpp[0];
+ unsigned int preq;
+ unsigned int mt_w = 0, mt_h = 0; /* w/h in a micro-tile */
+ u32 val;
+
+ if (WARN_ON(!dprc))
+ return;
+
+ dprc->use_aux_prg = false;
+
+ if (start) {
+ dprc_reset(dprc);
+
+ if (!dprc->is_blit_chan)
+ dprc_dpu_gpr_configure(dprc, stream_id);
+ }
+
+ if (interlace_frame) {
+ height /= 2;
+ y_offset /= 2;
+ }
+
+ dprc_height = height + y_offset;
+
+ /* disable all control irqs and enable all error irqs */
+ dprc_write(dprc, IRQ_CTRL_MASK, IRQ_MASK);
+
+ if (info->num_planes > 1) {
+ p1_w = round_up(dprc_width, modifier ? 8 : 64);
+ p1_h = round_up(dprc_height, 8);
+
+ p2_w = p1_w;
+ if (modifier)
+ p2_h = dprc_height / info->vsub;
+ else
+ p2_h = round_up((dprc_height / info->vsub), 8);
+
+ preq = modifier ? BYTE_64 : BYTE_1K;
+
+ dprc_write(dprc, preq, FRAME_2P_CTRL0);
+ if (dprc->sc_resource == IMX_SC_R_DC_0_BLIT1 ||
+ dprc->sc_resource == IMX_SC_R_DC_1_BLIT1) {
+ dprc_prg_sel_configure(dprc,
+ dprc->sc_resource == IMX_SC_R_DC_0_BLIT1 ?
+ IMX_SC_R_DC_0_BLIT0 : IMX_SC_R_DC_1_BLIT0,
+ true);
+ prg_set_auxiliary(dprc->prgs[1]);
+ dprc->has_aux_prg = true;
+ }
+ dprc_write(dprc, uv_baddr, FRAME_2P_BASE_ADDR_CTRL0);
+ } else {
+ switch (dprc->sc_resource) {
+ case IMX_SC_R_DC_0_BLIT0:
+ case IMX_SC_R_DC_1_BLIT0:
+ dprc_prg_sel_configure(dprc, dprc->sc_resource, false);
+ prg_set_primary(dprc->prgs[0]);
+ break;
+ case IMX_SC_R_DC_0_BLIT1:
+ case IMX_SC_R_DC_1_BLIT1:
+ dprc->has_aux_prg = false;
+ break;
+ default:
+ break;
+ }
+
+ switch (modifier) {
+ case DRM_FORMAT_MOD_VIVANTE_TILED:
+ p1_w = round_up(dprc_width, info->cpp[0] == 2 ? 8 : 4);
+ break;
+ case DRM_FORMAT_MOD_VIVANTE_SUPER_TILED:
+ if (dprc->is_blit_chan)
+ p1_w = round_up(dprc_width,
+ info->cpp[0] == 2 ? 8 : 4);
+ else
+ p1_w = round_up(dprc_width, 64);
+ break;
+ default:
+ p1_w = round_up(dprc_width,
+ info->cpp[0] == 2 ? 32 : 16);
+ break;
+ }
+ p1_h = round_up(dprc_height, 4);
+ }
+
+ dprc_write(dprc, PITCH(stride), FRAME_CTRL0);
+ switch (modifier) {
+ case DRM_FORMAT_MOD_AMPHION_TILED:
+ preq = BYTE_64;
+ mt_w = 8;
+ mt_h = 8;
+ break;
+ case DRM_FORMAT_MOD_VIVANTE_TILED:
+ preq = BYTE_256;
+ mt_w = bpp == 16 ? 8 : 4;
+ mt_h = 4;
+ break;
+ case DRM_FORMAT_MOD_VIVANTE_SUPER_TILED:
+ if (bpp == 16) {
+ preq = BYTE_64;
+ mt_w = 8;
+ } else {
+ preq = (x_offset % 8) ? BYTE_64 : BYTE_128;
+ mt_w = 4;
+ }
+ mt_h = 4;
+ break;
+ default:
+ preq = BYTE_1K;
+ break;
+ }
+ dprc_write(dprc, preq, FRAME_1P_CTRL0);
+ dprc_write(dprc, NUM_X_PIX_WIDE(p1_w), FRAME_1P_PIX_X_CTRL);
+ dprc_write(dprc, NUM_Y_PIX_HIGH(p1_h), FRAME_1P_PIX_Y_CTRL);
+ dprc_write(dprc, baddr, FRAME_1P_BASE_ADDR_CTRL0);
+ if (modifier) {
+ dprc_write(dprc, CROP_ULC_X(round_down(x_offset, mt_w)),
+ FRAME_PIX_X_ULC_CTRL);
+ dprc_write(dprc, CROP_ULC_Y(round_down(y_offset, mt_h)),
+ FRAME_PIX_Y_ULC_CTRL);
+ } else {
+ dprc_write(dprc, CROP_ULC_X(0), FRAME_PIX_X_ULC_CTRL);
+ dprc_write(dprc, CROP_ULC_Y(0), FRAME_PIX_Y_ULC_CTRL);
+ }
+
+ val = dprc_read(dprc, RTRAM_CTRL0);
+ val &= ~THRES_LOW_MASK;
+ val |= THRES_LOW(3);
+ val &= ~THRES_HIGH_MASK;
+ val |= THRES_HIGH(7);
+ dprc_write(dprc, val, RTRAM_CTRL0);
+
+ val = dprc_read(dprc, MODE_CTRL0);
+ val &= ~PIX_UV_SWAP;
+ val &= ~PIXEL_LUMA_UV_SWAP;
+ val &= ~COMP_2PLANE_EN;
+ val &= ~YUV_EN;
+ val &= ~TILE_TYPE;
+ switch (modifier) {
+ case DRM_FORMAT_MOD_NONE:
+ break;
+ case DRM_FORMAT_MOD_AMPHION_TILED:
+ val |= VPU_TILE;
+ break;
+ case DRM_FORMAT_MOD_VIVANTE_TILED:
+ val |= GPU_STANDARD_TILE;
+ break;
+ case DRM_FORMAT_MOD_VIVANTE_SUPER_TILED:
+ val |= GPU_SUPER_TILE;
+ break;
+ default:
+ dev_err(dprc->dev, "unsupported modifier 0x%016llx\n",
+ modifier);
+ return;
+ }
+ val &= ~RTR_4LINE_BUF_EN;
+ val |= info->num_planes > 1 ? LINE8 : LINE4;
+ val &= ~RTR_3BUF_EN;
+ val |= BUF2;
+ val &= ~(PIX_COMP_SEL_MASK | PIX_SIZE);
+ switch (format) {
+ case DRM_FORMAT_ARGB8888:
+ case DRM_FORMAT_XRGB8888:
+ case DRM_FORMAT_ABGR8888:
+ case DRM_FORMAT_XBGR8888:
+ case DRM_FORMAT_RGBA8888:
+ case DRM_FORMAT_RGBX8888:
+ case DRM_FORMAT_BGRA8888:
+ case DRM_FORMAT_BGRX8888:
+ /*
+ * It turns out pixel components are mapped directly
+ * without position change via DPR processing with
+ * the following color component configurations.
+ * Leave the pixel format to be handled by the
+ * display controllers.
+ */
+ val |= A_COMP_SEL(3) | R_COMP_SEL(2) |
+ G_COMP_SEL(1) | B_COMP_SEL(0);
+ val |= PIX_SIZE_32BIT;
+ break;
+ case DRM_FORMAT_YUYV:
+ case DRM_FORMAT_UYVY:
+ val |= YUV_EN;
+ fallthrough;
+ case DRM_FORMAT_RGB565:
+ val |= PIX_SIZE_16BIT;
+ break;
+ case DRM_FORMAT_NV12:
+ case DRM_FORMAT_NV21:
+ dprc->use_aux_prg = true;
+
+ val |= COMP_2PLANE_EN;
+ val |= YUV_EN;
+ val |= PIX_SIZE_8BIT;
+ break;
+ default:
+ dev_err(dprc->dev, "unsupported format 0x%08x\n", format);
+ return;
+ }
+ dprc_write(dprc, val, MODE_CTRL0);
+
+ if (dprc->is_blit_chan) {
+ val = SW_SHADOW_LOAD_SEL | RUN_EN | SHADOW_LOAD_EN;
+ dprc_write(dprc, val, SYSTEM_CTRL0);
+ } else if (start) {
+ /* software shadow load for the first frame */
+ val = SW_SHADOW_LOAD_SEL | SHADOW_LOAD_EN;
+ dprc_write(dprc, val, SYSTEM_CTRL0);
+
+ /* and then, run... */
+ val |= RUN_EN | REPEAT_EN;
+ dprc_write(dprc, val, SYSTEM_CTRL0);
+ }
+
+ prg_configure(dprc->prgs[0], width, height, x_offset, y_offset,
+ prg_stride, bpp, baddr, format, modifier, start);
+ if (dprc->use_aux_prg)
+ prg_configure(dprc->prgs[1], width, height, x_offset, y_offset,
+ prg_stride, 8, uv_baddr, format, modifier, aux_start);
+
+ dev_dbg(dprc->dev, "w-%u, h-%u, s-%u, fmt-0x%08x, mod-0x%016llx\n",
+ width, height, stride, format, modifier);
+}
+EXPORT_SYMBOL_GPL(dprc_configure);
+
+void dprc_disable_repeat_en(struct dprc *dprc)
+{
+ if (WARN_ON(!dprc))
+ return;
+
+ dprc_write(dprc, REPEAT_EN, SYSTEM_CTRL0 + CLR);
+}
+EXPORT_SYMBOL_GPL(dprc_disable_repeat_en);
+
+void dprc_reg_update(struct dprc *dprc)
+{
+ if (WARN_ON(!dprc))
+ return;
+
+ prg_reg_update(dprc->prgs[0]);
+ if (dprc->use_aux_prg)
+ prg_reg_update(dprc->prgs[1]);
+}
+EXPORT_SYMBOL_GPL(dprc_reg_update);
+
+void dprc_first_frame_handle(struct dprc *dprc)
+{
+ if (WARN_ON(!dprc))
+ return;
+
+ if (dprc->is_blit_chan)
+ return;
+
+ dprc_write(dprc, REPEAT_EN, SYSTEM_CTRL0);
+
+ prg_shadow_enable(dprc->prgs[0]);
+ if (dprc->use_aux_prg)
+ prg_shadow_enable(dprc->prgs[1]);
+}
+EXPORT_SYMBOL_GPL(dprc_first_frame_handle);
+
+void dprc_irq_handle(struct dprc *dprc)
+{
+ u32 mask, status;
+
+ if (WARN_ON(!dprc))
+ return;
+
+ spin_lock(&dprc->spin_lock);
+
+ mask = dprc_read(dprc, IRQ_MASK);
+ mask = ~mask;
+ status = dprc_read(dprc, IRQ_MASK_STATUS);
+ status &= mask;
+
+ /* disable irqs to be handled */
+ dprc_write(dprc, status, IRQ_MASK + SET);
+
+ /* clear status */
+ dprc_write(dprc, status, IRQ_MASK_STATUS);
+
+ if (status & DPR2RTR_FIFO_LOAD_BUF_RDY_UV_ERROR)
+ dev_err(dprc->dev,
+ "DPR to RTRAM FIFO load UV buffer ready error\n");
+
+ if (status & DPR2RTR_FIFO_LOAD_BUF_RDY_YRGB_ERROR)
+ dev_err(dprc->dev,
+ "DPR to RTRAM FIFO load YRGB buffer ready error\n");
+
+ if (status & DPR2RTR_UV_FIFO_OVFL)
+ dev_err(dprc->dev, "DPR to RTRAM FIFO UV FIFO overflow\n");
+
+ if (status & DPR2RTR_YRGB_FIFO_OVFL)
+ dev_err(dprc->dev, "DPR to RTRAM FIFO YRGB FIFO overflow\n");
+
+ if (status & IRQ_AXI_READ_ERROR)
+ dev_err(dprc->dev, "AXI read error\n");
+
+ if (status & IRQ_DPR_CRTL_DONE)
+ dprc_first_frame_handle(dprc);
+
+ spin_unlock(&dprc->spin_lock);
+}
+EXPORT_SYMBOL_GPL(dprc_irq_handle);
+
+void dprc_enable_ctrl_done_irq(struct dprc *dprc)
+{
+ unsigned long lock_flags;
+
+ if (WARN_ON(!dprc))
+ return;
+
+ spin_lock_irqsave(&dprc->spin_lock, lock_flags);
+ dprc_write(dprc, IRQ_DPR_CRTL_DONE, IRQ_MASK + CLR);
+ spin_unlock_irqrestore(&dprc->spin_lock, lock_flags);
+}
+EXPORT_SYMBOL_GPL(dprc_enable_ctrl_done_irq);
+
+bool dprc_format_supported(struct dprc *dprc, u32 format, u64 modifier)
+{
+ if (WARN_ON(!dprc))
+ return false;
+
+ switch (format) {
+ case DRM_FORMAT_ARGB8888:
+ case DRM_FORMAT_XRGB8888:
+ case DRM_FORMAT_ABGR8888:
+ case DRM_FORMAT_XBGR8888:
+ case DRM_FORMAT_RGBA8888:
+ case DRM_FORMAT_RGBX8888:
+ case DRM_FORMAT_BGRA8888:
+ case DRM_FORMAT_BGRX8888:
+ case DRM_FORMAT_RGB565:
+ return (modifier == DRM_FORMAT_MOD_NONE ||
+ modifier == DRM_FORMAT_MOD_VIVANTE_TILED ||
+ modifier == DRM_FORMAT_MOD_VIVANTE_SUPER_TILED);
+ case DRM_FORMAT_YUYV:
+ case DRM_FORMAT_UYVY:
+ switch (dprc->sc_resource) {
+ case IMX_SC_R_DC_0_FRAC0:
+ case IMX_SC_R_DC_1_FRAC0:
+ case IMX_SC_R_DC_0_WARP:
+ case IMX_SC_R_DC_1_WARP:
+ return false;
+ }
+ return modifier == DRM_FORMAT_MOD_NONE;
+ case DRM_FORMAT_NV12:
+ case DRM_FORMAT_NV21:
+ switch (dprc->sc_resource) {
+ case IMX_SC_R_DC_0_FRAC0:
+ case IMX_SC_R_DC_1_FRAC0:
+ case IMX_SC_R_DC_0_WARP:
+ case IMX_SC_R_DC_1_WARP:
+ return false;
+ case IMX_SC_R_DC_0_BLIT1:
+ case IMX_SC_R_DC_1_BLIT1:
+ return (modifier == DRM_FORMAT_MOD_NONE ||
+ modifier == DRM_FORMAT_MOD_AMPHION_TILED);
+ }
+ return (dprc->has_aux_prg &&
+ (modifier == DRM_FORMAT_MOD_NONE ||
+ modifier == DRM_FORMAT_MOD_AMPHION_TILED));
+ }
+
+ return false;
+}
+EXPORT_SYMBOL_GPL(dprc_format_supported);
+
+bool dprc_stride_supported(struct dprc *dprc,
+ unsigned int stride, unsigned int uv_stride,
+ unsigned int width, u32 format)
+{
+ const struct dprc_format_info *info = dprc_format_info(format);
+ unsigned int prg_stride = width * info->cpp[0];
+
+ if (WARN_ON(!dprc))
+ return false;
+
+ if (stride > 0xffff)
+ return false;
+
+ if (info->num_planes > 1 && stride != uv_stride)
+ return false;
+
+ return prg_stride_supported(dprc->prgs[0], prg_stride);
+}
+EXPORT_SYMBOL_GPL(dprc_stride_supported);
+
+bool dprc_stride_double_check(struct dprc *dprc,
+ unsigned int width, unsigned int x_offset,
+ u32 format, u64 modifier,
+ dma_addr_t baddr, dma_addr_t uv_baddr)
+{
+ const struct dprc_format_info *info = dprc_format_info(format);
+ unsigned int bpp = 8 * info->cpp[0];
+ unsigned int prg_stride = width * info->cpp[0];
+
+ if (WARN_ON(!dprc))
+ return false;
+
+ if (!prg_stride_double_check(dprc->prgs[0], width, x_offset,
+ bpp, modifier, prg_stride, baddr))
+ return false;
+
+ if (info->num_planes > 1 &&
+ !prg_stride_double_check(dprc->prgs[1], width, x_offset,
+ bpp, modifier, prg_stride, uv_baddr))
+ return false;
+
+ return true;
+}
+EXPORT_SYMBOL_GPL(dprc_stride_double_check);
+
+struct dprc *
+dprc_lookup_by_phandle(struct device *dev, const char *name, int index)
+{
+ struct device_node *dprc_node = of_parse_phandle(dev->of_node,
+ name, index);
+ struct dprc *dprc;
+
+ mutex_lock(&dprc_list_mutex);
+ list_for_each_entry(dprc, &dprc_list, list) {
+ if (dprc_node == dprc->dev->of_node) {
+ mutex_unlock(&dprc_list_mutex);
+ device_link_add(dev, dprc->dev,
+ DL_FLAG_AUTOREMOVE_CONSUMER);
+ return dprc;
+ }
+ }
+ mutex_unlock(&dprc_list_mutex);
+
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(dprc_lookup_by_phandle);
+
+static const struct of_device_id dprc_dt_ids[] = {
+ { .compatible = "fsl,imx8qm-dpr-channel", },
+ { .compatible = "fsl,imx8qxp-dpr-channel", },
+ { /* sentinel */ },
+};
+
+static int dprc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ struct dprc *dprc;
+ int ret, i;
+
+ dprc = devm_kzalloc(dev, sizeof(*dprc), GFP_KERNEL);
+ if (!dprc)
+ return -ENOMEM;
+
+ ret = imx_scu_get_handle(&dprc->ipc_handle);
+ if (ret)
+ return ret;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ dprc->base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(dprc->base))
+ return PTR_ERR(dprc->base);
+
+ dprc->clk_apb = devm_clk_get(dev, "apb");
+ if (IS_ERR(dprc->clk_apb))
+ return PTR_ERR(dprc->clk_apb);
+ clk_prepare_enable(dprc->clk_apb);
+
+ dprc->clk_b = devm_clk_get(dev, "b");
+ if (IS_ERR(dprc->clk_b))
+ return PTR_ERR(dprc->clk_b);
+ clk_prepare_enable(dprc->clk_b);
+
+ dprc->clk_rtram = devm_clk_get(dev, "rtram");
+ if (IS_ERR(dprc->clk_rtram))
+ return PTR_ERR(dprc->clk_rtram);
+ clk_prepare_enable(dprc->clk_rtram);
+
+ ret = of_property_read_u32(pdev->dev.of_node,
+ "fsl,sc-resource", &dprc->sc_resource);
+ if (ret) {
+ dev_err(dev, "cannot get SC resource %d\n", ret);
+ return ret;
+ }
+
+ switch (dprc->sc_resource) {
+ case IMX_SC_R_DC_0_BLIT1:
+ case IMX_SC_R_DC_1_BLIT1:
+ dprc->has_aux_prg = true;
+ fallthrough;
+ case IMX_SC_R_DC_0_BLIT0:
+ case IMX_SC_R_DC_1_BLIT0:
+ dprc->is_blit_chan = true;
+ fallthrough;
+ case IMX_SC_R_DC_0_FRAC0:
+ case IMX_SC_R_DC_1_FRAC0:
+ break;
+ case IMX_SC_R_DC_0_VIDEO0:
+ case IMX_SC_R_DC_0_VIDEO1:
+ case IMX_SC_R_DC_1_VIDEO0:
+ case IMX_SC_R_DC_1_VIDEO1:
+ case IMX_SC_R_DC_0_WARP:
+ case IMX_SC_R_DC_1_WARP:
+ dprc->has_aux_prg = true;
+ break;
+ default:
+ dev_err(dev, "wrong SC resource %u\n", dprc->sc_resource);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < 2; i++) {
+ if (i == 1 && !dprc->has_aux_prg)
+ break;
+
+ dprc->prgs[i] = prg_lookup_by_phandle(dev, "fsl,prgs", i);
+ if (!dprc->prgs[i])
+ return -EPROBE_DEFER;
+
+ if (i == 1)
+ prg_set_auxiliary(dprc->prgs[i]);
+
+ if (dprc->is_blit_chan)
+ prg_set_blit(dprc->prgs[i]);
+ }
+
+ dprc->dev = dev;
+ spin_lock_init(&dprc->spin_lock);
+ platform_set_drvdata(pdev, dprc);
+ mutex_lock(&dprc_list_mutex);
+ list_add(&dprc->list, &dprc_list);
+ mutex_unlock(&dprc_list_mutex);
+
+ dprc_reset(dprc);
+
+ return 0;
+}
+
+static int dprc_remove(struct platform_device *pdev)
+{
+ struct dprc *dprc = platform_get_drvdata(pdev);
+
+ mutex_lock(&dprc_list_mutex);
+ list_del(&dprc->list);
+ mutex_unlock(&dprc_list_mutex);
+
+ clk_disable_unprepare(dprc->clk_rtram);
+ clk_disable_unprepare(dprc->clk_b);
+ clk_disable_unprepare(dprc->clk_apb);
+
+ return 0;
+}
+
+struct platform_driver dprc_drv = {
+ .probe = dprc_probe,
+ .remove = dprc_remove,
+ .driver = {
+ .name = "imx8-dpr-channel",
+ .of_match_table = dprc_dt_ids,
+ },
+};
+module_platform_driver(dprc_drv);
+
+MODULE_DESCRIPTION("i.MX8 DPRC driver");
+MODULE_AUTHOR("NXP Semiconductor");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/imx/imx8_pc.c b/drivers/gpu/imx/imx8_pc.c
new file mode 100644
index 000000000000..f5386b9f6193
--- /dev/null
+++ b/drivers/gpu/imx/imx8_pc.c
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2018,2019 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <video/imx8-pc.h>
+
+#define REG0 0x0
+#define PIX_COMBINE_ENABLE BIT(0)
+#define DISP_PIX_COMBINE_BYPASS(n) BIT(1 + 21 * (n))
+#define DISP_HSYNC_POLARITY(n) BIT(2 + 11 * (n))
+#define DISP_HSYNC_POLARITY_POS(n) DISP_HSYNC_POLARITY(n)
+#define DISP_VSYNC_POLARITY(n) BIT(3 + 11 * (n))
+#define DISP_VSYNC_POLARITY_POS(n) DISP_VSYNC_POLARITY(n)
+#define DISP_DVALID_POLARITY(n) BIT(4 + 11 * (n))
+#define DISP_DVALID_POLARITY_POS(n) DISP_DVALID_POLARITY(n)
+#define VSYNC_MASK_ENABLE BIT(5)
+#define SKIP_MODE BIT(6)
+#define SKIP_NUMBER(n) (((n) & 0x3F) << 7)
+#define DISP_PIX_DATA_FORMAT_MASK(n) (0x7 << (16 + (n) * 3))
+#define DISP_PIX_DATA_FORMAT_SHIFT(n) (16 + (n) * 3)
+enum {
+ RGB = 0,
+ YUV444,
+ YUV422,
+ SPLIT_RGB,
+};
+
+#define REG1 0x10
+#define BUF_ACTIVE_DEPTH(n) ((n) & 0x7FF)
+
+#define REG2 0x20
+#define PC_SW_RESET_N BIT(0)
+#define DISP_SW_RESET_N(n) BIT(1 + (n))
+#define PC_FULL_RESET_N (PC_SW_RESET_N | \
+ DISP_SW_RESET_N(0) | \
+ DISP_SW_RESET_N(1))
+
+struct pc {
+ struct device *dev;
+ void __iomem *base;
+ struct list_head list;
+};
+
+static DEFINE_MUTEX(pc_list_mutex);
+static LIST_HEAD(pc_list);
+
+static inline u32 pc_read(struct pc *pc, unsigned int offset)
+{
+ return readl(pc->base + offset);
+}
+
+static inline void pc_write(struct pc *pc, unsigned int offset, u32 value)
+{
+ writel(value, pc->base + offset);
+}
+
+static void pc_reset(struct pc *pc)
+{
+ pc_write(pc, REG2, 0);
+ usleep_range(1000, 2000);
+ pc_write(pc, REG2, PC_FULL_RESET_N);
+}
+
+void pc_enable(struct pc *pc)
+{
+ u32 val;
+
+ if (WARN_ON(!pc))
+ return;
+
+ val = pc_read(pc, REG0);
+ val |= PIX_COMBINE_ENABLE;
+ pc_write(pc, REG0, val);
+
+ dev_dbg(pc->dev, "enable\n");
+}
+EXPORT_SYMBOL_GPL(pc_enable);
+
+void pc_disable(struct pc *pc)
+{
+ if (WARN_ON(!pc))
+ return;
+
+ pc_reset(pc);
+
+ dev_dbg(pc->dev, "disable\n");
+}
+EXPORT_SYMBOL_GPL(pc_disable);
+
+void pc_configure(struct pc *pc, unsigned int di, unsigned int frame_width,
+ u32 mode, u32 format)
+{
+ u32 val;
+
+ if (WARN_ON(!pc))
+ return;
+
+ if (WARN_ON(di != 0 && di != 1))
+ return;
+
+ dev_dbg(pc->dev, "configure mode-0x%08x frame_width-%u\n",
+ mode, frame_width);
+
+ val = pc_read(pc, REG0);
+ if (mode == PC_BYPASS) {
+ val |= DISP_PIX_COMBINE_BYPASS(di);
+ } else if (mode == PC_COMBINE) {
+ val &= ~DISP_PIX_COMBINE_BYPASS(di);
+ frame_width /= 4;
+ }
+
+ pc_write(pc, REG0, val);
+ pc_write(pc, REG1, BUF_ACTIVE_DEPTH(frame_width));
+}
+EXPORT_SYMBOL_GPL(pc_configure);
+
+struct pc *pc_lookup_by_phandle(struct device *dev, const char *name)
+{
+ struct device_node *pc_node = of_parse_phandle(dev->of_node,
+ name, 0);
+ struct pc *pc;
+
+ mutex_lock(&pc_list_mutex);
+ list_for_each_entry(pc, &pc_list, list) {
+ if (pc_node == pc->dev->of_node) {
+ mutex_unlock(&pc_list_mutex);
+ device_link_add(dev, pc->dev,
+ DL_FLAG_AUTOREMOVE_CONSUMER);
+ return pc;
+ }
+ }
+ mutex_unlock(&pc_list_mutex);
+
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(pc_lookup_by_phandle);
+
+static int pc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ struct pc *pc;
+ u32 val;
+
+ pc = devm_kzalloc(dev, sizeof(*pc), GFP_KERNEL);
+ if (!pc)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ pc->base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(pc->base))
+ return PTR_ERR(pc->base);
+
+ pc->dev = dev;
+ platform_set_drvdata(pdev, pc);
+ mutex_lock(&pc_list_mutex);
+ list_add(&pc->list, &pc_list);
+ mutex_unlock(&pc_list_mutex);
+
+ pc_reset(pc);
+
+ /*
+ * assume data enable is active high and HSYNC/VSYNC are active low
+ * also, bypass combine at startup
+ */
+ val = DISP_DVALID_POLARITY_POS(0) | DISP_DVALID_POLARITY_POS(1) |
+ DISP_PIX_COMBINE_BYPASS(0) | DISP_PIX_COMBINE_BYPASS(1) |
+ VSYNC_MASK_ENABLE;
+
+ pc_write(pc, REG0, val);
+
+ return 0;
+}
+
+static int pc_remove(struct platform_device *pdev)
+{
+ struct pc *pc = platform_get_drvdata(pdev);
+
+ mutex_lock(&pc_list_mutex);
+ list_del(&pc->list);
+ mutex_unlock(&pc_list_mutex);
+
+ return 0;
+}
+
+static const struct of_device_id pc_dt_ids[] = {
+ { .compatible = "fsl,imx8qm-pixel-combiner", },
+ { .compatible = "fsl,imx8qxp-pixel-combiner", },
+ { /* sentinel */ },
+};
+
+struct platform_driver pc_drv = {
+ .probe = pc_probe,
+ .remove = pc_remove,
+ .driver = {
+ .name = "imx8-pixel-combiner",
+ .of_match_table = pc_dt_ids,
+ },
+};
+module_platform_driver(pc_drv);
+
+MODULE_DESCRIPTION("i.MX8 Pixel Combiner driver");
+MODULE_AUTHOR("NXP Semiconductor");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/imx/imx8_prg.c b/drivers/gpu/imx/imx8_prg.c
new file mode 100644
index 000000000000..4dbcb1cb909a
--- /dev/null
+++ b/drivers/gpu/imx/imx8_prg.c
@@ -0,0 +1,452 @@
+/*
+ * Copyright 2017-2019 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <drm/drm_fourcc.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <video/imx8-prefetch.h>
+
+#define SET 0x4
+#define CLR 0x8
+#define TOG 0xc
+
+#define PRG_CTRL 0x00
+#define BYPASS BIT(0)
+#define SC_DATA_TYPE BIT(2)
+#define SC_DATA_TYPE_8BIT 0
+#define SC_DATA_TYPE_10BIT BIT(2)
+#define UV_EN BIT(3)
+#define HANDSHAKE_MODE BIT(4)
+#define HANDSHAKE_MODE_4LINES 0
+#define HANDSHAKE_MODE_8LINES BIT(4)
+#define SHADOW_LOAD_MODE BIT(5)
+#define DES_DATA_TYPE 0x30000
+enum {
+ DES_DATA_TYPE_32BPP = (0 << 16),
+ DES_DATA_TYPE_24BPP = (1 << 16),
+ DES_DATA_TYPE_16BPP = (2 << 16),
+ DES_DATA_TYPE_8BPP = (3 << 16),
+};
+#define SOFTRST BIT(30)
+#define SHADOW_EN BIT(31)
+
+#define PRG_STATUS 0x10
+#define BUFFER_VALID_B BIT(1)
+#define BUFFER_VALID_A BIT(0)
+
+#define PRG_REG_UPDATE 0x20
+#define REG_UPDATE BIT(0)
+
+#define PRG_STRIDE 0x30
+#define STRIDE(n) (((n) - 1) & 0xffff)
+
+#define PRG_HEIGHT 0x40
+#define HEIGHT(n) (((n) - 1) & 0xffff)
+
+#define PRG_BADDR 0x50
+
+#define PRG_OFFSET 0x60
+#define Y(n) (((n) & 0x7) << 16)
+#define X(n) ((n) & 0xffff)
+
+#define PRG_WIDTH 0x70
+#define WIDTH(n) (((n) - 1) & 0xffff)
+
+struct prg {
+ struct device *dev;
+ void __iomem *base;
+ struct list_head list;
+ struct clk *clk_apb;
+ struct clk *clk_rtram;
+ bool is_auxiliary;
+ bool is_blit;
+};
+
+static DEFINE_MUTEX(prg_list_mutex);
+static LIST_HEAD(prg_list);
+
+static inline u32 prg_read(struct prg *prg, unsigned int offset)
+{
+ return readl(prg->base + offset);
+}
+
+static inline void prg_write(struct prg *prg, u32 value, unsigned int offset)
+{
+ writel(value, prg->base + offset);
+}
+
+static void prg_reset(struct prg *prg)
+{
+ if (prg->is_blit)
+ usleep_range(10, 20);
+
+ prg_write(prg, SOFTRST, PRG_CTRL + SET);
+
+ if (prg->is_blit)
+ usleep_range(10, 20);
+ else
+ usleep_range(1000, 2000);
+
+ prg_write(prg, SOFTRST, PRG_CTRL + CLR);
+}
+
+void prg_enable(struct prg *prg)
+{
+ if (WARN_ON(!prg))
+ return;
+
+ prg_write(prg, BYPASS, PRG_CTRL + CLR);
+}
+EXPORT_SYMBOL_GPL(prg_enable);
+
+void prg_disable(struct prg *prg)
+{
+ if (WARN_ON(!prg))
+ return;
+
+ prg_write(prg, BYPASS, PRG_CTRL);
+}
+EXPORT_SYMBOL_GPL(prg_disable);
+
+void prg_configure(struct prg *prg, unsigned int width, unsigned int height,
+ unsigned int x_offset, unsigned int y_offset,
+ unsigned int stride, unsigned int bits_per_pixel,
+ unsigned long baddr, u32 format, u64 modifier,
+ bool start)
+{
+ unsigned int burst_size;
+ unsigned int mt_w = 0, mt_h = 0; /* w/h in a micro-tile */
+ unsigned long _baddr;
+ u32 val;
+
+ if (WARN_ON(!prg))
+ return;
+
+ if (start)
+ prg_reset(prg);
+
+ /* prg finer cropping into micro-tile block - top/left start point */
+ switch (modifier) {
+ case DRM_FORMAT_MOD_NONE:
+ break;
+ case DRM_FORMAT_MOD_AMPHION_TILED:
+ mt_w = 8;
+ mt_h = 8;
+ break;
+ case DRM_FORMAT_MOD_VIVANTE_TILED:
+ case DRM_FORMAT_MOD_VIVANTE_SUPER_TILED:
+ mt_w = (bits_per_pixel == 16) ? 8 : 4;
+ mt_h = 4;
+ break;
+ default:
+ dev_err(prg->dev, "unsupported modifier 0x%016llx\n", modifier);
+ return;
+ }
+
+ if (modifier) {
+ x_offset %= mt_w;
+ y_offset %= mt_h;
+
+ /* consider x offset to calculate stride */
+ _baddr = baddr + (x_offset * (bits_per_pixel / 8));
+ } else {
+ x_offset = 0;
+ y_offset = 0;
+ _baddr = baddr;
+ }
+
+ /*
+ * address TKT343664:
+ * fetch unit base address has to align to burst_size
+ */
+ burst_size = 1 << (ffs(_baddr) - 1);
+ burst_size = round_up(burst_size, 8);
+ burst_size = min(burst_size, 128U);
+
+ /*
+ * address TKT339017:
+ * fixup for burst size vs stride mismatch
+ */
+ if (modifier)
+ stride = round_up(stride + round_up(_baddr % 8, 8), burst_size);
+ else
+ stride = round_up(stride, burst_size);
+
+ /*
+ * address TKT342628(part 1):
+ * when prg stride is less or equals to burst size,
+ * the auxiliary prg height needs to be a half
+ */
+ if (prg->is_auxiliary && stride <= burst_size) {
+ height /= 2;
+ if (modifier)
+ y_offset /= 2;
+ }
+
+ prg_write(prg, STRIDE(stride), PRG_STRIDE);
+ prg_write(prg, WIDTH(width), PRG_WIDTH);
+ prg_write(prg, HEIGHT(height), PRG_HEIGHT);
+ prg_write(prg, X(x_offset) | Y(y_offset), PRG_OFFSET);
+ prg_write(prg, baddr, PRG_BADDR);
+
+ val = prg_read(prg, PRG_CTRL);
+ val &= ~SC_DATA_TYPE;
+ val |= SC_DATA_TYPE_8BIT;
+ val &= ~HANDSHAKE_MODE;
+ if (format == DRM_FORMAT_NV21 || format == DRM_FORMAT_NV12) {
+ val |= HANDSHAKE_MODE_8LINES;
+ /*
+ * address TKT342628(part 2):
+ * when prg stride is less or equals to burst size,
+ * we disable UV_EN bit for the auxiliary prg
+ */
+ if (prg->is_auxiliary && stride > burst_size)
+ val |= UV_EN;
+ else
+ val &= ~UV_EN;
+ } else {
+ val |= HANDSHAKE_MODE_4LINES;
+ val &= ~UV_EN;
+ }
+ val |= SHADOW_LOAD_MODE;
+ val &= ~DES_DATA_TYPE;
+ switch (bits_per_pixel) {
+ case 32:
+ val |= DES_DATA_TYPE_32BPP;
+ break;
+ case 24:
+ val |= DES_DATA_TYPE_24BPP;
+ break;
+ case 16:
+ val |= DES_DATA_TYPE_16BPP;
+ break;
+ case 8:
+ val |= DES_DATA_TYPE_8BPP;
+ break;
+ }
+ if (start)
+ /* no shadow for the first frame */
+ val &= ~SHADOW_EN;
+ else
+ val |= SHADOW_EN;
+ prg_write(prg, val, PRG_CTRL);
+
+ dev_dbg(prg->dev, "bits per pixel %u\n", bits_per_pixel);
+}
+EXPORT_SYMBOL_GPL(prg_configure);
+
+void prg_reg_update(struct prg *prg)
+{
+ if (WARN_ON(!prg))
+ return;
+
+ prg_write(prg, REG_UPDATE, PRG_REG_UPDATE);
+}
+EXPORT_SYMBOL_GPL(prg_reg_update);
+
+void prg_shadow_enable(struct prg *prg)
+{
+ if (WARN_ON(!prg))
+ return;
+
+ prg_write(prg, SHADOW_EN, PRG_CTRL + SET);
+}
+EXPORT_SYMBOL_GPL(prg_shadow_enable);
+
+bool prg_stride_supported(struct prg *prg, unsigned int stride)
+{
+ return stride < 0x10000;
+}
+EXPORT_SYMBOL_GPL(prg_stride_supported);
+
+bool prg_stride_double_check(struct prg *prg,
+ unsigned int width, unsigned int x_offset,
+ unsigned int bits_per_pixel, u64 modifier,
+ unsigned int stride, dma_addr_t baddr)
+{
+ unsigned int burst_size;
+ unsigned int mt_w = 0; /* w in a micro-tile */
+ dma_addr_t _baddr;
+
+ if (WARN_ON(!prg))
+ return false;
+
+ /* prg finer cropping into micro-tile block - top/left start point */
+ switch (modifier) {
+ case DRM_FORMAT_MOD_NONE:
+ break;
+ case DRM_FORMAT_MOD_AMPHION_TILED:
+ mt_w = 8;
+ break;
+ case DRM_FORMAT_MOD_VIVANTE_TILED:
+ case DRM_FORMAT_MOD_VIVANTE_SUPER_TILED:
+ mt_w = (bits_per_pixel == 16) ? 8 : 4;
+ break;
+ default:
+ dev_err(prg->dev, "unsupported modifier 0x%016llx\n", modifier);
+ return false;
+ }
+
+ if (modifier) {
+ x_offset %= mt_w;
+
+ /* consider x offset to calculate stride */
+ _baddr = baddr + (x_offset * (bits_per_pixel / 8));
+ } else {
+ _baddr = baddr;
+ }
+
+ /*
+ * address TKT343664:
+ * fetch unit base address has to align to burst size
+ */
+ burst_size = 1 << (ffs(_baddr) - 1);
+ burst_size = round_up(burst_size, 8);
+ burst_size = min(burst_size, 128U);
+
+ /*
+ * address TKT339017:
+ * fixup for burst size vs stride mismatch
+ */
+ if (modifier)
+ stride = round_up(stride + round_up(_baddr % 8, 8), burst_size);
+ else
+ stride = round_up(stride, burst_size);
+
+ return stride < 0x10000;
+}
+EXPORT_SYMBOL_GPL(prg_stride_double_check);
+
+void prg_set_auxiliary(struct prg *prg)
+{
+ if (WARN_ON(!prg))
+ return;
+
+ prg->is_auxiliary = true;
+}
+EXPORT_SYMBOL_GPL(prg_set_auxiliary);
+
+void prg_set_primary(struct prg *prg)
+{
+ if (WARN_ON(!prg))
+ return;
+
+ prg->is_auxiliary = false;
+}
+EXPORT_SYMBOL_GPL(prg_set_primary);
+
+void prg_set_blit(struct prg *prg)
+{
+ if (WARN_ON(!prg))
+ return;
+
+ prg->is_blit = true;
+}
+EXPORT_SYMBOL_GPL(prg_set_blit);
+
+struct prg *
+prg_lookup_by_phandle(struct device *dev, const char *name, int index)
+{
+ struct device_node *prg_node = of_parse_phandle(dev->of_node,
+ name, index);
+ struct prg *prg;
+
+ mutex_lock(&prg_list_mutex);
+ list_for_each_entry(prg, &prg_list, list) {
+ if (prg_node == prg->dev->of_node) {
+ mutex_unlock(&prg_list_mutex);
+ device_link_add(dev, prg->dev,
+ DL_FLAG_AUTOREMOVE_CONSUMER);
+ return prg;
+ }
+ }
+ mutex_unlock(&prg_list_mutex);
+
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(prg_lookup_by_phandle);
+
+static const struct of_device_id prg_dt_ids[] = {
+ { .compatible = "fsl,imx8qm-prg", },
+ { .compatible = "fsl,imx8qxp-prg", },
+ { /* sentinel */ },
+};
+
+static int prg_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ struct prg *prg;
+
+ prg = devm_kzalloc(dev, sizeof(*prg), GFP_KERNEL);
+ if (!prg)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ prg->base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(prg->base))
+ return PTR_ERR(prg->base);
+
+ prg->clk_apb = devm_clk_get(dev, "apb");
+ if (IS_ERR(prg->clk_apb))
+ return PTR_ERR(prg->clk_apb);
+ clk_prepare_enable(prg->clk_apb);
+
+ prg->clk_rtram = devm_clk_get(dev, "rtram");
+ if (IS_ERR(prg->clk_rtram))
+ return PTR_ERR(prg->clk_rtram);
+ clk_prepare_enable(prg->clk_rtram);
+
+ prg->dev = dev;
+ platform_set_drvdata(pdev, prg);
+ mutex_lock(&prg_list_mutex);
+ list_add(&prg->list, &prg_list);
+ mutex_unlock(&prg_list_mutex);
+
+ prg_reset(prg);
+
+ return 0;
+}
+
+static int prg_remove(struct platform_device *pdev)
+{
+ struct prg *prg = platform_get_drvdata(pdev);
+
+ mutex_lock(&prg_list_mutex);
+ list_del(&prg->list);
+ mutex_unlock(&prg_list_mutex);
+
+ clk_disable_unprepare(prg->clk_rtram);
+ clk_disable_unprepare(prg->clk_apb);
+
+ return 0;
+}
+
+struct platform_driver prg_drv = {
+ .probe = prg_probe,
+ .remove = prg_remove,
+ .driver = {
+ .name = "imx8-prg",
+ .of_match_table = prg_dt_ids,
+ },
+};
+module_platform_driver(prg_drv);
+
+MODULE_DESCRIPTION("i.MX8 PRG driver");
+MODULE_AUTHOR("NXP Semiconductor");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/ipu-v3/Kconfig b/drivers/gpu/imx/ipu-v3/Kconfig
index 061fb990c120..061fb990c120 100644
--- a/drivers/gpu/ipu-v3/Kconfig
+++ b/drivers/gpu/imx/ipu-v3/Kconfig
diff --git a/drivers/gpu/ipu-v3/Makefile b/drivers/gpu/imx/ipu-v3/Makefile
index 5fe5ef20701a..5fe5ef20701a 100644
--- a/drivers/gpu/ipu-v3/Makefile
+++ b/drivers/gpu/imx/ipu-v3/Makefile
diff --git a/drivers/gpu/ipu-v3/ipu-common.c b/drivers/gpu/imx/ipu-v3/ipu-common.c
index c35eac1116f5..c35eac1116f5 100644
--- a/drivers/gpu/ipu-v3/ipu-common.c
+++ b/drivers/gpu/imx/ipu-v3/ipu-common.c
diff --git a/drivers/gpu/ipu-v3/ipu-cpmem.c b/drivers/gpu/imx/ipu-v3/ipu-cpmem.c
index 82b244cb313e..82b244cb313e 100644
--- a/drivers/gpu/ipu-v3/ipu-cpmem.c
+++ b/drivers/gpu/imx/ipu-v3/ipu-cpmem.c
diff --git a/drivers/gpu/ipu-v3/ipu-csi.c b/drivers/gpu/imx/ipu-v3/ipu-csi.c
index 8ae301eef643..8ae301eef643 100644
--- a/drivers/gpu/ipu-v3/ipu-csi.c
+++ b/drivers/gpu/imx/ipu-v3/ipu-csi.c
diff --git a/drivers/gpu/ipu-v3/ipu-dc.c b/drivers/gpu/imx/ipu-v3/ipu-dc.c
index ca96b235491a..ca96b235491a 100644
--- a/drivers/gpu/ipu-v3/ipu-dc.c
+++ b/drivers/gpu/imx/ipu-v3/ipu-dc.c
diff --git a/drivers/gpu/ipu-v3/ipu-di.c b/drivers/gpu/imx/ipu-v3/ipu-di.c
index 0a34e0ab4fe6..0a34e0ab4fe6 100644
--- a/drivers/gpu/ipu-v3/ipu-di.c
+++ b/drivers/gpu/imx/ipu-v3/ipu-di.c
diff --git a/drivers/gpu/ipu-v3/ipu-dmfc.c b/drivers/gpu/imx/ipu-v3/ipu-dmfc.c
index ae682084a10a..ae682084a10a 100644
--- a/drivers/gpu/ipu-v3/ipu-dmfc.c
+++ b/drivers/gpu/imx/ipu-v3/ipu-dmfc.c
diff --git a/drivers/gpu/ipu-v3/ipu-dp.c b/drivers/gpu/imx/ipu-v3/ipu-dp.c
index 6a558205db96..6a558205db96 100644
--- a/drivers/gpu/ipu-v3/ipu-dp.c
+++ b/drivers/gpu/imx/ipu-v3/ipu-dp.c
diff --git a/drivers/gpu/ipu-v3/ipu-ic-csc.c b/drivers/gpu/imx/ipu-v3/ipu-ic-csc.c
index d1ca7ba94a12..d1ca7ba94a12 100644
--- a/drivers/gpu/ipu-v3/ipu-ic-csc.c
+++ b/drivers/gpu/imx/ipu-v3/ipu-ic-csc.c
diff --git a/drivers/gpu/ipu-v3/ipu-ic.c b/drivers/gpu/imx/ipu-v3/ipu-ic.c
index 846461bac70d..846461bac70d 100644
--- a/drivers/gpu/ipu-v3/ipu-ic.c
+++ b/drivers/gpu/imx/ipu-v3/ipu-ic.c
diff --git a/drivers/gpu/ipu-v3/ipu-image-convert.c b/drivers/gpu/imx/ipu-v3/ipu-image-convert.c
index aa1d4b6d278f..aa1d4b6d278f 100644
--- a/drivers/gpu/ipu-v3/ipu-image-convert.c
+++ b/drivers/gpu/imx/ipu-v3/ipu-image-convert.c
diff --git a/drivers/gpu/ipu-v3/ipu-pre.c b/drivers/gpu/imx/ipu-v3/ipu-pre.c
index ad82c9e0252f..ad82c9e0252f 100644
--- a/drivers/gpu/ipu-v3/ipu-pre.c
+++ b/drivers/gpu/imx/ipu-v3/ipu-pre.c
diff --git a/drivers/gpu/ipu-v3/ipu-prg.c b/drivers/gpu/imx/ipu-v3/ipu-prg.c
index 196797c1b4b3..196797c1b4b3 100644
--- a/drivers/gpu/ipu-v3/ipu-prg.c
+++ b/drivers/gpu/imx/ipu-v3/ipu-prg.c
diff --git a/drivers/gpu/ipu-v3/ipu-prv.h b/drivers/gpu/imx/ipu-v3/ipu-prv.h
index 291ac1bab66d..291ac1bab66d 100644
--- a/drivers/gpu/ipu-v3/ipu-prv.h
+++ b/drivers/gpu/imx/ipu-v3/ipu-prv.h
diff --git a/drivers/gpu/ipu-v3/ipu-smfc.c b/drivers/gpu/imx/ipu-v3/ipu-smfc.c
index 46ffc0a5906d..46ffc0a5906d 100644
--- a/drivers/gpu/ipu-v3/ipu-smfc.c
+++ b/drivers/gpu/imx/ipu-v3/ipu-smfc.c
diff --git a/drivers/gpu/ipu-v3/ipu-vdi.c b/drivers/gpu/imx/ipu-v3/ipu-vdi.c
index a593b232b6d3..a593b232b6d3 100644
--- a/drivers/gpu/ipu-v3/ipu-vdi.c
+++ b/drivers/gpu/imx/ipu-v3/ipu-vdi.c
diff --git a/drivers/gpu/imx/lcdif/Kconfig b/drivers/gpu/imx/lcdif/Kconfig
new file mode 100644
index 000000000000..dfaea1207940
--- /dev/null
+++ b/drivers/gpu/imx/lcdif/Kconfig
@@ -0,0 +1,9 @@
+config IMX_LCDIF_CORE
+ tristate "i.MX LCDIF core support"
+ depends on ARCH_MXC
+ depends on DRM && OF
+ select RESET_CONTROLLER
+ help
+ Choose this if you have a NXP i.MX8MM platform and want to use the
+ LCDIF display controller. This option only enables LCDIF base support.
+
diff --git a/drivers/gpu/imx/lcdif/Makefile b/drivers/gpu/imx/lcdif/Makefile
new file mode 100644
index 000000000000..8c7ce5ccce95
--- /dev/null
+++ b/drivers/gpu/imx/lcdif/Makefile
@@ -0,0 +1,3 @@
+obj-$(CONFIG_IMX_LCDIF_CORE) += imx-lcdif-core.o
+
+imx-lcdif-core-objs := lcdif-common.o
diff --git a/drivers/gpu/imx/lcdif/lcdif-common.c b/drivers/gpu/imx/lcdif/lcdif-common.c
new file mode 100644
index 000000000000..09b07758b9ea
--- /dev/null
+++ b/drivers/gpu/imx/lcdif/lcdif-common.c
@@ -0,0 +1,854 @@
+/*
+ * Copyright 2018,2021-2022 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/busfreq-imx.h>
+#include <linux/clk.h>
+#include <linux/iopoll.h>
+#include <linux/media-bus-format.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/reset.h>
+#include <linux/types.h>
+#include <drm/drm_fourcc.h>
+#include <video/imx-lcdif.h>
+#include <video/videomode.h>
+
+#include "lcdif-regs.h"
+
+#define DRIVER_NAME "imx-lcdif"
+
+struct lcdif_soc {
+ struct device *dev;
+
+ int irq;
+ void __iomem *base;
+ struct reset_control *soft_resetn;
+ struct reset_control *clk_enable;
+ atomic_t rpm_suspended;
+
+ struct clk *clk_pix;
+ struct clk *clk_disp_axi;
+ struct clk *clk_disp_apb;
+};
+
+struct lcdif_soc_pdata {
+ bool hsync_invert;
+ bool vsync_invert;
+ bool de_invert;
+};
+
+struct lcdif_platform_reg {
+ struct lcdif_client_platformdata pdata;
+ char *name;
+};
+
+struct lcdif_platform_reg client_reg[] = {
+ {
+ .pdata = { },
+ .name = "imx-lcdif-crtc",
+ },
+};
+
+struct lcdif_soc_pdata imx8mm_pdata = {
+ .hsync_invert = true,
+ .vsync_invert = true,
+ .de_invert = true,
+};
+
+static const struct of_device_id imx_lcdif_dt_ids[] = {
+ { .compatible = "fsl,imx8mm-lcdif", .data = &imx8mm_pdata, },
+ { .compatible = "fsl,imx8mn-lcdif", .data = &imx8mm_pdata, },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_lcdif_dt_ids);
+
+#ifdef CONFIG_PM
+static int imx_lcdif_runtime_suspend(struct device *dev);
+static int imx_lcdif_runtime_resume(struct device *dev);
+#else
+static int imx_lcdif_runtime_suspend(struct device *dev)
+{
+ return 0;
+}
+static int imx_lcdif_runtime_resume(struct device *dev)
+{
+ return 0;
+}
+#endif
+
+static int lcdif_rstc_reset(struct reset_control *rstc, bool assert)
+{
+ int ret;
+
+ if (!rstc)
+ return 0;
+
+ ret = assert ? reset_control_assert(rstc) :
+ reset_control_deassert(rstc);
+
+ return ret;
+}
+
+static int lcdif_enable_clocks(struct lcdif_soc *lcdif)
+{
+ int ret;
+
+ if (lcdif->clk_disp_axi) {
+ ret = clk_prepare_enable(lcdif->clk_disp_axi);
+ if (ret)
+ return ret;
+ }
+
+ if (lcdif->clk_disp_apb) {
+ ret = clk_prepare_enable(lcdif->clk_disp_apb);
+ if (ret)
+ goto disable_disp_axi;
+ }
+
+ ret = clk_prepare_enable(lcdif->clk_pix);
+ if (ret)
+ goto disable_disp_apb;
+
+ return 0;
+
+disable_disp_apb:
+ if (lcdif->clk_disp_apb)
+ clk_disable_unprepare(lcdif->clk_disp_apb);
+disable_disp_axi:
+ if (lcdif->clk_disp_axi)
+ clk_disable_unprepare(lcdif->clk_disp_axi);
+
+ return ret;
+}
+
+static void lcdif_disable_clocks(struct lcdif_soc *lcdif)
+{
+ clk_disable_unprepare(lcdif->clk_pix);
+
+ if (lcdif->clk_disp_axi)
+ clk_disable_unprepare(lcdif->clk_disp_axi);
+
+ if (lcdif->clk_disp_apb)
+ clk_disable_unprepare(lcdif->clk_disp_apb);
+}
+
+int lcdif_vblank_irq_get(struct lcdif_soc *lcdif)
+{
+ return lcdif->irq;
+}
+EXPORT_SYMBOL(lcdif_vblank_irq_get);
+
+void lcdif_dump_registers(struct lcdif_soc *lcdif)
+{
+ pr_info("%#x : %#x\n", LCDIF_CTRL,
+ readl(lcdif->base + LCDIF_CTRL));
+ pr_info("%#x : %#x\n", LCDIF_CTRL1,
+ readl(lcdif->base + LCDIF_CTRL1));
+ pr_info("%#x : %#x\n", LCDIF_CTRL2,
+ readl(lcdif->base + LCDIF_CTRL2));
+ pr_info("%#x : %#x\n", LCDIF_TRANSFER_COUNT,
+ readl(lcdif->base + LCDIF_TRANSFER_COUNT));
+ pr_info("%#x : %#x\n", LCDIF_CUR_BUF,
+ readl(lcdif->base + LCDIF_CUR_BUF));
+ pr_info("%#x : %#x\n", LCDIF_NEXT_BUF,
+ readl(lcdif->base + LCDIF_NEXT_BUF));
+ pr_info("%#x : %#x\n", LCDIF_VDCTRL0,
+ readl(lcdif->base + LCDIF_VDCTRL0));
+ pr_info("%#x : %#x\n", LCDIF_VDCTRL1,
+ readl(lcdif->base + LCDIF_VDCTRL1));
+ pr_info("%#x : %#x\n", LCDIF_VDCTRL2,
+ readl(lcdif->base + LCDIF_VDCTRL2));
+ pr_info("%#x : %#x\n", LCDIF_VDCTRL3,
+ readl(lcdif->base + LCDIF_VDCTRL3));
+ pr_info("%#x : %#x\n", LCDIF_VDCTRL4,
+ readl(lcdif->base + LCDIF_VDCTRL4));
+}
+EXPORT_SYMBOL(lcdif_dump_registers);
+
+void lcdif_vblank_irq_enable(struct lcdif_soc *lcdif)
+{
+ writel(CTRL1_CUR_FRAME_DONE_IRQ, lcdif->base + LCDIF_CTRL1 + REG_CLR);
+ writel(CTRL1_CUR_FRAME_DONE_IRQ_EN, lcdif->base + LCDIF_CTRL1 + REG_SET);
+}
+EXPORT_SYMBOL(lcdif_vblank_irq_enable);
+
+void lcdif_vblank_irq_disable(struct lcdif_soc *lcdif)
+{
+ writel(CTRL1_CUR_FRAME_DONE_IRQ_EN, lcdif->base + LCDIF_CTRL1 + REG_CLR);
+ writel(CTRL1_CUR_FRAME_DONE_IRQ, lcdif->base + LCDIF_CTRL1 + REG_CLR);
+}
+EXPORT_SYMBOL(lcdif_vblank_irq_disable);
+
+void lcdif_vblank_irq_clear(struct lcdif_soc *lcdif)
+{
+ writel(CTRL1_CUR_FRAME_DONE_IRQ, lcdif->base + LCDIF_CTRL1 + REG_CLR);
+}
+EXPORT_SYMBOL(lcdif_vblank_irq_clear);
+
+static uint32_t lcdif_get_bpp_from_fmt(uint32_t format)
+{
+ /* TODO: only support RGB for now */
+
+ switch (format) {
+ case DRM_FORMAT_RGB565:
+ case DRM_FORMAT_BGR565:
+ case DRM_FORMAT_ARGB1555:
+ case DRM_FORMAT_XRGB1555:
+ case DRM_FORMAT_ABGR1555:
+ case DRM_FORMAT_XBGR1555:
+ return 16;
+ case DRM_FORMAT_ARGB8888:
+ case DRM_FORMAT_XRGB8888:
+ case DRM_FORMAT_ABGR8888:
+ case DRM_FORMAT_XBGR8888:
+ case DRM_FORMAT_RGBA8888:
+ case DRM_FORMAT_RGBX8888:
+ return 32;
+ default:
+ /* unsupported format */
+ return 0;
+ }
+}
+
+/*
+ * Get the bus format supported by LCDIF
+ * according to drm fourcc format
+ */
+int lcdif_get_bus_fmt_from_pix_fmt(struct lcdif_soc *lcdif,
+ uint32_t format)
+{
+ uint32_t bpp;
+
+ bpp = lcdif_get_bpp_from_fmt(format);
+ if (!bpp)
+ return -EINVAL;
+
+ switch (bpp) {
+ case 16:
+ return MEDIA_BUS_FMT_RGB565_1X16;
+ case 18:
+ return MEDIA_BUS_FMT_RGB666_1X18;
+ case 24:
+ case 32:
+ return MEDIA_BUS_FMT_RGB888_1X24;
+ default:
+ return -EINVAL;
+ }
+}
+EXPORT_SYMBOL(lcdif_get_bus_fmt_from_pix_fmt);
+
+int lcdif_set_pix_fmt(struct lcdif_soc *lcdif, u32 format)
+{
+ u32 ctrl = 0, ctrl1 = 0;
+
+ /* TODO: lcdif should be disabled to set pixel format */
+
+ ctrl = readl(lcdif->base + LCDIF_CTRL);
+ ctrl1 = readl(lcdif->base + LCDIF_CTRL1);
+
+ /* clear pixel format related bits */
+ ctrl &= ~(CTRL_SHIFT_NUM(0x3f) | CTRL_INPUT_SWIZZLE(0x3) |
+ CTRL_CSC_SWIZZLE(0x3) | CTRL_SET_WORD_LENGTH(0x3));
+
+ ctrl1 &= ~CTRL1_SET_BYTE_PACKAGING(0xf);
+
+ /* default is 'RGB' order */
+ writel(CTRL2_ODD_LINE_PATTERN(0x7) |
+ CTRL2_EVEN_LINE_PATTERN(0x7),
+ lcdif->base + LCDIF_CTRL2 + REG_CLR);
+
+ switch (format) {
+ /* bpp 16 */
+ case DRM_FORMAT_RGB565:
+ case DRM_FORMAT_BGR565:
+ case DRM_FORMAT_ARGB1555:
+ case DRM_FORMAT_XRGB1555:
+ case DRM_FORMAT_ABGR1555:
+ case DRM_FORMAT_XBGR1555:
+ /* Data format */
+ ctrl = (format == DRM_FORMAT_RGB565 ||
+ format == DRM_FORMAT_BGR565) ?
+ (ctrl & ~CTRL_DF16) : (ctrl | CTRL_DF16);
+
+ ctrl |= CTRL_SET_WORD_LENGTH(0x0);
+
+ /* Byte packing */
+ ctrl1 |= CTRL1_SET_BYTE_PACKAGING(0xf);
+
+ /* 'BGR' order */
+ if (format == DRM_FORMAT_BGR565 ||
+ format == DRM_FORMAT_ABGR1555 ||
+ format == DRM_FORMAT_XBGR1555)
+ writel(CTRL2_ODD_LINE_PATTERN(0x5) |
+ CTRL2_EVEN_LINE_PATTERN(0x5),
+ lcdif->base + LCDIF_CTRL2 + REG_SET);
+ break;
+ /* bpp 32 */
+ case DRM_FORMAT_ARGB8888:
+ case DRM_FORMAT_XRGB8888:
+ case DRM_FORMAT_ABGR8888:
+ case DRM_FORMAT_XBGR8888:
+ case DRM_FORMAT_RGBA8888:
+ case DRM_FORMAT_RGBX8888:
+ /*Data format */
+ ctrl &= ~CTRL_DF24;
+ ctrl |= CTRL_SET_WORD_LENGTH(3);
+
+ if (format == DRM_FORMAT_RGBA8888 ||
+ format == DRM_FORMAT_RGBX8888)
+ ctrl |= CTRL_SHIFT_DIR(1) | CTRL_SHIFT_NUM(8);
+
+ /* Byte packing */
+ ctrl1 |= CTRL1_SET_BYTE_PACKAGING(0x7);
+
+ /* 'BGR' order */
+ if (format == DRM_FORMAT_ABGR8888 ||
+ format == DRM_FORMAT_XBGR8888)
+ writel(CTRL2_ODD_LINE_PATTERN(0x5) |
+ CTRL2_EVEN_LINE_PATTERN(0x5),
+ lcdif->base + LCDIF_CTRL2 + REG_SET);
+ break;
+ default:
+ dev_err(lcdif->dev, "unsupported pixel format: %p4cc\n",
+ &format);
+ return -EINVAL;
+ }
+
+ writel(ctrl, lcdif->base + LCDIF_CTRL);
+ writel(ctrl1, lcdif->base + LCDIF_CTRL1);
+
+ return 0;
+}
+EXPORT_SYMBOL(lcdif_set_pix_fmt);
+
+void lcdif_set_bus_fmt(struct lcdif_soc *lcdif, u32 bus_format)
+{
+ u32 bus_width;
+
+ switch (bus_format) {
+ case MEDIA_BUS_FMT_RGB565_1X16:
+ bus_width = CTRL_SET_BUS_WIDTH(STMLCDIF_16BIT);
+ break;
+ case MEDIA_BUS_FMT_RGB666_1X18:
+ bus_width = CTRL_SET_BUS_WIDTH(STMLCDIF_18BIT);
+ break;
+ case MEDIA_BUS_FMT_RGB888_1X24:
+ bus_width = CTRL_SET_BUS_WIDTH(STMLCDIF_24BIT);
+ break;
+ default:
+ dev_err(lcdif->dev, "unknown bus format: %#x\n", bus_format);
+ return;
+ }
+
+ writel(CTRL_SET_BUS_WIDTH(0x3), lcdif->base + LCDIF_CTRL + REG_CLR);
+ writel(bus_width, lcdif->base + LCDIF_CTRL + REG_SET);
+}
+EXPORT_SYMBOL(lcdif_set_bus_fmt);
+
+void lcdif_set_fb_addr(struct lcdif_soc *lcdif, int id, u32 addr, bool use_i80)
+{
+ switch (id) {
+ case 0:
+ /* primary plane */
+ if (use_i80)
+ writel(addr, lcdif->base + LCDIF_CUR_BUF);
+ else
+ writel(addr, lcdif->base + LCDIF_NEXT_BUF);
+ break;
+ default:
+ /* TODO: add overlay support */
+ return;
+ }
+}
+EXPORT_SYMBOL(lcdif_set_fb_addr);
+
+void lcdif_set_fb_hcrop(struct lcdif_soc *lcdif, u32 src_w,
+ u32 fb_w, bool crop)
+{
+ u32 mask_cnt;
+ u32 vdctrl3, vdctrl4, transfer_count;
+ u32 pigeon_12_0, pigeon_12_1, pigeon_12_2;
+
+ if (!crop) {
+ writel(0x0, lcdif->base + HW_EPDC_PIGEON_12_0);
+ writel(0x0, lcdif->base + HW_EPDC_PIGEON_12_1);
+
+ return;
+ }
+
+ /*
+ * transfer_count's hcount and vdctrl4's H_VALID_DATA_CNT
+ * should use fb width instead of hactive when requires cropping.
+ */
+ transfer_count = readl(lcdif->base + LCDIF_TRANSFER_COUNT);
+ transfer_count &= ~TRANSFER_COUNT_SET_HCOUNT(0xffff);
+ transfer_count |= TRANSFER_COUNT_SET_HCOUNT(fb_w);
+ writel(transfer_count, lcdif->base + LCDIF_TRANSFER_COUNT);
+
+ vdctrl4 = readl(lcdif->base + LCDIF_VDCTRL4);
+ vdctrl4 &= ~SET_DOTCLK_H_VALID_DATA_CNT(0x3ffff);
+ vdctrl4 |= SET_DOTCLK_H_VALID_DATA_CNT(fb_w);
+ writel(vdctrl4, lcdif->base + LCDIF_VDCTRL4);
+
+ /* configure related pigeon registers */
+ vdctrl3 = readl(lcdif->base + LCDIF_VDCTRL3);
+ mask_cnt = GET_HOR_WAIT_CNT(vdctrl3) - 5;
+
+ pigeon_12_0 = PIGEON_12_0_SET_STATE_MASK(0x24) |
+ PIGEON_12_0_SET_MASK_CNT(mask_cnt) |
+ PIGEON_12_0_SET_MASK_CNT_SEL(0x6) |
+ PIGEON_12_0_POL_ACTIVE_LOW |
+ PIGEON_12_0_EN;
+ writel(pigeon_12_0, lcdif->base + HW_EPDC_PIGEON_12_0);
+
+ pigeon_12_1 = PIGEON_12_1_SET_CLR_CNT(src_w) |
+ PIGEON_12_1_SET_SET_CNT(0x0);
+ writel(pigeon_12_1, lcdif->base + HW_EPDC_PIGEON_12_1);
+
+ pigeon_12_2 = 0x0;
+ writel(pigeon_12_2, lcdif->base + HW_EPDC_PIGEON_12_2);
+}
+EXPORT_SYMBOL(lcdif_set_fb_hcrop);
+
+
+void lcdif_set_mode(struct lcdif_soc *lcdif, struct videomode *vmode,
+ bool use_i80)
+{
+ const struct of_device_id *of_id =
+ of_match_device(imx_lcdif_dt_ids, lcdif->dev);
+ const struct lcdif_soc_pdata *soc_pdata;
+ u32 vdctrl0, vdctrl1, vdctrl2, vdctrl3, vdctrl4, htotal;
+
+ if (unlikely(!of_id))
+ return;
+ soc_pdata = of_id->data;
+
+ /* Clear the FIFO */
+ writel(CTRL1_FIFO_CLEAR, lcdif->base + LCDIF_CTRL1 + REG_SET);
+ writel(CTRL1_FIFO_CLEAR, lcdif->base + LCDIF_CTRL1 + REG_CLR);
+
+ /* set pixel clock rate */
+ clk_disable_unprepare(lcdif->clk_pix);
+ clk_set_rate(lcdif->clk_pix, vmode->pixelclock);
+ clk_prepare_enable(lcdif->clk_pix);
+
+ /* config display timings */
+ writel(TRANSFER_COUNT_SET_VCOUNT(vmode->vactive) |
+ TRANSFER_COUNT_SET_HCOUNT(vmode->hactive),
+ lcdif->base + LCDIF_TRANSFER_COUNT);
+
+ if (use_i80) {
+ /* use MPU 8080 mode */
+ writel(CTRL1_MODE86, lcdif->base + LCDIF_CTRL1 + REG_CLR);
+ return;
+ }
+
+ vdctrl0 = VDCTRL0_ENABLE_PRESENT |
+ VDCTRL0_VSYNC_PERIOD_UNIT |
+ VDCTRL0_VSYNC_PULSE_WIDTH_UNIT |
+ VDCTRL0_SET_VSYNC_PULSE_WIDTH(vmode->vsync_len);
+
+ /* Polarities */
+ if (soc_pdata) {
+ if ((soc_pdata->hsync_invert &&
+ vmode->flags & DISPLAY_FLAGS_HSYNC_LOW) ||
+ (!soc_pdata->hsync_invert &&
+ vmode->flags & DISPLAY_FLAGS_HSYNC_HIGH))
+ vdctrl0 |= VDCTRL0_HSYNC_ACT_HIGH;
+
+ if ((soc_pdata->vsync_invert &&
+ vmode->flags & DISPLAY_FLAGS_VSYNC_LOW) ||
+ (!soc_pdata->vsync_invert &&
+ vmode->flags & DISPLAY_FLAGS_VSYNC_HIGH))
+ vdctrl0 |= VDCTRL0_VSYNC_ACT_HIGH;
+
+ if ((soc_pdata->de_invert &&
+ vmode->flags & DISPLAY_FLAGS_DE_LOW) ||
+ (!soc_pdata->de_invert &&
+ vmode->flags & DISPLAY_FLAGS_DE_HIGH))
+ vdctrl0 |= VDCTRL0_ENABLE_ACT_HIGH;
+ } else {
+ if (vmode->flags & DISPLAY_FLAGS_HSYNC_HIGH)
+ vdctrl0 |= VDCTRL0_HSYNC_ACT_HIGH;
+ if (vmode->flags & DISPLAY_FLAGS_VSYNC_HIGH)
+ vdctrl0 |= VDCTRL0_VSYNC_ACT_HIGH;
+ if (vmode->flags & DISPLAY_FLAGS_DE_HIGH)
+ vdctrl0 |= VDCTRL0_ENABLE_ACT_HIGH;
+ }
+
+ if (vmode->flags & DISPLAY_FLAGS_PIXDATA_POSEDGE)
+ vdctrl0 |= VDCTRL0_DOTCLK_ACT_FALLING;
+
+ writel(vdctrl0, lcdif->base + LCDIF_VDCTRL0);
+
+ vdctrl1 = vmode->vactive + vmode->vsync_len +
+ vmode->vfront_porch + vmode->vback_porch;
+ writel(vdctrl1, lcdif->base + LCDIF_VDCTRL1);
+
+ htotal = vmode->hactive + vmode->hsync_len +
+ vmode->hfront_porch + vmode->hback_porch;
+ vdctrl2 = VDCTRL2_SET_HSYNC_PULSE_WIDTH(vmode->hsync_len) |
+ VDCTRL2_SET_HSYNC_PERIOD(htotal);
+ writel(vdctrl2, lcdif->base + LCDIF_VDCTRL2);
+
+ vdctrl3 = SET_HOR_WAIT_CNT(vmode->hsync_len + vmode->hback_porch) |
+ SET_VERT_WAIT_CNT(vmode->vsync_len + vmode->vback_porch);
+ writel(vdctrl3, lcdif->base + LCDIF_VDCTRL3);
+
+ vdctrl4 = SET_DOTCLK_H_VALID_DATA_CNT(vmode->hactive);
+ writel(vdctrl4, lcdif->base + LCDIF_VDCTRL4);
+}
+EXPORT_SYMBOL(lcdif_set_mode);
+
+void lcdif_enable_controller(struct lcdif_soc *lcdif, bool use_i80)
+{
+ u32 ctrl2, vdctrl4, timing;
+
+ ctrl2 = readl(lcdif->base + LCDIF_CTRL2);
+ vdctrl4 = readl(lcdif->base + LCDIF_VDCTRL4);
+
+ ctrl2 &= ~CTRL2_OUTSTANDING_REQS(0x7);
+ ctrl2 |= CTRL2_OUTSTANDING_REQS(use_i80 ? REQ_8 : REQ_16);
+ writel(ctrl2, lcdif->base + LCDIF_CTRL2);
+
+ if (use_i80) {
+ /* MPU 8080 write mode */
+ writel(CTRL_DATA_SELECT, lcdif->base + LCDIF_CTRL + REG_SET);
+ writel(CTRL_READ_WRITEB, lcdif->base + LCDIF_CTRL + REG_CLR);
+ writel(CTRL_BYPASS_COUNT, lcdif->base + LCDIF_CTRL + REG_CLR);
+ writel(CTRL_DVI_MODE | CTRL_VSYNC_MODE | CTRL_DOTCLK_MODE,
+ lcdif->base + LCDIF_CTRL + REG_CLR);
+
+ writel(CTRL1_COMBINE_MPU_WR_STRB,
+ lcdif->base + LCDIF_CTRL1 + REG_CLR);
+
+ timing = TIMING_CMD_HOLD(2) | TIMING_CMD_SETUP(1) |
+ TIMING_DATA_HOLD(2) | TIMING_DATA_SETUP(1);
+ writel(timing, lcdif->base + LCDIF_TIMING);
+ } else {
+ /* Continuous dotclock mode */
+ writel(CTRL_BYPASS_COUNT | CTRL_DOTCLK_MODE,
+ lcdif->base + LCDIF_CTRL + REG_SET);
+ }
+
+ /* enable the SYNC signals first, then the DMA engine */
+ vdctrl4 |= VDCTRL4_SYNC_SIGNALS_ON;
+ writel(vdctrl4, lcdif->base + LCDIF_VDCTRL4);
+
+ /* enable underflow recovery */
+ writel(CTRL1_RECOVERY_ON_UNDERFLOW,
+ lcdif->base + LCDIF_CTRL1 + REG_SET);
+
+ /* run lcdif */
+ writel(CTRL_MASTER, lcdif->base + LCDIF_CTRL + REG_SET);
+ writel(CTRL_RUN, lcdif->base + LCDIF_CTRL + REG_SET);
+}
+EXPORT_SYMBOL(lcdif_enable_controller);
+
+void lcdif_disable_controller(struct lcdif_soc *lcdif, bool use_i80)
+{
+ int ret;
+ u32 ctrl, vdctrl4;
+
+ writel(CTRL_RUN, lcdif->base + LCDIF_CTRL + REG_CLR);
+
+ if (!use_i80) {
+ writel(CTRL_DOTCLK_MODE, lcdif->base + LCDIF_CTRL + REG_CLR);
+
+ ret = readl_poll_timeout(lcdif->base + LCDIF_CTRL, ctrl,
+ !(ctrl & CTRL_RUN), 0, 1000);
+ if (WARN_ON(ret))
+ dev_err(lcdif->dev, "disable lcdif run timeout\n");
+ }
+
+ writel(CTRL_MASTER, lcdif->base + LCDIF_CTRL + REG_CLR);
+
+ vdctrl4 = readl(lcdif->base + LCDIF_VDCTRL4);
+ vdctrl4 &= ~VDCTRL4_SYNC_SIGNALS_ON;
+ writel(vdctrl4, lcdif->base + LCDIF_VDCTRL4);
+}
+EXPORT_SYMBOL(lcdif_disable_controller);
+
+long lcdif_pix_clk_round_rate(struct lcdif_soc *lcdif,
+ unsigned long rate)
+{
+ if (unlikely(!rate))
+ return -EINVAL;
+
+ return clk_round_rate(lcdif->clk_pix, rate);
+}
+EXPORT_SYMBOL(lcdif_pix_clk_round_rate);
+
+static int platform_remove_device_fn(struct device *dev, void *data)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+
+ platform_device_unregister(pdev);
+
+ return 0;
+}
+
+static void platform_device_unregister_children(struct platform_device *pdev)
+{
+ device_for_each_child(&pdev->dev, NULL, platform_remove_device_fn);
+}
+
+static int lcdif_add_client_devices(struct lcdif_soc *lcdif)
+{
+ int ret = 0, i;
+ struct device *dev = lcdif->dev;
+ struct platform_device *pdev = NULL;
+ struct device_node *of_node;
+
+ for (i = 0; i < ARRAY_SIZE(client_reg); i++) {
+ of_node = of_graph_get_port_by_id(dev->of_node, i);
+ if (!of_node) {
+ dev_info(dev, "no port@%d node in %s\n",
+ i, dev->of_node->full_name);
+ continue;
+ }
+ of_node_put(of_node);
+
+ pdev = platform_device_alloc(client_reg[i].name, i);
+ if (!pdev) {
+ dev_err(dev, "Can't allocate port pdev\n");
+ ret = -ENOMEM;
+ goto err_register;
+ }
+
+ pdev->dev.parent = dev;
+ client_reg[i].pdata.of_node = of_node;
+
+ ret = platform_device_add_data(pdev, &client_reg[i].pdata,
+ sizeof(client_reg[i].pdata));
+ if (!ret)
+ ret = platform_device_add(pdev);
+ if (ret) {
+ platform_device_put(pdev);
+ goto err_register;
+ }
+
+ pdev->dev.of_node = of_node;
+ }
+
+ if (!pdev)
+ return -ENODEV;
+
+ return 0;
+
+err_register:
+ platform_device_unregister_children(to_platform_device(dev));
+ return ret;
+}
+
+static int lcdif_of_parse_resets(struct lcdif_soc *lcdif)
+{
+ int ret;
+ struct device *dev = lcdif->dev;
+ struct device_node *np = dev->of_node;
+ struct device_node *parent, *child;
+ struct of_phandle_args args;
+ struct reset_control *rstc;
+ const char *compat;
+ uint32_t len, rstc_num = 0;
+
+ ret = of_parse_phandle_with_args(np, "resets", "#reset-cells",
+ 0, &args);
+ if (ret)
+ return ret;
+
+ parent = args.np;
+ for_each_child_of_node(parent, child) {
+ compat = of_get_property(child, "compatible", NULL);
+ if (!compat)
+ continue;
+
+ rstc = of_reset_control_array_get(child, false, false, true);
+ if (IS_ERR(rstc))
+ continue;
+
+ len = strlen(compat);
+ if (!of_compat_cmp("lcdif,soft-resetn", compat, len)) {
+ lcdif->soft_resetn = rstc;
+ rstc_num++;
+ } else if (!of_compat_cmp("lcdif,clk-enable", compat, len)) {
+ lcdif->clk_enable = rstc;
+ rstc_num++;
+ }
+ else
+ dev_warn(dev, "invalid lcdif reset node: %s\n", compat);
+ }
+
+ if (!rstc_num) {
+ dev_err(dev, "no invalid reset control exists\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int imx_lcdif_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct device *dev = &pdev->dev;
+ struct lcdif_soc *lcdif;
+ struct resource *res;
+
+ dev_dbg(dev, "%s: probe begin\n", __func__);
+
+ lcdif = devm_kzalloc(dev, sizeof(*lcdif), GFP_KERNEL);
+ if (!lcdif) {
+ dev_err(dev, "Can't allocate 'lcdif_soc' structure\n");
+ return -ENOMEM;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENODEV;
+
+ lcdif->irq = platform_get_irq(pdev, 0);
+ if (lcdif->irq < 0)
+ return -ENODEV;
+
+ lcdif->clk_pix = devm_clk_get(dev, "pix");
+ if (IS_ERR(lcdif->clk_pix))
+ return PTR_ERR(lcdif->clk_pix);
+
+ lcdif->clk_disp_axi = devm_clk_get(dev, "disp-axi");
+ if (IS_ERR(lcdif->clk_disp_axi))
+ lcdif->clk_disp_axi = NULL;
+
+ lcdif->clk_disp_apb = devm_clk_get(dev, "disp-apb");
+ if (IS_ERR(lcdif->clk_disp_apb))
+ lcdif->clk_disp_apb = NULL;
+
+ lcdif->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(lcdif->base))
+ return PTR_ERR(lcdif->base);
+
+ lcdif->dev = dev;
+ ret = lcdif_of_parse_resets(lcdif);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, lcdif);
+
+ atomic_set(&lcdif->rpm_suspended, 0);
+ pm_runtime_enable(dev);
+ atomic_inc(&lcdif->rpm_suspended);
+
+ dev_dbg(dev, "%s: probe end\n", __func__);
+
+ return lcdif_add_client_devices(lcdif);
+}
+
+static int imx_lcdif_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int imx_lcdif_suspend(struct device *dev)
+{
+ return imx_lcdif_runtime_suspend(dev);
+}
+
+static int imx_lcdif_resume(struct device *dev)
+{
+ return imx_lcdif_runtime_resume(dev);
+}
+#endif
+
+#ifdef CONFIG_PM
+static int imx_lcdif_runtime_suspend(struct device *dev)
+{
+ struct lcdif_soc *lcdif = dev_get_drvdata(dev);
+
+ if (atomic_inc_return(&lcdif->rpm_suspended) > 1)
+ return 0;
+
+ lcdif_disable_clocks(lcdif);
+
+ release_bus_freq(BUS_FREQ_HIGH);
+
+ return 0;
+}
+
+static int imx_lcdif_runtime_resume(struct device *dev)
+{
+ int ret = 0;
+ struct lcdif_soc *lcdif = dev_get_drvdata(dev);
+
+ if (unlikely(!atomic_read(&lcdif->rpm_suspended))) {
+ dev_warn(lcdif->dev, "Unbalanced %s!\n", __func__);
+ return 0;
+ }
+
+ if (!atomic_dec_and_test(&lcdif->rpm_suspended))
+ return 0;
+
+ request_bus_freq(BUS_FREQ_HIGH);
+
+ ret = lcdif_enable_clocks(lcdif);
+ if (ret) {
+ release_bus_freq(BUS_FREQ_HIGH);
+ return ret;
+ }
+
+ ret = lcdif_rstc_reset(lcdif->soft_resetn, false);
+ if (ret) {
+ dev_err(dev, "deassert soft_resetn failed\n");
+ return ret;
+ }
+
+ ret = lcdif_rstc_reset(lcdif->clk_enable, true);
+ if (ret) {
+ dev_err(dev, "assert clk_enable failed\n");
+ return ret;
+ }
+
+ /* Pull LCDIF out of reset */
+ writel(0x0, lcdif->base + LCDIF_CTRL);
+
+ return ret;
+}
+#endif
+
+static const struct dev_pm_ops imx_lcdif_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(imx_lcdif_suspend, imx_lcdif_resume)
+ SET_RUNTIME_PM_OPS(imx_lcdif_runtime_suspend,
+ imx_lcdif_runtime_resume, NULL)
+};
+
+struct platform_driver imx_lcdif_driver = {
+ .probe = imx_lcdif_probe,
+ .remove = imx_lcdif_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = imx_lcdif_dt_ids,
+ .pm = &imx_lcdif_pm_ops,
+ },
+};
+
+module_platform_driver(imx_lcdif_driver);
+
+MODULE_DESCRIPTION("NXP i.MX LCDIF Display Controller driver");
+MODULE_AUTHOR("Fancy Fang <chen.fang@nxp.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/imx/lcdif/lcdif-regs.h b/drivers/gpu/imx/lcdif/lcdif-regs.h
new file mode 100644
index 000000000000..e40d5a7d5190
--- /dev/null
+++ b/drivers/gpu/imx/lcdif/lcdif-regs.h
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2018,2021 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __LCDIF_REGS_H
+#define __LCDIF_REGS_H
+
+#define REG_SET 4
+#define REG_CLR 8
+
+/* regs offset */
+#define LCDIF_CTRL 0x00
+#define LCDIF_CTRL1 0X10
+#define LCDIF_CTRL2 0X20
+#define LCDIF_TRANSFER_COUNT 0x30
+#define LCDIF_CUR_BUF 0x40
+#define LCDIF_NEXT_BUF 0x50
+#define LCDIF_TIMING 0x60
+#define LCDIF_VDCTRL0 0x70
+#define LCDIF_VDCTRL1 0x80
+#define LCDIF_VDCTRL2 0x90
+#define LCDIF_VDCTRL3 0xa0
+#define LCDIF_VDCTRL4 0xb0
+
+/* pigeon registers for crop */
+#define HW_EPDC_PIGEON_12_0 0xb00
+#define HW_EPDC_PIGEON_12_1 0xb10
+#define HW_EPDC_PIGEON_12_2 0xb20
+
+/* reg bit manipulation */
+#define REG_MASK(e, s) (((1 << ((e) - (s) + 1)) - 1) << (s))
+#define REG_PUT(x, e, s) (((x) << (s)) & REG_MASK(e, s))
+#define REG_GET(x, e, s) (((x) & REG_MASK(e, s)) >> (s))
+
+#define SWIZZLE_LE 0 /* Little-Endian or No swap */
+#define SWIZZLE_BE 1 /* Big-Endian or swap all */
+#define SWIZZLE_HWD 2 /* Swap half-words */
+#define SWIZZLE_HWD_BYTE 3 /* Swap bytes within each half-word */
+
+/* regs bit fields */
+#define CTRL_SFTRST BIT(31)
+#define CTRL_CLKGATE BIT(30)
+#define CTRL_READ_WRITEB BIT(28)
+#define CTRL_SHIFT_DIR(x) REG_PUT((x), 26, 26)
+#define CTRL_SHIFT_NUM(x) REG_PUT((x), 25, 21)
+#define CTRL_DVI_MODE BIT(20)
+#define CTRL_BYPASS_COUNT BIT(19)
+#define CTRL_VSYNC_MODE BIT(18)
+#define CTRL_DOTCLK_MODE BIT(17)
+#define CTRL_DATA_SELECT BIT(16)
+#define CTRL_INPUT_SWIZZLE(x) REG_PUT((x), 15, 14)
+#define CTRL_CSC_SWIZZLE(x) REG_PUT((x), 13, 12)
+#define CTRL_SET_BUS_WIDTH(x) REG_PUT((x), 11, 10)
+#define CTRL_GET_BUS_WIDTH(x) REG_GET((x), 11, 10)
+#define CTRL_BUS_WIDTH_MASK REG_PUT((0x3), 11, 10)
+#define CTRL_SET_WORD_LENGTH(x) REG_PUT((x), 9, 8)
+#define CTRL_GET_WORD_LENGTH(x) REG_GET((x), 9, 8)
+#define CTRL_MASTER BIT(5)
+#define CTRL_DF16 BIT(3)
+#define CTRL_DF18 BIT(2)
+#define CTRL_DF24 BIT(1)
+#define CTRL_RUN BIT(0)
+
+#define CTRL1_COMBINE_MPU_WR_STRB BIT(27)
+#define CTRL1_RECOVERY_ON_UNDERFLOW BIT(24)
+#define CTRL1_FIFO_CLEAR BIT(21)
+#define CTRL1_SET_BYTE_PACKAGING(x) REG_PUT((x), 19, 16)
+#define CTRL1_GET_BYTE_PACKAGING(x) REG_GET((x), 19, 16)
+#define CTRL1_CUR_FRAME_DONE_IRQ_EN BIT(13)
+#define CTRL1_CUR_FRAME_DONE_IRQ BIT(9)
+#define CTRL1_MODE86 BIT(1)
+
+#define REQ_1 0
+#define REQ_2 1
+#define REQ_4 2
+#define REQ_8 3
+#define REQ_16 4
+
+#define CTRL2_OUTSTANDING_REQS(x) REG_PUT((x), 23, 21)
+#define CTRL2_ODD_LINE_PATTERN(x) REG_PUT((x), 18, 16)
+#define CTRL2_EVEN_LINE_PATTERN(x) REG_PUT((x), 14, 12)
+
+#define TRANSFER_COUNT_SET_VCOUNT(x) (((x) & 0xffff) << 16)
+#define TRANSFER_COUNT_GET_VCOUNT(x) (((x) >> 16) & 0xffff)
+#define TRANSFER_COUNT_SET_HCOUNT(x) ((x) & 0xffff)
+#define TRANSFER_COUNT_GET_HCOUNT(x) ((x) & 0xffff)
+
+#define TIMING_CMD_HOLD(x) REG_PUT((x), 31, 24)
+#define TIMING_CMD_SETUP(x) REG_PUT((x), 23, 16)
+#define TIMING_DATA_HOLD(x) REG_PUT((x), 15, 8)
+#define TIMING_DATA_SETUP(x) REG_PUT((x), 7, 0)
+
+#define VDCTRL0_ENABLE_PRESENT BIT(28)
+#define VDCTRL0_VSYNC_ACT_HIGH BIT(27)
+#define VDCTRL0_HSYNC_ACT_HIGH BIT(26)
+#define VDCTRL0_DOTCLK_ACT_FALLING BIT(25)
+#define VDCTRL0_ENABLE_ACT_HIGH BIT(24)
+#define VDCTRL0_VSYNC_PERIOD_UNIT BIT(21)
+#define VDCTRL0_VSYNC_PULSE_WIDTH_UNIT BIT(20)
+#define VDCTRL0_HALF_LINE BIT(19)
+#define VDCTRL0_HALF_LINE_MODE BIT(18)
+#define VDCTRL0_SET_VSYNC_PULSE_WIDTH(x) ((x) & 0x3ffff)
+#define VDCTRL0_GET_VSYNC_PULSE_WIDTH(x) ((x) & 0x3ffff)
+
+#define VDCTRL2_SET_HSYNC_PULSE_WIDTH(x) (((x) & 0x3fff) << 18)
+#define VDCTRL2_GET_HSYNC_PULSE_WIDTH(x) (((x) >> 18) & 0x3fff)
+#define VDCTRL2_SET_HSYNC_PERIOD(x) ((x) & 0x3ffff)
+#define VDCTRL2_GET_HSYNC_PERIOD(x) ((x) & 0x3ffff)
+
+#define VDCTRL3_MUX_SYNC_SIGNALS BIT(29)
+#define VDCTRL3_VSYNC_ONLY BIT(28)
+#define SET_HOR_WAIT_CNT(x) (((x) & 0xfff) << 16)
+#define GET_HOR_WAIT_CNT(x) (((x) >> 16) & 0xfff)
+#define SET_VERT_WAIT_CNT(x) ((x) & 0xffff)
+#define GET_VERT_WAIT_CNT(x) ((x) & 0xffff)
+
+#define VDCTRL4_SET_DOTCLK_DLY(x) (((x) & 0x7) << 29) /* v4 only */
+#define VDCTRL4_GET_DOTCLK_DLY(x) (((x) >> 29) & 0x7) /* v4 only */
+#define VDCTRL4_SYNC_SIGNALS_ON BIT(18)
+#define SET_DOTCLK_H_VALID_DATA_CNT(x) ((x) & 0x3ffff)
+
+#define PIGEON_12_0_SET_STATE_MASK(x) REG_PUT((x), 31, 24)
+#define PIGEON_12_0_SET_MASK_CNT(x) REG_PUT((x), 23, 12)
+#define PIGEON_12_0_SET_MASK_CNT_SEL(x) REG_PUT((x), 11, 8)
+#define PIGEON_12_0_SET_OFFSET(x) REG_PUT((x), 7, 4)
+#define PIGEON_12_0_SET_INC_SEL(x) REG_PUT((x), 3, 2)
+#define PIGEON_12_0_POL_ACTIVE_LOW BIT(1)
+#define PIGEON_12_0_EN BIT(0)
+
+#define PIGEON_12_1_SET_CLR_CNT(x) REG_PUT((x), 31, 16)
+#define PIGEON_12_1_SET_SET_CNT(x) REG_PUT((x), 15, 0)
+
+#define STMLCDIF_8BIT 1 /* pixel data bus to the display is of 8 bit width */
+#define STMLCDIF_16BIT 0 /* pixel data bus to the display is of 16 bit width */
+#define STMLCDIF_18BIT 2 /* pixel data bus to the display is of 18 bit width */
+#define STMLCDIF_24BIT 3 /* pixel data bus to the display is of 24 bit width */
+
+#define MIN_XRES 120
+#define MIN_YRES 120
+#define MAX_XRES 0xffff
+#define MAX_YRES 0xffff
+
+#endif
diff --git a/drivers/gpu/imx/lcdifv3/Kconfig b/drivers/gpu/imx/lcdifv3/Kconfig
new file mode 100644
index 000000000000..8365c285f4a4
--- /dev/null
+++ b/drivers/gpu/imx/lcdifv3/Kconfig
@@ -0,0 +1,10 @@
+config IMX_LCDIFV3_CORE
+ tristate "i.MX LCDIFV3 core support"
+ depends on ARCH_MXC
+ depends on DRM && OF
+ select RESET_CONTROLLER
+ help
+ Choose this if you have a NXP i.MX8MP platform and want to use the
+ LCDIFV3 display controller. This option only enables LCDIFV3 base
+ support.
+
diff --git a/drivers/gpu/imx/lcdifv3/Makefile b/drivers/gpu/imx/lcdifv3/Makefile
new file mode 100644
index 000000000000..812043e52f34
--- /dev/null
+++ b/drivers/gpu/imx/lcdifv3/Makefile
@@ -0,0 +1,3 @@
+obj-$(CONFIG_IMX_LCDIFV3_CORE) += imx-lcdifv3-core.o
+
+imx-lcdifv3-core-objs := lcdifv3-common.o
diff --git a/drivers/gpu/imx/lcdifv3/lcdifv3-common.c b/drivers/gpu/imx/lcdifv3/lcdifv3-common.c
new file mode 100644
index 000000000000..8dab74c3fdad
--- /dev/null
+++ b/drivers/gpu/imx/lcdifv3/lcdifv3-common.c
@@ -0,0 +1,883 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+/*
+ * Copyright 2019,2022 NXP
+ */
+
+#include <linux/busfreq-imx.h>
+#include <linux/clk.h>
+#include <linux/iopoll.h>
+#include <linux/media-bus-format.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.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/reset.h>
+#include <linux/types.h>
+#include <drm/drm_fourcc.h>
+#include <video/imx-lcdifv3.h>
+#include <video/videomode.h>
+
+#include "lcdifv3-regs.h"
+
+#define DRIVER_NAME "imx-lcdifv3"
+
+struct lcdifv3_soc {
+ struct device *dev;
+
+ int irq;
+ void __iomem *base;
+ struct regmap *gpr;
+ atomic_t rpm_suspended;
+
+ struct clk *clk_pix;
+ struct clk *clk_disp_axi;
+ struct clk *clk_disp_apb;
+
+ u32 thres_low_mul;
+ u32 thres_low_div;
+ u32 thres_high_mul;
+ u32 thres_high_div;
+};
+
+struct lcdifv3_soc_pdata {
+ bool hsync_invert;
+ bool vsync_invert;
+ bool de_invert;
+ bool hdmimix;
+};
+
+struct lcdifv3_platform_reg {
+ struct lcdifv3_client_platformdata pdata;
+ char *name;
+};
+
+static struct lcdifv3_platform_reg client_reg[] = {
+ {
+ .pdata = { },
+ .name = "imx-lcdifv3-crtc",
+ },
+};
+
+static struct lcdifv3_soc_pdata imx8mp_lcdif1_pdata = {
+ .hsync_invert = false,
+ .vsync_invert = false,
+ .de_invert = false,
+ .hdmimix = false,
+};
+
+static struct lcdifv3_soc_pdata imx8mp_lcdif2_pdata = {
+ .hsync_invert = false,
+ .vsync_invert = false,
+ .de_invert = true,
+ .hdmimix = false,
+};
+
+static struct lcdifv3_soc_pdata imx8mp_lcdif3_pdata = {
+ .hsync_invert = false,
+ .vsync_invert = false,
+ .de_invert = false,
+ .hdmimix = true,
+};
+static const struct of_device_id imx_lcdifv3_dt_ids[] = {
+ { .compatible = "fsl,imx93-lcdif", },
+ { .compatible = "fsl,imx8mp-lcdif1", .data = &imx8mp_lcdif1_pdata, },
+ { .compatible = "fsl,imx8mp-lcdif2", .data = &imx8mp_lcdif2_pdata, },
+ { .compatible = "fsl,imx8mp-lcdif3", .data = &imx8mp_lcdif3_pdata,},
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_lcdifv3_dt_ids);
+
+static int lcdifv3_enable_clocks(struct lcdifv3_soc *lcdifv3)
+{
+ int ret;
+
+ if (lcdifv3->clk_disp_axi) {
+ ret = clk_prepare_enable(lcdifv3->clk_disp_axi);
+ if (ret)
+ return ret;
+ }
+
+ if (lcdifv3->clk_disp_apb) {
+ ret = clk_prepare_enable(lcdifv3->clk_disp_apb);
+ if (ret)
+ goto disable_disp_axi;
+ }
+
+ ret = clk_prepare_enable(lcdifv3->clk_pix);
+ if (ret)
+ goto disable_disp_apb;
+
+ return 0;
+
+disable_disp_apb:
+ if (lcdifv3->clk_disp_apb)
+ clk_disable_unprepare(lcdifv3->clk_disp_apb);
+disable_disp_axi:
+ if (lcdifv3->clk_disp_axi)
+ clk_disable_unprepare(lcdifv3->clk_disp_axi);
+
+ return ret;
+}
+
+static void lcdifv3_disable_clocks(struct lcdifv3_soc *lcdifv3)
+{
+ clk_disable_unprepare(lcdifv3->clk_pix);
+
+ if (lcdifv3->clk_disp_axi)
+ clk_disable_unprepare(lcdifv3->clk_disp_axi);
+
+ if (lcdifv3->clk_disp_apb)
+ clk_disable_unprepare(lcdifv3->clk_disp_apb);
+}
+
+static void lcdifv3_enable_plane_panic(struct lcdifv3_soc *lcdifv3)
+{
+ u32 panic_thres, thres_low, thres_high;
+
+ /* apb clock has been enabled */
+
+ /* As suggestion, the thres_low should be 1/3 FIFO,
+ * and thres_high should be 2/3 FIFO (The FIFO size
+ * is 8KB = 512 * 128bit).
+ * threshold = n * 128bit (n: 0 ~ 511)
+ */
+ thres_low = DIV_ROUND_UP(511 * lcdifv3->thres_low_mul,
+ lcdifv3->thres_low_div);
+ thres_high = DIV_ROUND_UP(511 * lcdifv3->thres_high_mul,
+ lcdifv3->thres_high_div);
+
+ panic_thres = PANIC0_THRES_PANIC_THRES_LOW(thres_low) |
+ PANIC0_THRES_PANIC_THRES_HIGH(thres_high);
+
+ writel(panic_thres, lcdifv3->base + LCDIFV3_PANIC0_THRES);
+
+ /* Enable Panic:
+ *
+ * As designed, the panic won't trigger an irq,
+ * so it is unnecessary to handle this as an irq
+ * and NoC + QoS modules will handle panic
+ * automatically.
+ */
+ writel(INT_ENABLE_D1_PLANE_PANIC_EN,
+ lcdifv3->base + LCDIFV3_INT_ENABLE_D1);
+}
+
+int lcdifv3_vblank_irq_get(struct lcdifv3_soc *lcdifv3)
+{
+ return lcdifv3->irq;
+}
+EXPORT_SYMBOL(lcdifv3_vblank_irq_get);
+
+/* TODO: use VS_BLANK or VSYNC? */
+void lcdifv3_vblank_irq_enable(struct lcdifv3_soc *lcdifv3)
+{
+ uint32_t int_enable_d0;
+
+ int_enable_d0 = readl(lcdifv3->base + LCDIFV3_INT_ENABLE_D0);
+ int_enable_d0 |= INT_STATUS_D0_VS_BLANK;
+
+ /* W1C */
+ writel(INT_STATUS_D0_VS_BLANK,
+ lcdifv3->base + LCDIFV3_INT_STATUS_D0);
+ /* enable */
+ writel(int_enable_d0,
+ lcdifv3->base + LCDIFV3_INT_ENABLE_D0);
+}
+EXPORT_SYMBOL(lcdifv3_vblank_irq_enable);
+
+void lcdifv3_vblank_irq_disable(struct lcdifv3_soc *lcdifv3)
+{
+ uint32_t int_enable_d0;
+
+ int_enable_d0 = readl(lcdifv3->base + LCDIFV3_INT_ENABLE_D0);
+ int_enable_d0 &= ~INT_STATUS_D0_VS_BLANK;
+
+ /* disable */
+ writel(int_enable_d0,
+ lcdifv3->base + LCDIFV3_INT_ENABLE_D0);
+ /* W1C */
+ writel(INT_STATUS_D0_VS_BLANK,
+ lcdifv3->base + LCDIFV3_INT_STATUS_D0);
+}
+EXPORT_SYMBOL(lcdifv3_vblank_irq_disable);
+
+void lcdifv3_vblank_irq_clear(struct lcdifv3_soc *lcdifv3)
+{
+ /* W1C */
+ writel(INT_STATUS_D0_VS_BLANK,
+ lcdifv3->base + LCDIFV3_INT_STATUS_D0);
+}
+EXPORT_SYMBOL(lcdifv3_vblank_irq_clear);
+
+static uint32_t lcdifv3_get_bpp_from_fmt(uint32_t format)
+{
+ /* TODO: only support RGB for now */
+
+ switch (format) {
+ case DRM_FORMAT_RGB565:
+ case DRM_FORMAT_BGR565:
+ case DRM_FORMAT_ARGB1555:
+ case DRM_FORMAT_XRGB1555:
+ case DRM_FORMAT_ABGR1555:
+ case DRM_FORMAT_XBGR1555:
+ return 16;
+ case DRM_FORMAT_ARGB8888:
+ case DRM_FORMAT_XRGB8888:
+ case DRM_FORMAT_ABGR8888:
+ case DRM_FORMAT_XBGR8888:
+ case DRM_FORMAT_RGBA8888:
+ case DRM_FORMAT_RGBX8888:
+ return 32;
+ default:
+ /* unsupported format */
+ return 0;
+ }
+}
+
+/*
+ * Get the bus format supported by LCDIF
+ * according to drm fourcc format
+ */
+int lcdifv3_get_bus_fmt_from_pix_fmt(struct lcdifv3_soc *lcdifv3,
+ uint32_t format)
+{
+ uint32_t bpp;
+
+ bpp = lcdifv3_get_bpp_from_fmt(format);
+ if (!bpp)
+ return -EINVAL;
+
+ switch (bpp) {
+ case 16:
+ return MEDIA_BUS_FMT_RGB565_1X16;
+ case 18:
+ return MEDIA_BUS_FMT_RGB666_1X18;
+ case 24:
+ case 32:
+ return MEDIA_BUS_FMT_RGB888_1X24;
+ default:
+ return -EINVAL;
+ }
+}
+EXPORT_SYMBOL(lcdifv3_get_bus_fmt_from_pix_fmt);
+
+int lcdifv3_set_pix_fmt(struct lcdifv3_soc *lcdifv3, u32 format)
+{
+ uint32_t ctrldescl0_5 = 0;
+
+ ctrldescl0_5 = readl(lcdifv3->base + LCDIFV3_CTRLDESCL0_5);
+
+ ctrldescl0_5 &= ~(CTRLDESCL0_5_BPP(0xf) | CTRLDESCL0_5_YUV_FORMAT(0x3));
+
+ switch (format) {
+ case DRM_FORMAT_RGB565:
+ ctrldescl0_5 |= CTRLDESCL0_5_BPP(BPP16_RGB565);
+ break;
+ case DRM_FORMAT_ARGB1555:
+ case DRM_FORMAT_XRGB1555:
+ ctrldescl0_5 |= CTRLDESCL0_5_BPP(BPP16_ARGB1555);
+ break;
+ case DRM_FORMAT_ARGB8888:
+ case DRM_FORMAT_XRGB8888:
+ ctrldescl0_5 |= CTRLDESCL0_5_BPP(BPP32_ARGB8888);
+ break;
+ case DRM_FORMAT_ABGR8888:
+ case DRM_FORMAT_XBGR8888:
+ ctrldescl0_5 |= CTRLDESCL0_5_BPP(BPP32_ABGR8888);
+ break;
+ default:
+ dev_err(lcdifv3->dev, "unsupported pixel format: %p4cc\n",
+ &format);
+ return -EINVAL;
+ }
+
+ writel(ctrldescl0_5, lcdifv3->base + LCDIFV3_CTRLDESCL0_5);
+
+ return 0;
+}
+EXPORT_SYMBOL(lcdifv3_set_pix_fmt);
+
+void lcdifv3_set_bus_fmt(struct lcdifv3_soc *lcdifv3, u32 bus_format)
+{
+ uint32_t disp_para = 0;
+
+ disp_para = readl(lcdifv3->base + LCDIFV3_DISP_PARA);
+
+ /* clear line pattern bits */
+ disp_para &= ~DISP_PARA_LINE_PATTERN(0xf);
+
+ switch (bus_format) {
+ case MEDIA_BUS_FMT_RGB565_1X16:
+ disp_para |= DISP_PARA_LINE_PATTERN(LP_RGB565);
+ break;
+ case MEDIA_BUS_FMT_RGB888_1X24:
+ disp_para |= DISP_PARA_LINE_PATTERN(LP_RGB888_OR_YUV444);
+ break;
+ default:
+ dev_err(lcdifv3->dev, "unknown bus format: %#x\n", bus_format);
+ return;
+ }
+
+ /* config display mode: default is normal mode */
+ disp_para &= ~DISP_PARA_DISP_MODE(3);
+ disp_para |= DISP_PARA_DISP_MODE(0);
+
+ writel(disp_para, lcdifv3->base + LCDIFV3_DISP_PARA);
+}
+EXPORT_SYMBOL(lcdifv3_set_bus_fmt);
+
+void lcdifv3_set_fb_addr(struct lcdifv3_soc *lcdifv3, int id, u32 addr)
+{
+ switch (id) {
+ case 0:
+ /* primary plane */
+ writel(addr, lcdifv3->base + LCDIFV3_CTRLDESCL_LOW0_4);
+ break;
+ default:
+ /* TODO: add overlay support */
+ return;
+ }
+}
+EXPORT_SYMBOL(lcdifv3_set_fb_addr);
+
+void lcdifv3_set_fb_hcrop(struct lcdifv3_soc *lcdifv3, u32 src_w,
+ u32 pitch, bool crop)
+{
+ uint32_t ctrldescl0_3 = 0;
+
+ /* config P_SIZE and T_SIZE:
+ * 1. P_SIZE and T_SIZE should never
+ * be less than AXI bus width.
+ * 2. P_SIZE should never be less than T_SIZE.
+ */
+ ctrldescl0_3 |= CTRLDESCL0_3_P_SIZE(2);
+ ctrldescl0_3 |= CTRLDESCL0_3_T_SIZE(2);
+
+ /* config pitch */
+ ctrldescl0_3 |= CTRLDESCL0_3_PITCH(pitch);
+
+ /* enable frame clear to clear FIFO data on
+ * every vsync blank period to make sure no
+ * dirty data exits to affect next frame
+ * display, otherwise some flicker issue may
+ * be observed in some cases.
+ */
+ ctrldescl0_3 |= CTRLDESCL0_3_STATE_CLEAR_VSYNC;
+
+ writel(ctrldescl0_3, lcdifv3->base + LCDIFV3_CTRLDESCL0_3);
+}
+EXPORT_SYMBOL(lcdifv3_set_fb_hcrop);
+
+
+void lcdifv3_set_mode(struct lcdifv3_soc *lcdifv3, struct videomode *vmode)
+{
+ const struct of_device_id *of_id =
+ of_match_device(imx_lcdifv3_dt_ids, lcdifv3->dev);
+ const struct lcdifv3_soc_pdata *soc_pdata;
+ u32 disp_size, hsyn_para, vsyn_para, vsyn_hsyn_width, ctrldescl0_1;
+
+ if (unlikely(!of_id))
+ return;
+ soc_pdata = of_id->data;
+
+ /* set pixel clock rate */
+ clk_disable_unprepare(lcdifv3->clk_pix);
+ clk_set_rate(lcdifv3->clk_pix, vmode->pixelclock);
+ clk_prepare_enable(lcdifv3->clk_pix);
+
+ /* config display timings */
+ disp_size = DISP_SIZE_DELTA_Y(vmode->vactive) |
+ DISP_SIZE_DELTA_X(vmode->hactive);
+ writel(disp_size, lcdifv3->base + LCDIFV3_DISP_SIZE);
+
+ WARN_ON(!vmode->hback_porch || !vmode->hfront_porch);
+ hsyn_para = HSYN_PARA_BP_H(vmode->hback_porch) |
+ HSYN_PARA_FP_H(vmode->hfront_porch);
+ writel(hsyn_para, lcdifv3->base + LCDIFV3_HSYN_PARA);
+
+ WARN_ON(!vmode->vback_porch || !vmode->vfront_porch);
+ vsyn_para = VSYN_PARA_BP_V(vmode->vback_porch) |
+ VSYN_PARA_FP_V(vmode->vfront_porch);
+ writel(vsyn_para, lcdifv3->base + LCDIFV3_VSYN_PARA);
+
+ WARN_ON(!vmode->vsync_len || !vmode->hsync_len);
+ vsyn_hsyn_width = VSYN_HSYN_WIDTH_PW_V(vmode->vsync_len) |
+ VSYN_HSYN_WIDTH_PW_H(vmode->hsync_len);
+ writel(vsyn_hsyn_width, lcdifv3->base + LCDIFV3_VSYN_HSYN_WIDTH);
+
+ /* config layer size */
+ /* TODO: 32bits alignment for width */
+ ctrldescl0_1 = CTRLDESCL0_1_HEIGHT(vmode->vactive) |
+ CTRLDESCL0_1_WIDTH(vmode->hactive);
+ writel(ctrldescl0_1, lcdifv3->base + LCDIFV3_CTRLDESCL0_1);
+
+ /* Polarities */
+ if (soc_pdata) {
+ if ((soc_pdata->hsync_invert &&
+ vmode->flags & DISPLAY_FLAGS_HSYNC_HIGH) ||
+ (!soc_pdata->hsync_invert &&
+ vmode->flags & DISPLAY_FLAGS_HSYNC_LOW))
+ writel(CTRL_INV_HS, lcdifv3->base + LCDIFV3_CTRL_SET);
+ else
+ writel(CTRL_INV_HS, lcdifv3->base + LCDIFV3_CTRL_CLR);
+
+ if ((soc_pdata->vsync_invert &&
+ vmode->flags & DISPLAY_FLAGS_VSYNC_HIGH) ||
+ (!soc_pdata->vsync_invert &&
+ vmode->flags & DISPLAY_FLAGS_VSYNC_LOW))
+ writel(CTRL_INV_VS, lcdifv3->base + LCDIFV3_CTRL_SET);
+ else
+ writel(CTRL_INV_VS, lcdifv3->base + LCDIFV3_CTRL_CLR);
+
+ if ((soc_pdata->de_invert &&
+ vmode->flags & DISPLAY_FLAGS_DE_HIGH) ||
+ (!soc_pdata->de_invert &&
+ vmode->flags & DISPLAY_FLAGS_DE_LOW))
+ writel(CTRL_INV_DE, lcdifv3->base + LCDIFV3_CTRL_SET);
+ else
+ writel(CTRL_INV_DE, lcdifv3->base + LCDIFV3_CTRL_CLR);
+ } else {
+ if (vmode->flags & DISPLAY_FLAGS_HSYNC_LOW)
+ writel(CTRL_INV_HS, lcdifv3->base + LCDIFV3_CTRL_SET);
+ else
+ writel(CTRL_INV_HS, lcdifv3->base + LCDIFV3_CTRL_CLR);
+ if (vmode->flags & DISPLAY_FLAGS_VSYNC_LOW)
+ writel(CTRL_INV_VS, lcdifv3->base + LCDIFV3_CTRL_SET);
+ else
+ writel(CTRL_INV_VS, lcdifv3->base + LCDIFV3_CTRL_CLR);
+ if (vmode->flags & DISPLAY_FLAGS_DE_LOW)
+ writel(CTRL_INV_DE, lcdifv3->base + LCDIFV3_CTRL_SET);
+ else
+ writel(CTRL_INV_DE, lcdifv3->base + LCDIFV3_CTRL_CLR);
+ }
+
+ if (vmode->flags & DISPLAY_FLAGS_PIXDATA_NEGEDGE)
+ writel(CTRL_INV_PXCK, lcdifv3->base + LCDIFV3_CTRL_SET);
+ else
+ writel(CTRL_INV_PXCK, lcdifv3->base + LCDIFV3_CTRL_CLR);
+}
+EXPORT_SYMBOL(lcdifv3_set_mode);
+
+void lcdifv3_en_shadow_load(struct lcdifv3_soc *lcdifv3)
+{
+ u32 ctrldescl0_5;
+
+ ctrldescl0_5 = readl(lcdifv3->base + LCDIFV3_CTRLDESCL0_5);
+ ctrldescl0_5 |= CTRLDESCL0_5_SHADOW_LOAD_EN;
+
+ writel(ctrldescl0_5, lcdifv3->base + LCDIFV3_CTRLDESCL0_5);
+}
+EXPORT_SYMBOL(lcdifv3_en_shadow_load);
+
+void lcdifv3_enable_controller(struct lcdifv3_soc *lcdifv3)
+{
+ u32 disp_para, ctrldescl0_5;
+
+ disp_para = readl(lcdifv3->base + LCDIFV3_DISP_PARA);
+ ctrldescl0_5 = readl(lcdifv3->base + LCDIFV3_CTRLDESCL0_5);
+
+ /* disp on */
+ disp_para |= DISP_PARA_DISP_ON;
+ writel(disp_para, lcdifv3->base + LCDIFV3_DISP_PARA);
+
+ /* enable layer dma */
+ ctrldescl0_5 |= CTRLDESCL0_5_EN;
+ writel(ctrldescl0_5, lcdifv3->base + LCDIFV3_CTRLDESCL0_5);
+}
+EXPORT_SYMBOL(lcdifv3_enable_controller);
+
+void lcdifv3_disable_controller(struct lcdifv3_soc *lcdifv3)
+{
+ u32 disp_para, ctrldescl0_5;
+
+ disp_para = readl(lcdifv3->base + LCDIFV3_DISP_PARA);
+ ctrldescl0_5 = readl(lcdifv3->base + LCDIFV3_CTRLDESCL0_5);
+
+ /* disable dma */
+ ctrldescl0_5 &= ~CTRLDESCL0_5_EN;
+ writel(ctrldescl0_5, lcdifv3->base + LCDIFV3_CTRLDESCL0_5);
+
+ /* dma config only takes effect at the end of
+ * one frame, so add delay to wait dma disable
+ * done before turn off disp.
+ */
+ usleep_range(20000, 25000);
+
+ /* disp off */
+ disp_para &= ~DISP_PARA_DISP_ON;
+ writel(disp_para, lcdifv3->base + LCDIFV3_DISP_PARA);
+}
+EXPORT_SYMBOL(lcdifv3_disable_controller);
+
+long lcdifv3_pix_clk_round_rate(struct lcdifv3_soc *lcdifv3,
+ unsigned long rate)
+{
+ if (unlikely(!rate))
+ return -EINVAL;
+
+ return clk_round_rate(lcdifv3->clk_pix, rate);
+}
+EXPORT_SYMBOL(lcdifv3_pix_clk_round_rate);
+
+static int hdmimix_lcdif3_setup(struct lcdifv3_soc *lcdifv3)
+{
+ struct device *dev = lcdifv3->dev;
+ int ret;
+
+ struct clk_bulk_data clocks[] = {
+ { .id = "mix_apb" },
+ { .id = "mix_axi" },
+ { .id = "xtl_24m" },
+ { .id = "mix_pix" },
+ { .id = "lcdif_apb" },
+ { .id = "lcdif_axi" },
+ { .id = "lcdif_pdi" },
+ { .id = "lcdif_pix" },
+ { .id = "lcdif_spu" },
+ { .id = "noc_hdmi" },
+ };
+
+ /* power up hdmimix lcdif and nor */
+ ret = device_reset(dev);
+ if (ret)
+ dev_warn(dev, "No hdmimix sub reset found\n");
+ if (ret == -EPROBE_DEFER)
+ return ret;
+
+ /* enable lpcg of hdmimix lcdif and nor */
+ ret = devm_clk_bulk_get(dev, ARRAY_SIZE(clocks), clocks);
+ if (ret < 0)
+ return ret;
+ ret = clk_bulk_prepare_enable(ARRAY_SIZE(clocks), clocks);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int platform_remove_device_fn(struct device *dev, void *data)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+
+ platform_device_unregister(pdev);
+
+ return 0;
+}
+
+static void platform_device_unregister_children(struct platform_device *pdev)
+{
+ device_for_each_child(&pdev->dev, NULL, platform_remove_device_fn);
+}
+
+static DEFINE_MUTEX(lcdifv3_client_id_mutex);
+static int lcdifv3_client_id;
+
+static int lcdifv3_add_client_devices(struct lcdifv3_soc *lcdifv3)
+{
+ int ret = 0, i, id;
+ struct device *dev = lcdifv3->dev;
+ struct platform_device *pdev = NULL;
+ struct device_node *of_node;
+
+ for (i = 0; i < ARRAY_SIZE(client_reg); i++) {
+ of_node = of_graph_get_port_by_id(dev->of_node, i);
+ if (!of_node) {
+ dev_info(dev, "no port@%d node in %s\n",
+ i, dev->of_node->full_name);
+ continue;
+ }
+ of_node_put(of_node);
+
+ mutex_lock(&lcdifv3_client_id_mutex);
+ id = lcdifv3_client_id++;
+ mutex_unlock(&lcdifv3_client_id_mutex);
+
+ pdev = platform_device_alloc(client_reg[i].name, id);
+ if (!pdev) {
+ dev_err(dev, "Can't allocate port pdev\n");
+ ret = -ENOMEM;
+ goto err_register;
+ }
+
+ pdev->dev.parent = dev;
+ client_reg[i].pdata.of_node = of_node;
+
+ /* make child device 'dma_mask' to point to its
+ * coherent dma mask, otherwise later probe will
+ * print warning message: 'DMA mask not set'.
+ */
+ pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask;
+
+ ret = platform_device_add_data(pdev, &client_reg[i].pdata,
+ sizeof(client_reg[i].pdata));
+ if (!ret)
+ ret = platform_device_add(pdev);
+ if (ret) {
+ platform_device_put(pdev);
+ goto err_register;
+ }
+
+ pdev->dev.of_node = of_node;
+ }
+
+ if (!pdev)
+ return -ENODEV;
+
+ return 0;
+
+err_register:
+ platform_device_unregister_children(to_platform_device(dev));
+ return ret;
+}
+
+static int imx_lcdifv3_check_thres_value(u32 mul, u32 div)
+{
+ if (!div)
+ return -EINVAL;
+
+ if (mul > div)
+ return -EINVAL;
+
+ return 0;
+}
+
+static void imx_lcdifv3_of_parse_thres(struct lcdifv3_soc *lcdifv3)
+{
+ int ret;
+ u32 thres_low[2], thres_high[2];
+ struct device_node *np = lcdifv3->dev->of_node;
+
+ /* default 'thres-low' value: FIFO * 1/3;
+ * default 'thres-high' value: FIFO * 2/3.
+ */
+ lcdifv3->thres_low_mul = 1;
+ lcdifv3->thres_low_div = 3;
+ lcdifv3->thres_high_mul = 2;
+ lcdifv3->thres_high_div = 3;
+
+ ret = of_property_read_u32_array(np, "thres-low", thres_low, 2);
+ if (!ret) {
+ /* check the value effectiveness */
+ ret = imx_lcdifv3_check_thres_value(thres_low[0], thres_low[1]);
+ if (!ret) {
+ lcdifv3->thres_low_mul = thres_low[0];
+ lcdifv3->thres_low_div = thres_low[1];
+ }
+ }
+
+ ret = of_property_read_u32_array(np, "thres-high", thres_high, 2);
+ if (!ret) {
+ /* check the value effectiveness */
+ ret = imx_lcdifv3_check_thres_value(thres_high[0], thres_high[1]);
+ if (!ret) {
+ lcdifv3->thres_high_mul = thres_high[0];
+ lcdifv3->thres_high_div = thres_high[1];
+ }
+ }
+}
+
+static int imx_lcdifv3_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct lcdifv3_soc *lcdifv3;
+ struct resource *res;
+ const struct of_device_id *of_id;
+ const struct lcdifv3_soc_pdata *soc_pdata;
+
+ dev_dbg(dev, "%s: probe begin\n", __func__);
+
+ of_id = of_match_device(imx_lcdifv3_dt_ids, dev);
+ if (!of_id) {
+ dev_err(&pdev->dev, "OF data missing\n");
+ return -EINVAL;
+ }
+
+ soc_pdata = of_id->data;
+
+ lcdifv3 = devm_kzalloc(dev, sizeof(*lcdifv3), GFP_KERNEL);
+ if (!lcdifv3) {
+ dev_err(dev, "Can't allocate 'lcdifv3_soc' structure\n");
+ return -ENOMEM;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENODEV;
+
+ lcdifv3->irq = platform_get_irq(pdev, 0);
+ if (lcdifv3->irq < 0) {
+ dev_err(dev, "No irq get, ret=%d\n", lcdifv3->irq);
+ return lcdifv3->irq;
+ }
+
+ lcdifv3->clk_pix = devm_clk_get(dev, "pix");
+ if (IS_ERR(lcdifv3->clk_pix)) {
+ ret = PTR_ERR(lcdifv3->clk_pix);
+ dev_err(dev, "No pix clock get: %d\n", ret);
+ return ret;
+ }
+
+ lcdifv3->clk_disp_axi = devm_clk_get(dev, "disp-axi");
+ if (IS_ERR(lcdifv3->clk_disp_axi))
+ lcdifv3->clk_disp_axi = NULL;
+
+ lcdifv3->clk_disp_apb = devm_clk_get(dev, "disp-apb");
+ if (IS_ERR(lcdifv3->clk_disp_apb))
+ lcdifv3->clk_disp_apb = NULL;
+
+ lcdifv3->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(lcdifv3->base))
+ return PTR_ERR(lcdifv3->base);
+
+ if (of_device_is_compatible(np, "fsl,imx93-lcdif")) {
+ lcdifv3->gpr = syscon_regmap_lookup_by_phandle(np, "fsl,gpr");
+ if (IS_ERR(lcdifv3->gpr)) {
+ ret = PTR_ERR(lcdifv3->gpr);
+ dev_err(dev, "failed to get gpr: %d\n", ret);
+ return ret;
+ }
+ }
+
+ lcdifv3->dev = dev;
+
+ /* reset controller to avoid any conflict
+ * with uboot splash screen settings.
+ */
+ if (of_device_is_compatible(np, "fsl,imx8mp-lcdif1")) {
+ /* TODO: Maybe the clock enable should
+ * be done in reset driver.
+ */
+ clk_prepare_enable(lcdifv3->clk_disp_axi);
+ clk_prepare_enable(lcdifv3->clk_disp_apb);
+
+ writel(CTRL_SW_RESET, lcdifv3->base + LCDIFV3_CTRL_CLR);
+
+ ret = device_reset(dev);
+ if (ret)
+ dev_warn(dev, "lcdif1 reset failed: %d\n", ret);
+
+ clk_disable_unprepare(lcdifv3->clk_disp_axi);
+ clk_disable_unprepare(lcdifv3->clk_disp_apb);
+ }
+
+ imx_lcdifv3_of_parse_thres(lcdifv3);
+
+ platform_set_drvdata(pdev, lcdifv3);
+
+ if (soc_pdata && soc_pdata->hdmimix) {
+ ret = hdmimix_lcdif3_setup(lcdifv3);
+ if (ret < 0) {
+ dev_err(dev, "hdmimix lcdif3 setup failed\n");
+ return ret;
+ }
+ }
+
+ atomic_set(&lcdifv3->rpm_suspended, 0);
+ pm_runtime_enable(dev);
+ atomic_inc(&lcdifv3->rpm_suspended);
+
+ dev_dbg(dev, "%s: probe end\n", __func__);
+
+ return lcdifv3_add_client_devices(lcdifv3);
+}
+
+static int imx_lcdifv3_remove(struct platform_device *pdev)
+{
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int imx_lcdifv3_runtime_suspend(struct device *dev)
+{
+ struct lcdifv3_soc *lcdifv3 = dev_get_drvdata(dev);
+
+ if (atomic_inc_return(&lcdifv3->rpm_suspended) > 1)
+ return 0;
+
+ lcdifv3_disable_clocks(lcdifv3);
+
+ release_bus_freq(BUS_FREQ_HIGH);
+
+ /* clear LCDIF QoS and cache */
+ if (of_device_is_compatible(dev->of_node, "fsl,imx93-lcdif"))
+ regmap_write(lcdifv3->gpr, 0xc, 0x0);
+
+ return 0;
+}
+
+static int imx_lcdifv3_runtime_resume(struct device *dev)
+{
+ int ret = 0;
+ struct lcdifv3_soc *lcdifv3 = dev_get_drvdata(dev);
+
+ if (unlikely(!atomic_read(&lcdifv3->rpm_suspended))) {
+ dev_warn(lcdifv3->dev, "Unbalanced %s!\n", __func__);
+ return 0;
+ }
+
+ if (!atomic_dec_and_test(&lcdifv3->rpm_suspended))
+ return 0;
+
+ /* set LCDIF QoS and cache */
+ if (of_device_is_compatible(dev->of_node, "fsl,imx93-lcdif"))
+ regmap_write(lcdifv3->gpr, 0xc, 0x3712);
+
+ request_bus_freq(BUS_FREQ_HIGH);
+
+ ret = lcdifv3_enable_clocks(lcdifv3);
+ if (ret) {
+ release_bus_freq(BUS_FREQ_HIGH);
+ return ret;
+ }
+
+ /* clear sw_reset */
+ writel(CTRL_SW_RESET, lcdifv3->base + LCDIFV3_CTRL_CLR);
+
+ /* enable plane FIFO panic */
+ lcdifv3_enable_plane_panic(lcdifv3);
+
+ return ret;
+}
+#endif
+
+#ifdef CONFIG_PM_SLEEP
+static int imx_lcdifv3_suspend(struct device *dev)
+{
+ return imx_lcdifv3_runtime_suspend(dev);
+}
+
+static int imx_lcdifv3_resume(struct device *dev)
+{
+ return imx_lcdifv3_runtime_resume(dev);
+}
+#endif
+
+static const struct dev_pm_ops imx_lcdifv3_pm_ops = {
+ SET_LATE_SYSTEM_SLEEP_PM_OPS(imx_lcdifv3_suspend,
+ imx_lcdifv3_resume)
+ SET_RUNTIME_PM_OPS(imx_lcdifv3_runtime_suspend,
+ imx_lcdifv3_runtime_resume, NULL)
+};
+
+struct platform_driver imx_lcdifv3_driver = {
+ .probe = imx_lcdifv3_probe,
+ .remove = imx_lcdifv3_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = imx_lcdifv3_dt_ids,
+ .pm = &imx_lcdifv3_pm_ops,
+ },
+};
+
+module_platform_driver(imx_lcdifv3_driver);
+
+MODULE_DESCRIPTION("NXP i.MX LCDIFV3 Display Controller driver");
+MODULE_AUTHOR("Fancy Fang <chen.fang@nxp.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/imx/lcdifv3/lcdifv3-regs.h b/drivers/gpu/imx/lcdifv3/lcdifv3-regs.h
new file mode 100644
index 000000000000..d47c2f80b8c3
--- /dev/null
+++ b/drivers/gpu/imx/lcdifv3/lcdifv3-regs.h
@@ -0,0 +1,150 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2019 NXP
+ */
+
+#ifndef __LCDIFV3_REGS_H
+#define __LCDIFV3_REGS_H
+
+/* regs offset */
+#define LCDIFV3_CTRL 0x00
+#define LCDIFV3_CTRL_SET 0x04
+#define LCDIFV3_CTRL_CLR 0x08
+#define LCDIFV3_CTRL_TOG 0x0c
+#define LCDIFV3_DISP_PARA 0x10
+#define LCDIFV3_DISP_SIZE 0x14
+#define LCDIFV3_HSYN_PARA 0x18
+#define LCDIFV3_VSYN_PARA 0x1c
+#define LCDIFV3_VSYN_HSYN_WIDTH 0x20
+#define LCDIFV3_INT_STATUS_D0 0x24
+#define LCDIFV3_INT_ENABLE_D0 0x28
+#define LCDIFV3_INT_STATUS_D1 0x30
+#define LCDIFV3_INT_ENABLE_D1 0x34
+
+#define LCDIFV3_CTRLDESCL0_1 0x200
+#define LCDIFV3_CTRLDESCL0_3 0x208
+#define LCDIFV3_CTRLDESCL_LOW0_4 0x20c
+#define LCDIFV3_CTRLDESCL_HIGH0_4 0x210
+#define LCDIFV3_CTRLDESCL0_5 0x214
+#define LCDIFV3_CSC0_CTRL 0x21c
+#define LCDIFV3_CSC0_COEF0 0x220
+#define LCDIFV3_CSC0_COEF1 0x224
+#define LCDIFV3_CSC0_COEF2 0x228
+#define LCDIFV3_CSC0_COEF3 0x22c
+#define LCDIFV3_CSC0_COEF4 0x230
+#define LCDIFV3_CSC0_COEF5 0x234
+#define LCDIFV3_PANIC0_THRES 0x238
+
+/* reg bit manipulation */
+#define REG_MASK(e, s) (((1 << ((e) - (s) + 1)) - 1) << (s))
+#define REG_PUT(x, e, s) (((x) << (s)) & REG_MASK(e, s))
+#define REG_GET(x, e, s) (((x) & REG_MASK(e, s)) >> (s))
+
+/* regs bit fields */
+#define CTRL_SW_RESET BIT(31)
+#define CTRL_FETCH_START_OPTION(x) REG_PUT((x), 9, 8)
+ #define FPV 0
+ #define PWV 1
+ #define BPV 2
+ #define RESV 3
+#define CTRL_NEG BIT(4)
+#define CTRL_INV_PXCK BIT(3)
+#define CTRL_INV_DE BIT(2)
+#define CTRL_INV_VS BIT(1)
+#define CTRL_INV_HS BIT(0)
+
+#define DISP_PARA_DISP_ON BIT(31)
+#define DISP_PARA_SWAP_EN BIT(30)
+#define DISP_PARA_LINE_PATTERN(x) REG_PUT((x), 29, 26)
+ /* line pattern formats (output) */
+ #define LP_RGB888_OR_YUV444 0x0
+ #define LP_RBG888 0x1
+ #define LP_GBR888 0x2
+ #define LP_GRB888_OR_UYV444 0x3
+ #define LP_BRG888 0x4
+ #define LP_BGR888 0x5
+ #define LP_RGB555 0x6
+ #define LP_RGB565 0x7
+ #define LP_YUYV_16_0 0x8
+ #define LP_UYVY_16_0 0x9
+ #define LP_YVYU_16_0 0xa
+ #define LP_VYUY_16_0 0xb
+ #define LP_YUYV_23_8 0xc
+ #define LP_UYVY_23_8 0xd
+ #define LP_YVYU_23_8 0xe
+ #define LP_VYUY_23_8 0xf
+
+#define DISP_PARA_DISP_MODE(x) REG_PUT((x), 25, 24)
+#define DISP_PARA_BGND_R(x) REG_PUT((x), 23, 16)
+#define DISP_PARA_BGND_G(x) REG_PUT((x), 15, 8)
+#define DISP_PARA_BGND_B(x) REG_PUT((x), 7, 0)
+
+#define DISP_SIZE_DELTA_Y(x) REG_PUT((x), 31, 16)
+#define DISP_SIZE_DELTA_X(x) REG_PUT((x), 15, 0)
+
+#define HSYN_PARA_BP_H(x) REG_PUT((x), 31, 16)
+#define HSYN_PARA_FP_H(x) REG_PUT((x), 15, 0)
+
+#define VSYN_PARA_BP_V(x) REG_PUT((x), 31, 16)
+#define VSYN_PARA_FP_V(x) REG_PUT((x), 15, 0)
+
+#define VSYN_HSYN_WIDTH_PW_V(x) REG_PUT((x), 31, 16)
+#define VSYN_HSYN_WIDTH_PW_H(x) REG_PUT((x), 15, 0)
+
+#define INT_STATUS_D0_FIFO_EMPTY BIT(24)
+#define INT_STATUS_D0_DMA_DONE BIT(16)
+#define INT_STATUS_D0_DMA_ERR BIT(8)
+#define INT_STATUS_D0_VS_BLANK BIT(2)
+#define INT_STATUS_D0_UNDERRUN BIT(1)
+#define INT_STATUS_D0_VSYNC BIT(0)
+
+#define INT_ENABLE_D0_FIFO_EMPTY_EN BIT(24)
+#define INT_ENABLE_D0_DMA_DONE_EN BIT(16)
+#define INT_ENABLE_D0_DMA_ERR_EN BIT(8)
+#define INT_ENABLE_D0_VS_BLANK_EN BIT(2)
+#define INT_ENABLE_D0_UNDERRUN_EN BIT(1)
+#define INT_ENABLE_D0_VSYNC_EN BIT(0)
+
+#define INT_STATUS_D1_PLANE_PANIC BIT(0)
+#define INT_ENABLE_D1_PLANE_PANIC_EN BIT(0)
+
+#define CTRLDESCL0_1_HEIGHT(x) REG_PUT((x), 31, 16)
+#define CTRLDESCL0_1_WIDTH(x) REG_PUT((x), 15, 0)
+#define CTRLDESCL0_3_STATE_CLEAR_VSYNC BIT(23)
+#define CTRLDESCL0_3_P_SIZE(x) REG_PUT((x), 22, 20)
+#define CTRLDESCL0_3_T_SIZE(x) REG_PUT((x), 17, 16)
+#define CTRLDESCL0_3_PITCH(x) REG_PUT((x), 15, 0)
+//#define CTRLDESCL_LOW0_4_ADDR_LOW(x) REG_PUT((x), 31, 0)
+#define CTRLDESCL_HIGH0_4_ADDR_HIGH(x) REG_PUT((x), 3, 0)
+#define CTRLDESCL0_5_EN BIT(31) /* enable layer for DMA */
+#define CTRLDESCL0_5_SHADOW_LOAD_EN BIT(30)
+#define CTRLDESCL0_5_BPP(x) REG_PUT((x), 27, 24)
+ /* layer encoding formats (input) */
+ #define BPP16_RGB565 0x4
+ #define BPP16_ARGB1555 0x5
+ #define BPP16_ARGB4444 0x6
+ #define BPP16_YCbCr422 0x7
+ #define BPP24_RGB888 0x8
+ #define BPP32_ARGB8888 0x9
+ #define BPP32_ABGR8888 0xa
+#define CTRLDESCL0_5_YUV_FORMAT(x) REG_PUT((x), 15, 14)
+
+#define CSC0_CTRL_CSC_MODE(x) REG_PUT((x), 2, 1)
+#define CSC0_CTRL_BYPASS BIT(0)
+#define CSC0_COEF0_A2(x) REG_PUT((x), 26, 16)
+#define CSC0_COEF0_A1(x) REG_PUT((x), 10, 0)
+#define CSC0_COEF1_B1(x) REG_PUT((x), 26, 16)
+#define CSC0_COEF1_A3(x) REG_PUT((x), 10, 0)
+#define CSC0_COEF2_B3(x) REG_PUT((x), 26, 16)
+#define CSC0_COEF2_B2(x) REG_PUT((x), 10, 0)
+#define CSC0_COEF3_C2(x) REG_PUT((x), 26, 16)
+#define CSC0_COEF3_C1(x) REG_PUT((x), 10, 0)
+#define CSC0_COEF4_D1(x) REG_PUT((x), 24, 16)
+#define CSC0_COEF4_C3(x) REG_PUT((x), 10, 0)
+#define CSC0_COEF5_D3(x) REG_PUT((x), 24, 16)
+#define CSC0_COEF5_D2(x) REG_PUT((x), 8, 0)
+
+#define PANIC0_THRES_PANIC_THRES_LOW(x) REG_PUT((x), 24, 16)
+#define PANIC0_THRES_PANIC_THRES_HIGH(x) REG_PUT((x), 8, 0)
+
+#endif /* __LCDIFV3_REGS_H */