};
};
+ i2c1: i2c@10040000 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ compatible = "snps,designware-i2c";
+ reg = <0x0 0x10040000 0x0 0x10000>;
+ interrupt-parent = <&plic>;
+ interrupts = <36>;
+ /*clocks = <&hfclk>; */
+ clock-frequency = <100000>;
+ i2c-sda-hold-time-ns = <300>;
+ i2c-sda-falling-time-ns = <3000>;
+ i2c-scl-falling-time-ns = <3000>;
+ auto_calc_scl_lhcnt;
+
+ adv7513@39 {
+ compatible = "adi,adv7513";
+ reg = <0x39>, <0x49>, <0x29>, <0x59>;
+ reg-names = "main", "edid", "cec", "packet";
+
+ adi,input-depth = <8>;
+ adi,input-colorspace = "rgb";
+ adi,input-clock = "1x";
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ adv7513_0_in: endpoint {
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+ adv7513_1_out: endpoint {
+ };
+ };
+ };
+ };
+ /*
+ seeed_plane_i2c@45 { //next to do handle this node to i2cX
+ compatible = "seeed_panel";
+ reg = <0x45>;
+
+ port {
+ panel_dsi_port: endpoint {
+ remote-endpoint = <&dsi_out_port>;
+ };
+ };
+ };*/
+ };
/*emmc*/
sdio0:sdio0@16010000{
compatible = "snps,dw-mshc";
mbox-names = "rx", "tx";
mboxes = <&mailbox_contrl0 0 1>,<&mailbox_contrl0 1 0>;
};
+
+ display-subsystem {
+ compatible = "verisilicon,display-subsystem";
+ //memory-region = <&sffb_reserved>;
+ ports = <&dc_out_dpi0>;
+ status = "okay";
+ };
+
+ display-encoder {
+ compatible = "starfive,display-encoder";
+ encoder-type = <2>; //2-TMDS, 3-LVDS, 6-DSI, 8-DPI
+ status = "okay";
+
+ ports {
+ port@0 {
+ endpoint {
+ remote-endpoint = <&adv7513_0_in>;
+ //remote-endpoint = <&dsi_0_in>;
+ };
+ };
+
+ port@1 {
+ endpoint {
+ remote-endpoint = <&dc_out_dpi0>;
+ };
+ };
+
+ };
+ };
+
+ dc8200@29400000 {
+ compatible = "verisilicon,dc8200";
+ reg = <0x0 0x29400000 0x0 0x100>,<0x0 0x29400800 0x0 0x2000>;
+ interrupt-parent = <&plic>;
+ interrupts = <95>;
+
+ port {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ dc_out_dpi0: endpoint@0 {
+ /*reg = <0>;
+ remote-endpoint = <&hdmi_input>;*/
+ };
+ dc_out_dpi1: endpoint@1 {
+ /*reg = <1>;
+ remote-endpoint = <&vd_input>;*/
+ };
+ };
+
+ /*port: port@0 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0>;
+
+ crtc_0_out: endpoint {
+ };
+ };*/
+ };
};
};
source "drivers/gpu/drm/gud/Kconfig"
+source "drivers/gpu/drm/verisilicon/Kconfig"
+
config DRM_HYPERV
tristate "DRM Support for Hyper-V synthetic video device"
depends on DRM && PCI && MMU && HYPERV
obj-y += xlnx/
obj-y += gud/
obj-$(CONFIG_DRM_HYPERV) += hyperv/
+obj-$(CONFIG_DRM_VERISILICON) += verisilicon/
!list_empty(&config->plane_list));
config->allow_fb_modifiers = true;
} else {
- drm_WARN_ON(dev, config->allow_fb_modifiers);
+ //drm_WARN_ON(dev, config->allow_fb_modifiers);
}
plane->modifier_count = format_modifier_count;
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0
+
+config DRM_VERISILICON
+ tristate "DRM Support for VeriSilicon"
+ depends on DRM
+ select DRM_KMS_HELPER
+ help
+ Choose this option if you have a VeriSilicon soc chipset.
+ This driver provides VeriSilicon kernel mode
+ setting and buffer management. It does not
+ provide 2D or 3D acceleration.
+
+config VERISILICON_VIRTUAL_DISPLAY
+ bool "display content output to debugfs file"
+ depends on DRM_VERISILICON
+ select DEBUG_FS
+ help
+ This is a debug feature which capture video content output
+ from display controller. Output path is debugfs/dri/connector/.
+ The content format is ARGB which Alpha is 0 for 8bits.
+ Disabled in default.
+
+config VERISILICON_DW_MIPI_DSI
+ bool "VeriSilicon specific driver for Synopsys DW MIPI DSI"
+ depends on DRM_VERISILICON
+ select DRM_MIPI_DSI
+ select GENERIC_PHY
+ select GENERIC_PHY_MIPI_DPHY
+ help
+ This driver supports Synopsys DW MIPI DSI controller.
+ MIPI DSI controller is a bridge between encoder
+ and panel. Also it is a MIPI DSI host and can work
+ in dual mode.
+
+config VERISILICON_MMU
+ bool "MMU support for VeriSilicon display controller"
+ depends on DRM_VERISILICON
+ help
+ This is a memory management function which translate virtual address
+ to physical address. DPU MMU only do address translate, doesn't
+ support shareable.
+
+config VERISILICON_DEC
+ bool "DEC support for VeriSilicon display controller"
+ depends on DRM_VERISILICON
+ help
+ This is a decompression function which reads compressed pixels from
+ external memory (DDR or SRAM) under DPU's control, then it decompress
+ comprssed pixels before returing these pixels to DPU.
+
+config STARFIVE_INNO_HDMI
+ bool "HDMI2.0"
+ help
+ This selects support for Rockchip SoC specific extensions
+ for the Innosilicon HDMI driver. If you want to enable
+ HDMI on RK3036 based SoC, you should select this option.
+config STARFIVE_DSI
+ bool "DSI"
+ help
+ enable dsi, you should select this option.
+
+source "drivers/gpu/drm/verisilicon/adv7511/Kconfig"
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0
+
+vs_drm-objs := vs_dc_hw.o \
+ vs_dc.o \
+ vs_crtc.o \
+ vs_drv.o \
+ vs_fb.o \
+ vs_gem.o \
+ vs_plane.o \
+ starfive_drm_encoder.o
+
+vs_drm-$(CONFIG_VERISILICON_VIRTUAL_DISPLAY) += vs_virtual.o
+vs_drm-$(CONFIG_VERISILICON_DW_MIPI_DSI) += dw_mipi_dsi.o
+vs_drm-$(CONFIG_VERISILICON_MMU) += vs_dc_mmu.o
+vs_drm-$(CONFIG_VERISILICON_DEC) += vs_dc_dec.o
+vs_drm-$(CONFIG_STARFIVE_INNO_HDMI) += inno_hdmi.o
+vs_drm-$(CONFIG_STARFIVE_DSI) += starfive_drm_dsi.o starfive_drm_seeedpanel.o
+obj-$(CONFIG_DRM_VERISILICON) += vs_drm.o
+
+obj-y+= adv7511/
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-only
+config DRM_I2C_ADV7513
+ tristate "ADV7513 encoder"
+ depends on OF
+ select DRM_KMS_HELPER
+ select REGMAP_I2C
+ select DRM_MIPI_DSI
+ help
+ Support for the Analog Devices ADV7511(W)/13/33/35 HDMI encoders.
+
+config DRM_I2C_ADV7511_AUDIO
+ bool "ADV7511 HDMI Audio driver"
+ depends on DRM_I2C_ADV7511 && SND_SOC
+ select SND_SOC_HDMI_CODEC
+ help
+ Support the ADV7511 HDMI Audio interface. This is used in
+ conjunction with the AV7511 HDMI driver.
+
+config DRM_I2C_ADV7511_CEC
+ bool "ADV7511/33/35 HDMI CEC driver"
+ depends on DRM_I2C_ADV7513
+ select CEC_CORE
+ default y
+ help
+ When selected the HDMI transmitter will support the CEC feature.
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-only
+adv7513-y := adv7511_drv.o adv7533.o
+adv7511-$(CONFIG_DRM_I2C_ADV7511_AUDIO) += adv7511_audio.o
+adv7511-$(CONFIG_DRM_I2C_ADV7511_CEC) += adv7511_cec.o
+obj-y += adv7513.o
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Analog Devices ADV7511 HDMI transmitter driver
+ *
+ * Copyright 2012 Analog Devices Inc.
+ */
+
+#ifndef __DRM_I2C_ADV7511_H__
+#define __DRM_I2C_ADV7511_H__
+
+#include <linux/hdmi.h>
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+
+#include <drm/drm_bridge.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_modes.h>
+
+#define ADV7511_REG_CHIP_REVISION 0x00
+#define ADV7511_REG_N0 0x01
+#define ADV7511_REG_N1 0x02
+#define ADV7511_REG_N2 0x03
+#define ADV7511_REG_SPDIF_FREQ 0x04
+#define ADV7511_REG_CTS_AUTOMATIC1 0x05
+#define ADV7511_REG_CTS_AUTOMATIC2 0x06
+#define ADV7511_REG_CTS_MANUAL0 0x07
+#define ADV7511_REG_CTS_MANUAL1 0x08
+#define ADV7511_REG_CTS_MANUAL2 0x09
+#define ADV7511_REG_AUDIO_SOURCE 0x0a
+#define ADV7511_REG_AUDIO_CONFIG 0x0b
+#define ADV7511_REG_I2S_CONFIG 0x0c
+#define ADV7511_REG_I2S_WIDTH 0x0d
+#define ADV7511_REG_AUDIO_SUB_SRC0 0x0e
+#define ADV7511_REG_AUDIO_SUB_SRC1 0x0f
+#define ADV7511_REG_AUDIO_SUB_SRC2 0x10
+#define ADV7511_REG_AUDIO_SUB_SRC3 0x11
+#define ADV7511_REG_AUDIO_CFG1 0x12
+#define ADV7511_REG_AUDIO_CFG2 0x13
+#define ADV7511_REG_AUDIO_CFG3 0x14
+#define ADV7511_REG_I2C_FREQ_ID_CFG 0x15
+#define ADV7511_REG_VIDEO_INPUT_CFG1 0x16
+#define ADV7511_REG_CSC_UPPER(x) (0x18 + (x) * 2)
+#define ADV7511_REG_CSC_LOWER(x) (0x19 + (x) * 2)
+#define ADV7511_REG_SYNC_DECODER(x) (0x30 + (x))
+#define ADV7511_REG_DE_GENERATOR (0x35 + (x))
+#define ADV7511_REG_PIXEL_REPETITION 0x3b
+#define ADV7511_REG_VIC_MANUAL 0x3c
+#define ADV7511_REG_VIC_SEND 0x3d
+#define ADV7511_REG_VIC_DETECTED 0x3e
+#define ADV7511_REG_AUX_VIC_DETECTED 0x3f
+#define ADV7511_REG_PACKET_ENABLE0 0x40
+#define ADV7511_REG_POWER 0x41
+#define ADV7511_REG_STATUS 0x42
+#define ADV7511_REG_EDID_I2C_ADDR 0x43
+#define ADV7511_REG_PACKET_ENABLE1 0x44
+#define ADV7511_REG_PACKET_I2C_ADDR 0x45
+#define ADV7511_REG_DSD_ENABLE 0x46
+#define ADV7511_REG_VIDEO_INPUT_CFG2 0x48
+#define ADV7511_REG_INFOFRAME_UPDATE 0x4a
+#define ADV7511_REG_GC(x) (0x4b + (x)) /* 0x4b - 0x51 */
+#define ADV7511_REG_AVI_INFOFRAME_VERSION 0x52
+#define ADV7511_REG_AVI_INFOFRAME_LENGTH 0x53
+#define ADV7511_REG_AVI_INFOFRAME_CHECKSUM 0x54
+#define ADV7511_REG_AVI_INFOFRAME(x) (0x55 + (x)) /* 0x55 - 0x6f */
+#define ADV7511_REG_AUDIO_INFOFRAME_VERSION 0x70
+#define ADV7511_REG_AUDIO_INFOFRAME_LENGTH 0x71
+#define ADV7511_REG_AUDIO_INFOFRAME_CHECKSUM 0x72
+#define ADV7511_REG_AUDIO_INFOFRAME(x) (0x73 + (x)) /* 0x73 - 0x7c */
+#define ADV7511_REG_INT_ENABLE(x) (0x94 + (x))
+#define ADV7511_REG_INT(x) (0x96 + (x))
+#define ADV7511_REG_INPUT_CLK_DIV 0x9d
+#define ADV7511_REG_PLL_STATUS 0x9e
+#define ADV7511_REG_HDMI_POWER 0xa1
+#define ADV7511_REG_HDCP_HDMI_CFG 0xaf
+#define ADV7511_REG_AN(x) (0xb0 + (x)) /* 0xb0 - 0xb7 */
+#define ADV7511_REG_HDCP_STATUS 0xb8
+#define ADV7511_REG_BCAPS 0xbe
+#define ADV7511_REG_BKSV(x) (0xc0 + (x)) /* 0xc0 - 0xc3 */
+#define ADV7511_REG_EDID_SEGMENT 0xc4
+#define ADV7511_REG_DDC_STATUS 0xc8
+#define ADV7511_REG_EDID_READ_CTRL 0xc9
+#define ADV7511_REG_BSTATUS(x) (0xca + (x)) /* 0xca - 0xcb */
+#define ADV7511_REG_TIMING_GEN_SEQ 0xd0
+#define ADV7511_REG_POWER2 0xd6
+#define ADV7511_REG_HSYNC_PLACEMENT_MSB 0xfa
+
+#define ADV7511_REG_SYNC_ADJUSTMENT(x) (0xd7 + (x)) /* 0xd7 - 0xdc */
+#define ADV7511_REG_TMDS_CLOCK_INV 0xde
+#define ADV7511_REG_ARC_CTRL 0xdf
+#define ADV7511_REG_CEC_I2C_ADDR 0xe1
+#define ADV7511_REG_CEC_CTRL 0xe2
+#define ADV7511_REG_CHIP_ID_HIGH 0xf5
+#define ADV7511_REG_CHIP_ID_LOW 0xf6
+
+/* Hardware defined default addresses for I2C register maps */
+#define ADV7511_CEC_I2C_ADDR_DEFAULT 0x3c
+#define ADV7511_EDID_I2C_ADDR_DEFAULT 0x3f
+#define ADV7511_PACKET_I2C_ADDR_DEFAULT 0x38
+
+#define ADV7511_CSC_ENABLE BIT(7)
+#define ADV7511_CSC_UPDATE_MODE BIT(5)
+
+#define ADV7511_INT0_HPD BIT(7)
+#define ADV7511_INT0_VSYNC BIT(5)
+#define ADV7511_INT0_AUDIO_FIFO_FULL BIT(4)
+#define ADV7511_INT0_EDID_READY BIT(2)
+#define ADV7511_INT0_HDCP_AUTHENTICATED BIT(1)
+
+#define ADV7511_INT1_DDC_ERROR BIT(7)
+#define ADV7511_INT1_BKSV BIT(6)
+#define ADV7511_INT1_CEC_TX_READY BIT(5)
+#define ADV7511_INT1_CEC_TX_ARBIT_LOST BIT(4)
+#define ADV7511_INT1_CEC_TX_RETRY_TIMEOUT BIT(3)
+#define ADV7511_INT1_CEC_RX_READY3 BIT(2)
+#define ADV7511_INT1_CEC_RX_READY2 BIT(1)
+#define ADV7511_INT1_CEC_RX_READY1 BIT(0)
+
+#define ADV7511_ARC_CTRL_POWER_DOWN BIT(0)
+
+#define ADV7511_CEC_CTRL_POWER_DOWN BIT(0)
+
+#define ADV7511_POWER_POWER_DOWN BIT(6)
+
+#define ADV7511_HDMI_CFG_MODE_MASK 0x2
+#define ADV7511_HDMI_CFG_MODE_DVI 0x0
+#define ADV7511_HDMI_CFG_MODE_HDMI 0x2
+
+#define ADV7511_AUDIO_SELECT_I2C 0x0
+#define ADV7511_AUDIO_SELECT_SPDIF 0x1
+#define ADV7511_AUDIO_SELECT_DSD 0x2
+#define ADV7511_AUDIO_SELECT_HBR 0x3
+#define ADV7511_AUDIO_SELECT_DST 0x4
+
+#define ADV7511_I2S_SAMPLE_LEN_16 0x2
+#define ADV7511_I2S_SAMPLE_LEN_20 0x3
+#define ADV7511_I2S_SAMPLE_LEN_18 0x4
+#define ADV7511_I2S_SAMPLE_LEN_22 0x5
+#define ADV7511_I2S_SAMPLE_LEN_19 0x8
+#define ADV7511_I2S_SAMPLE_LEN_23 0x9
+#define ADV7511_I2S_SAMPLE_LEN_24 0xb
+#define ADV7511_I2S_SAMPLE_LEN_17 0xc
+#define ADV7511_I2S_SAMPLE_LEN_21 0xd
+
+#define ADV7511_SAMPLE_FREQ_44100 0x0
+#define ADV7511_SAMPLE_FREQ_48000 0x2
+#define ADV7511_SAMPLE_FREQ_32000 0x3
+#define ADV7511_SAMPLE_FREQ_88200 0x8
+#define ADV7511_SAMPLE_FREQ_96000 0xa
+#define ADV7511_SAMPLE_FREQ_176400 0xc
+#define ADV7511_SAMPLE_FREQ_192000 0xe
+
+#define ADV7511_STATUS_POWER_DOWN_POLARITY BIT(7)
+#define ADV7511_STATUS_HPD BIT(6)
+#define ADV7511_STATUS_MONITOR_SENSE BIT(5)
+#define ADV7511_STATUS_I2S_32BIT_MODE BIT(3)
+
+#define ADV7511_PACKET_ENABLE_N_CTS BIT(8+6)
+#define ADV7511_PACKET_ENABLE_AUDIO_SAMPLE BIT(8+5)
+#define ADV7511_PACKET_ENABLE_AVI_INFOFRAME BIT(8+4)
+#define ADV7511_PACKET_ENABLE_AUDIO_INFOFRAME BIT(8+3)
+#define ADV7511_PACKET_ENABLE_GC BIT(7)
+#define ADV7511_PACKET_ENABLE_SPD BIT(6)
+#define ADV7511_PACKET_ENABLE_MPEG BIT(5)
+#define ADV7511_PACKET_ENABLE_ACP BIT(4)
+#define ADV7511_PACKET_ENABLE_ISRC BIT(3)
+#define ADV7511_PACKET_ENABLE_GM BIT(2)
+#define ADV7511_PACKET_ENABLE_SPARE2 BIT(1)
+#define ADV7511_PACKET_ENABLE_SPARE1 BIT(0)
+
+#define ADV7511_REG_POWER2_HPD_SRC_MASK 0xc0
+#define ADV7511_REG_POWER2_HPD_SRC_BOTH 0x00
+#define ADV7511_REG_POWER2_HPD_SRC_HPD 0x40
+#define ADV7511_REG_POWER2_HPD_SRC_CEC 0x80
+#define ADV7511_REG_POWER2_HPD_SRC_NONE 0xc0
+#define ADV7511_REG_POWER2_TDMS_ENABLE BIT(4)
+#define ADV7511_REG_POWER2_GATE_INPUT_CLK BIT(0)
+
+#define ADV7511_LOW_REFRESH_RATE_NONE 0x0
+#define ADV7511_LOW_REFRESH_RATE_24HZ 0x1
+#define ADV7511_LOW_REFRESH_RATE_25HZ 0x2
+#define ADV7511_LOW_REFRESH_RATE_30HZ 0x3
+
+#define ADV7511_AUDIO_CFG3_LEN_MASK 0x0f
+#define ADV7511_I2C_FREQ_ID_CFG_RATE_MASK 0xf0
+
+#define ADV7511_AUDIO_SOURCE_I2S 0
+#define ADV7511_AUDIO_SOURCE_SPDIF 1
+
+#define ADV7511_I2S_FORMAT_I2S 0
+#define ADV7511_I2S_FORMAT_RIGHT_J 1
+#define ADV7511_I2S_FORMAT_LEFT_J 2
+
+#define ADV7511_PACKET(p, x) ((p) * 0x20 + (x))
+#define ADV7511_PACKET_SDP(x) ADV7511_PACKET(0, x)
+#define ADV7511_PACKET_MPEG(x) ADV7511_PACKET(1, x)
+#define ADV7511_PACKET_ACP(x) ADV7511_PACKET(2, x)
+#define ADV7511_PACKET_ISRC1(x) ADV7511_PACKET(3, x)
+#define ADV7511_PACKET_ISRC2(x) ADV7511_PACKET(4, x)
+#define ADV7511_PACKET_GM(x) ADV7511_PACKET(5, x)
+#define ADV7511_PACKET_SPARE(x) ADV7511_PACKET(6, x)
+
+#define ADV7511_REG_CEC_TX_FRAME_HDR 0x00
+#define ADV7511_REG_CEC_TX_FRAME_DATA0 0x01
+#define ADV7511_REG_CEC_TX_FRAME_LEN 0x10
+#define ADV7511_REG_CEC_TX_ENABLE 0x11
+#define ADV7511_REG_CEC_TX_RETRY 0x12
+#define ADV7511_REG_CEC_TX_LOW_DRV_CNT 0x14
+#define ADV7511_REG_CEC_RX_FRAME_HDR 0x15
+#define ADV7511_REG_CEC_RX_FRAME_DATA0 0x16
+#define ADV7511_REG_CEC_RX_FRAME_LEN 0x25
+#define ADV7511_REG_CEC_RX_ENABLE 0x26
+#define ADV7511_REG_CEC_RX_BUFFERS 0x4a
+#define ADV7511_REG_CEC_LOG_ADDR_MASK 0x4b
+#define ADV7511_REG_CEC_LOG_ADDR_0_1 0x4c
+#define ADV7511_REG_CEC_LOG_ADDR_2 0x4d
+#define ADV7511_REG_CEC_CLK_DIV 0x4e
+#define ADV7511_REG_CEC_SOFT_RESET 0x50
+
+#define ADV7533_REG_CEC_OFFSET 0x70
+
+enum adv7511_input_clock {
+ ADV7511_INPUT_CLOCK_1X,
+ ADV7511_INPUT_CLOCK_2X,
+ ADV7511_INPUT_CLOCK_DDR,
+};
+
+enum adv7511_input_justification {
+ ADV7511_INPUT_JUSTIFICATION_EVENLY = 0,
+ ADV7511_INPUT_JUSTIFICATION_RIGHT = 1,
+ ADV7511_INPUT_JUSTIFICATION_LEFT = 2,
+};
+
+enum adv7511_input_sync_pulse {
+ ADV7511_INPUT_SYNC_PULSE_DE = 0,
+ ADV7511_INPUT_SYNC_PULSE_HSYNC = 1,
+ ADV7511_INPUT_SYNC_PULSE_VSYNC = 2,
+ ADV7511_INPUT_SYNC_PULSE_NONE = 3,
+};
+
+/**
+ * enum adv7511_sync_polarity - Polarity for the input sync signals
+ * @ADV7511_SYNC_POLARITY_PASSTHROUGH: Sync polarity matches that of
+ * the currently configured mode.
+ * @ADV7511_SYNC_POLARITY_LOW: Sync polarity is low
+ * @ADV7511_SYNC_POLARITY_HIGH: Sync polarity is high
+ *
+ * If the polarity is set to either LOW or HIGH the driver will configure the
+ * ADV7511 to internally invert the sync signal if required to match the sync
+ * polarity setting for the currently selected output mode.
+ *
+ * If the polarity is set to PASSTHROUGH, the ADV7511 will route the signal
+ * unchanged. This is used when the upstream graphics core already generates
+ * the sync signals with the correct polarity.
+ */
+enum adv7511_sync_polarity {
+ ADV7511_SYNC_POLARITY_PASSTHROUGH,
+ ADV7511_SYNC_POLARITY_LOW,
+ ADV7511_SYNC_POLARITY_HIGH,
+};
+
+/**
+ * struct adv7511_link_config - Describes adv7511 hardware configuration
+ * @input_color_depth: Number of bits per color component (8, 10 or 12)
+ * @input_colorspace: The input colorspace (RGB, YUV444, YUV422)
+ * @input_clock: The input video clock style (1x, 2x, DDR)
+ * @input_style: The input component arrangement variant
+ * @input_justification: Video input format bit justification
+ * @clock_delay: Clock delay for the input clock (in ps)
+ * @embedded_sync: Video input uses BT.656-style embedded sync
+ * @sync_pulse: Select the sync pulse
+ * @vsync_polarity: vsync input signal configuration
+ * @hsync_polarity: hsync input signal configuration
+ */
+struct adv7511_link_config {
+ unsigned int input_color_depth;
+ enum hdmi_colorspace input_colorspace;
+ enum adv7511_input_clock input_clock;
+ unsigned int input_style;
+ enum adv7511_input_justification input_justification;
+
+ int clock_delay;
+
+ bool embedded_sync;
+ enum adv7511_input_sync_pulse sync_pulse;
+ enum adv7511_sync_polarity vsync_polarity;
+ enum adv7511_sync_polarity hsync_polarity;
+};
+
+/**
+ * enum adv7511_csc_scaling - Scaling factor for the ADV7511 CSC
+ * @ADV7511_CSC_SCALING_1: CSC results are not scaled
+ * @ADV7511_CSC_SCALING_2: CSC results are scaled by a factor of two
+ * @ADV7511_CSC_SCALING_4: CSC results are scalled by a factor of four
+ */
+enum adv7511_csc_scaling {
+ ADV7511_CSC_SCALING_1 = 0,
+ ADV7511_CSC_SCALING_2 = 1,
+ ADV7511_CSC_SCALING_4 = 2,
+};
+
+/**
+ * struct adv7511_video_config - Describes adv7511 hardware configuration
+ * @csc_enable: Whether to enable color space conversion
+ * @csc_scaling_factor: Color space conversion scaling factor
+ * @csc_coefficents: Color space conversion coefficents
+ * @hdmi_mode: Whether to use HDMI or DVI output mode
+ * @avi_infoframe: HDMI infoframe
+ */
+struct adv7511_video_config {
+ bool csc_enable;
+ enum adv7511_csc_scaling csc_scaling_factor;
+ const uint16_t *csc_coefficents;
+
+ bool hdmi_mode;
+ struct hdmi_avi_infoframe avi_infoframe;
+};
+
+enum adv7511_type {
+ ADV7511,
+ ADV7533,
+ ADV7535,
+};
+
+#define ADV7511_MAX_ADDRS 3
+
+struct adv7511 {
+ struct i2c_client *i2c_main;
+ struct i2c_client *i2c_edid;
+ struct i2c_client *i2c_packet;
+ struct i2c_client *i2c_cec;
+
+ struct regmap *regmap;
+ struct regmap *regmap_cec;
+ enum drm_connector_status status;
+ bool powered;
+
+ struct drm_display_mode curr_mode;
+
+ unsigned int f_tmds;
+ unsigned int f_audio;
+ unsigned int audio_source;
+
+ unsigned int current_edid_segment;
+ uint8_t edid_buf[256];
+ bool edid_read;
+
+ wait_queue_head_t wq;
+ struct work_struct hpd_work;
+
+ struct drm_encoder encoder;
+ struct drm_bridge bridge;
+ struct drm_connector connector;
+
+ bool embedded_sync;
+ enum adv7511_sync_polarity vsync_polarity;
+ enum adv7511_sync_polarity hsync_polarity;
+ bool rgb;
+
+ struct gpio_desc *gpio_pd;
+
+ struct regulator_bulk_data *supplies;
+ unsigned int num_supplies;
+
+ /* ADV7533 DSI RX related params */
+ struct device_node *host_node;
+ struct mipi_dsi_device *dsi;
+ u8 num_dsi_lanes;
+ bool use_timing_gen;
+
+ enum adv7511_type type;
+ struct platform_device *audio_pdev;
+
+ struct cec_adapter *cec_adap;
+ u8 cec_addr[ADV7511_MAX_ADDRS];
+ u8 cec_valid_addrs;
+ bool cec_enabled_adap;
+ struct clk *cec_clk;
+ u32 cec_clk_freq;
+};
+
+#ifdef CONFIG_DRM_I2C_ADV7511_CEC
+int adv7511_cec_init(struct device *dev, struct adv7511 *adv7511);
+void adv7511_cec_irq_process(struct adv7511 *adv7511, unsigned int irq1);
+#else
+static inline int adv7511_cec_init(struct device *dev, struct adv7511 *adv7511)
+{
+ unsigned int offset = adv7511->type == ADV7533 ?
+ ADV7533_REG_CEC_OFFSET : 0;
+
+ regmap_write(adv7511->regmap, ADV7511_REG_CEC_CTRL + offset,
+ ADV7511_CEC_CTRL_POWER_DOWN);
+ return 0;
+}
+#endif
+
+void adv7533_dsi_power_on(struct adv7511 *adv);
+void adv7533_dsi_power_off(struct adv7511 *adv);
+void adv7533_mode_set(struct adv7511 *adv, const struct drm_display_mode *mode);
+int adv7533_patch_registers(struct adv7511 *adv);
+int adv7533_patch_cec_registers(struct adv7511 *adv);
+int adv7533_attach_dsi(struct adv7511 *adv);
+void adv7533_detach_dsi(struct adv7511 *adv);
+int adv7533_parse_dt(struct device_node *np, struct adv7511 *adv);
+
+#ifdef CONFIG_DRM_I2C_ADV7511_AUDIO
+int adv7511_audio_init(struct device *dev, struct adv7511 *adv7511);
+void adv7511_audio_exit(struct adv7511 *adv7511);
+#else /*CONFIG_DRM_I2C_ADV7511_AUDIO */
+static inline int adv7511_audio_init(struct device *dev, struct adv7511 *adv7511)
+{
+ return 0;
+}
+static inline void adv7511_audio_exit(struct adv7511 *adv7511)
+{
+}
+#endif /* CONFIG_DRM_I2C_ADV7511_AUDIO */
+
+#endif /* __DRM_I2C_ADV7511_H__ */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Analog Devices ADV7511 HDMI transmitter driver
+ *
+ * Copyright 2012 Analog Devices Inc.
+ * Copyright (c) 2016, Linaro Limited
+ */
+
+#include <sound/core.h>
+#include <sound/hdmi-codec.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <linux/of_graph.h>
+
+#include "adv7511.h"
+
+static void adv7511_calc_cts_n(unsigned int f_tmds, unsigned int fs,
+ unsigned int *cts, unsigned int *n)
+{
+ switch (fs) {
+ case 32000:
+ case 48000:
+ case 96000:
+ case 192000:
+ *n = fs * 128 / 1000;
+ break;
+ case 44100:
+ case 88200:
+ case 176400:
+ *n = fs * 128 / 900;
+ break;
+ }
+
+ *cts = ((f_tmds * *n) / (128 * fs)) * 1000;
+}
+
+static int adv7511_update_cts_n(struct adv7511 *adv7511)
+{
+ unsigned int cts = 0;
+ unsigned int n = 0;
+
+ adv7511_calc_cts_n(adv7511->f_tmds, adv7511->f_audio, &cts, &n);
+
+ regmap_write(adv7511->regmap, ADV7511_REG_N0, (n >> 16) & 0xf);
+ regmap_write(adv7511->regmap, ADV7511_REG_N1, (n >> 8) & 0xff);
+ regmap_write(adv7511->regmap, ADV7511_REG_N2, n & 0xff);
+
+ regmap_write(adv7511->regmap, ADV7511_REG_CTS_MANUAL0,
+ (cts >> 16) & 0xf);
+ regmap_write(adv7511->regmap, ADV7511_REG_CTS_MANUAL1,
+ (cts >> 8) & 0xff);
+ regmap_write(adv7511->regmap, ADV7511_REG_CTS_MANUAL2,
+ cts & 0xff);
+
+ return 0;
+}
+
+int adv7511_hdmi_hw_params(struct device *dev, void *data,
+ struct hdmi_codec_daifmt *fmt,
+ struct hdmi_codec_params *hparms)
+{
+ struct adv7511 *adv7511 = dev_get_drvdata(dev);
+ unsigned int audio_source, i2s_format = 0;
+ unsigned int invert_clock;
+ unsigned int rate;
+ unsigned int len;
+
+ switch (hparms->sample_rate) {
+ case 32000:
+ rate = ADV7511_SAMPLE_FREQ_32000;
+ break;
+ case 44100:
+ rate = ADV7511_SAMPLE_FREQ_44100;
+ break;
+ case 48000:
+ rate = ADV7511_SAMPLE_FREQ_48000;
+ break;
+ case 88200:
+ rate = ADV7511_SAMPLE_FREQ_88200;
+ break;
+ case 96000:
+ rate = ADV7511_SAMPLE_FREQ_96000;
+ break;
+ case 176400:
+ rate = ADV7511_SAMPLE_FREQ_176400;
+ break;
+ case 192000:
+ rate = ADV7511_SAMPLE_FREQ_192000;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (hparms->sample_width) {
+ case 16:
+ len = ADV7511_I2S_SAMPLE_LEN_16;
+ break;
+ case 18:
+ len = ADV7511_I2S_SAMPLE_LEN_18;
+ break;
+ case 20:
+ len = ADV7511_I2S_SAMPLE_LEN_20;
+ break;
+ case 24:
+ len = ADV7511_I2S_SAMPLE_LEN_24;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt->fmt) {
+ case HDMI_I2S:
+ audio_source = ADV7511_AUDIO_SOURCE_I2S;
+ i2s_format = ADV7511_I2S_FORMAT_I2S;
+ break;
+ case HDMI_RIGHT_J:
+ audio_source = ADV7511_AUDIO_SOURCE_I2S;
+ i2s_format = ADV7511_I2S_FORMAT_RIGHT_J;
+ break;
+ case HDMI_LEFT_J:
+ audio_source = ADV7511_AUDIO_SOURCE_I2S;
+ i2s_format = ADV7511_I2S_FORMAT_LEFT_J;
+ break;
+ case HDMI_SPDIF:
+ audio_source = ADV7511_AUDIO_SOURCE_SPDIF;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ invert_clock = fmt->bit_clk_inv;
+
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_AUDIO_SOURCE, 0x70,
+ audio_source << 4);
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_AUDIO_CONFIG, BIT(6),
+ invert_clock << 6);
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_I2S_CONFIG, 0x03,
+ i2s_format);
+
+ adv7511->audio_source = audio_source;
+
+ adv7511->f_audio = hparms->sample_rate;
+
+ adv7511_update_cts_n(adv7511);
+
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_AUDIO_CFG3,
+ ADV7511_AUDIO_CFG3_LEN_MASK, len);
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_I2C_FREQ_ID_CFG,
+ ADV7511_I2C_FREQ_ID_CFG_RATE_MASK, rate << 4);
+ regmap_write(adv7511->regmap, 0x73, 0x1);
+
+ return 0;
+}
+
+static int audio_startup(struct device *dev, void *data)
+{
+ struct adv7511 *adv7511 = dev_get_drvdata(dev);
+
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_AUDIO_CONFIG,
+ BIT(7), 0);
+
+ /* hide Audio infoframe updates */
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_INFOFRAME_UPDATE,
+ BIT(5), BIT(5));
+ /* enable N/CTS, enable Audio sample packets */
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_PACKET_ENABLE1,
+ BIT(5), BIT(5));
+ /* enable N/CTS */
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_PACKET_ENABLE1,
+ BIT(6), BIT(6));
+ /* not copyrighted */
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_AUDIO_CFG1,
+ BIT(5), BIT(5));
+ /* enable audio infoframes */
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_PACKET_ENABLE1,
+ BIT(3), BIT(3));
+ /* AV mute disable */
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_GC(0),
+ BIT(7) | BIT(6), BIT(7));
+ /* use Audio infoframe updated info */
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_GC(1),
+ BIT(5), 0);
+ /* enable SPDIF receiver */
+ if (adv7511->audio_source == ADV7511_AUDIO_SOURCE_SPDIF)
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_AUDIO_CONFIG,
+ BIT(7), BIT(7));
+
+ return 0;
+}
+
+static void audio_shutdown(struct device *dev, void *data)
+{
+ struct adv7511 *adv7511 = dev_get_drvdata(dev);
+
+ if (adv7511->audio_source == ADV7511_AUDIO_SOURCE_SPDIF)
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_AUDIO_CONFIG,
+ BIT(7), 0);
+}
+
+static int adv7511_hdmi_i2s_get_dai_id(struct snd_soc_component *component,
+ struct device_node *endpoint)
+{
+ struct of_endpoint of_ep;
+ int ret;
+
+ ret = of_graph_parse_endpoint(endpoint, &of_ep);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * HDMI sound should be located as reg = <2>
+ * Then, it is sound port 0
+ */
+ if (of_ep.port == 2)
+ return 0;
+
+ return -EINVAL;
+}
+
+static const struct hdmi_codec_ops adv7511_codec_ops = {
+ .hw_params = adv7511_hdmi_hw_params,
+ .audio_shutdown = audio_shutdown,
+ .audio_startup = audio_startup,
+ .get_dai_id = adv7511_hdmi_i2s_get_dai_id,
+};
+
+static const struct hdmi_codec_pdata codec_data = {
+ .ops = &adv7511_codec_ops,
+ .max_i2s_channels = 2,
+ .i2s = 1,
+ .spdif = 1,
+};
+
+int adv7511_audio_init(struct device *dev, struct adv7511 *adv7511)
+{
+ adv7511->audio_pdev = platform_device_register_data(dev,
+ HDMI_CODEC_DRV_NAME,
+ PLATFORM_DEVID_AUTO,
+ &codec_data,
+ sizeof(codec_data));
+ return PTR_ERR_OR_ZERO(adv7511->audio_pdev);
+}
+
+void adv7511_audio_exit(struct adv7511 *adv7511)
+{
+ if (adv7511->audio_pdev) {
+ platform_device_unregister(adv7511->audio_pdev);
+ adv7511->audio_pdev = NULL;
+ }
+}
--- /dev/null
+/*
+ * adv7511_cec.c - Analog Devices ADV7511/33 cec driver
+ *
+ * Copyright 2017 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
+ *
+ * This program is free software; you may redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+
+#include <media/cec.h>
+
+#include "adv7511.h"
+
+#define ADV7511_INT1_CEC_MASK \
+ (ADV7511_INT1_CEC_TX_READY | ADV7511_INT1_CEC_TX_ARBIT_LOST | \
+ ADV7511_INT1_CEC_TX_RETRY_TIMEOUT | ADV7511_INT1_CEC_RX_READY1)
+
+static void adv_cec_tx_raw_status(struct adv7511 *adv7511, u8 tx_raw_status)
+{
+ unsigned int offset = adv7511->type == ADV7533 ?
+ ADV7533_REG_CEC_OFFSET : 0;
+ unsigned int val;
+
+ if (regmap_read(adv7511->regmap_cec,
+ ADV7511_REG_CEC_TX_ENABLE + offset, &val))
+ return;
+
+ if ((val & 0x01) == 0)
+ return;
+
+ if (tx_raw_status & ADV7511_INT1_CEC_TX_ARBIT_LOST) {
+ cec_transmit_attempt_done(adv7511->cec_adap,
+ CEC_TX_STATUS_ARB_LOST);
+ return;
+ }
+ if (tx_raw_status & ADV7511_INT1_CEC_TX_RETRY_TIMEOUT) {
+ u8 status;
+ u8 err_cnt = 0;
+ u8 nack_cnt = 0;
+ u8 low_drive_cnt = 0;
+ unsigned int cnt;
+
+ /*
+ * We set this status bit since this hardware performs
+ * retransmissions.
+ */
+ status = CEC_TX_STATUS_MAX_RETRIES;
+ if (regmap_read(adv7511->regmap_cec,
+ ADV7511_REG_CEC_TX_LOW_DRV_CNT + offset, &cnt)) {
+ err_cnt = 1;
+ status |= CEC_TX_STATUS_ERROR;
+ } else {
+ nack_cnt = cnt & 0xf;
+ if (nack_cnt)
+ status |= CEC_TX_STATUS_NACK;
+ low_drive_cnt = cnt >> 4;
+ if (low_drive_cnt)
+ status |= CEC_TX_STATUS_LOW_DRIVE;
+ }
+ cec_transmit_done(adv7511->cec_adap, status,
+ 0, nack_cnt, low_drive_cnt, err_cnt);
+ return;
+ }
+ if (tx_raw_status & ADV7511_INT1_CEC_TX_READY) {
+ cec_transmit_attempt_done(adv7511->cec_adap, CEC_TX_STATUS_OK);
+ return;
+ }
+}
+
+void adv7511_cec_irq_process(struct adv7511 *adv7511, unsigned int irq1)
+{
+ unsigned int offset = adv7511->type == ADV7533 ?
+ ADV7533_REG_CEC_OFFSET : 0;
+ const u32 irq_tx_mask = ADV7511_INT1_CEC_TX_READY |
+ ADV7511_INT1_CEC_TX_ARBIT_LOST |
+ ADV7511_INT1_CEC_TX_RETRY_TIMEOUT;
+ struct cec_msg msg = {};
+ unsigned int len;
+ unsigned int val;
+ u8 i;
+
+ if (irq1 & irq_tx_mask)
+ adv_cec_tx_raw_status(adv7511, irq1);
+
+ if (!(irq1 & ADV7511_INT1_CEC_RX_READY1))
+ return;
+
+ if (regmap_read(adv7511->regmap_cec,
+ ADV7511_REG_CEC_RX_FRAME_LEN + offset, &len))
+ return;
+
+ msg.len = len & 0x1f;
+
+ if (msg.len > 16)
+ msg.len = 16;
+
+ if (!msg.len)
+ return;
+
+ for (i = 0; i < msg.len; i++) {
+ regmap_read(adv7511->regmap_cec,
+ i + ADV7511_REG_CEC_RX_FRAME_HDR + offset, &val);
+ msg.msg[i] = val;
+ }
+
+ /* toggle to re-enable rx 1 */
+ regmap_write(adv7511->regmap_cec,
+ ADV7511_REG_CEC_RX_BUFFERS + offset, 1);
+ regmap_write(adv7511->regmap_cec,
+ ADV7511_REG_CEC_RX_BUFFERS + offset, 0);
+ cec_received_msg(adv7511->cec_adap, &msg);
+}
+
+static int adv7511_cec_adap_enable(struct cec_adapter *adap, bool enable)
+{
+ struct adv7511 *adv7511 = cec_get_drvdata(adap);
+ unsigned int offset = adv7511->type == ADV7533 ?
+ ADV7533_REG_CEC_OFFSET : 0;
+
+ if (adv7511->i2c_cec == NULL)
+ return -EIO;
+
+ if (!adv7511->cec_enabled_adap && enable) {
+ /* power up cec section */
+ regmap_update_bits(adv7511->regmap_cec,
+ ADV7511_REG_CEC_CLK_DIV + offset,
+ 0x03, 0x01);
+ /* legacy mode and clear all rx buffers */
+ regmap_write(adv7511->regmap_cec,
+ ADV7511_REG_CEC_RX_BUFFERS + offset, 0x07);
+ regmap_write(adv7511->regmap_cec,
+ ADV7511_REG_CEC_RX_BUFFERS + offset, 0);
+ /* initially disable tx */
+ regmap_update_bits(adv7511->regmap_cec,
+ ADV7511_REG_CEC_TX_ENABLE + offset, 1, 0);
+ /* enabled irqs: */
+ /* tx: ready */
+ /* tx: arbitration lost */
+ /* tx: retry timeout */
+ /* rx: ready 1 */
+ regmap_update_bits(adv7511->regmap,
+ ADV7511_REG_INT_ENABLE(1), 0x3f,
+ ADV7511_INT1_CEC_MASK);
+ } else if (adv7511->cec_enabled_adap && !enable) {
+ regmap_update_bits(adv7511->regmap,
+ ADV7511_REG_INT_ENABLE(1), 0x3f, 0);
+ /* disable address mask 1-3 */
+ regmap_update_bits(adv7511->regmap_cec,
+ ADV7511_REG_CEC_LOG_ADDR_MASK + offset,
+ 0x70, 0x00);
+ /* power down cec section */
+ regmap_update_bits(adv7511->regmap_cec,
+ ADV7511_REG_CEC_CLK_DIV + offset,
+ 0x03, 0x00);
+ adv7511->cec_valid_addrs = 0;
+ }
+ adv7511->cec_enabled_adap = enable;
+ return 0;
+}
+
+static int adv7511_cec_adap_log_addr(struct cec_adapter *adap, u8 addr)
+{
+ struct adv7511 *adv7511 = cec_get_drvdata(adap);
+ unsigned int offset = adv7511->type == ADV7533 ?
+ ADV7533_REG_CEC_OFFSET : 0;
+ unsigned int i, free_idx = ADV7511_MAX_ADDRS;
+
+ if (!adv7511->cec_enabled_adap)
+ return addr == CEC_LOG_ADDR_INVALID ? 0 : -EIO;
+
+ if (addr == CEC_LOG_ADDR_INVALID) {
+ regmap_update_bits(adv7511->regmap_cec,
+ ADV7511_REG_CEC_LOG_ADDR_MASK + offset,
+ 0x70, 0);
+ adv7511->cec_valid_addrs = 0;
+ return 0;
+ }
+
+ for (i = 0; i < ADV7511_MAX_ADDRS; i++) {
+ bool is_valid = adv7511->cec_valid_addrs & (1 << i);
+
+ if (free_idx == ADV7511_MAX_ADDRS && !is_valid)
+ free_idx = i;
+ if (is_valid && adv7511->cec_addr[i] == addr)
+ return 0;
+ }
+ if (i == ADV7511_MAX_ADDRS) {
+ i = free_idx;
+ if (i == ADV7511_MAX_ADDRS)
+ return -ENXIO;
+ }
+ adv7511->cec_addr[i] = addr;
+ adv7511->cec_valid_addrs |= 1 << i;
+
+ switch (i) {
+ case 0:
+ /* enable address mask 0 */
+ regmap_update_bits(adv7511->regmap_cec,
+ ADV7511_REG_CEC_LOG_ADDR_MASK + offset,
+ 0x10, 0x10);
+ /* set address for mask 0 */
+ regmap_update_bits(adv7511->regmap_cec,
+ ADV7511_REG_CEC_LOG_ADDR_0_1 + offset,
+ 0x0f, addr);
+ break;
+ case 1:
+ /* enable address mask 1 */
+ regmap_update_bits(adv7511->regmap_cec,
+ ADV7511_REG_CEC_LOG_ADDR_MASK + offset,
+ 0x20, 0x20);
+ /* set address for mask 1 */
+ regmap_update_bits(adv7511->regmap_cec,
+ ADV7511_REG_CEC_LOG_ADDR_0_1 + offset,
+ 0xf0, addr << 4);
+ break;
+ case 2:
+ /* enable address mask 2 */
+ regmap_update_bits(adv7511->regmap_cec,
+ ADV7511_REG_CEC_LOG_ADDR_MASK + offset,
+ 0x40, 0x40);
+ /* set address for mask 1 */
+ regmap_update_bits(adv7511->regmap_cec,
+ ADV7511_REG_CEC_LOG_ADDR_2 + offset,
+ 0x0f, addr);
+ break;
+ }
+ return 0;
+}
+
+static int adv7511_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
+ u32 signal_free_time, struct cec_msg *msg)
+{
+ struct adv7511 *adv7511 = cec_get_drvdata(adap);
+ unsigned int offset = adv7511->type == ADV7533 ?
+ ADV7533_REG_CEC_OFFSET : 0;
+ u8 len = msg->len;
+ unsigned int i;
+
+ /*
+ * The number of retries is the number of attempts - 1, but retry
+ * at least once. It's not clear if a value of 0 is allowed, so
+ * let's do at least one retry.
+ */
+ regmap_update_bits(adv7511->regmap_cec,
+ ADV7511_REG_CEC_TX_RETRY + offset,
+ 0x70, max(1, attempts - 1) << 4);
+
+ /* blocking, clear cec tx irq status */
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_INT(1), 0x38, 0x38);
+
+ /* write data */
+ for (i = 0; i < len; i++)
+ regmap_write(adv7511->regmap_cec,
+ i + ADV7511_REG_CEC_TX_FRAME_HDR + offset,
+ msg->msg[i]);
+
+ /* set length (data + header) */
+ regmap_write(adv7511->regmap_cec,
+ ADV7511_REG_CEC_TX_FRAME_LEN + offset, len);
+ /* start transmit, enable tx */
+ regmap_write(adv7511->regmap_cec,
+ ADV7511_REG_CEC_TX_ENABLE + offset, 0x01);
+ return 0;
+}
+
+static const struct cec_adap_ops adv7511_cec_adap_ops = {
+ .adap_enable = adv7511_cec_adap_enable,
+ .adap_log_addr = adv7511_cec_adap_log_addr,
+ .adap_transmit = adv7511_cec_adap_transmit,
+};
+
+static int adv7511_cec_parse_dt(struct device *dev, struct adv7511 *adv7511)
+{
+ adv7511->cec_clk = devm_clk_get(dev, "cec");
+ if (IS_ERR(adv7511->cec_clk)) {
+ int ret = PTR_ERR(adv7511->cec_clk);
+
+ adv7511->cec_clk = NULL;
+ return ret;
+ }
+ clk_prepare_enable(adv7511->cec_clk);
+ adv7511->cec_clk_freq = clk_get_rate(adv7511->cec_clk);
+ return 0;
+}
+
+int adv7511_cec_init(struct device *dev, struct adv7511 *adv7511)
+{
+ unsigned int offset = adv7511->type == ADV7533 ?
+ ADV7533_REG_CEC_OFFSET : 0;
+ int ret = adv7511_cec_parse_dt(dev, adv7511);
+
+ if (ret)
+ goto err_cec_parse_dt;
+
+ adv7511->cec_adap = cec_allocate_adapter(&adv7511_cec_adap_ops,
+ adv7511, dev_name(dev), CEC_CAP_DEFAULTS, ADV7511_MAX_ADDRS);
+ if (IS_ERR(adv7511->cec_adap)) {
+ ret = PTR_ERR(adv7511->cec_adap);
+ goto err_cec_alloc;
+ }
+
+ regmap_write(adv7511->regmap, ADV7511_REG_CEC_CTRL + offset, 0);
+ /* cec soft reset */
+ regmap_write(adv7511->regmap_cec,
+ ADV7511_REG_CEC_SOFT_RESET + offset, 0x01);
+ regmap_write(adv7511->regmap_cec,
+ ADV7511_REG_CEC_SOFT_RESET + offset, 0x00);
+
+ /* legacy mode */
+ regmap_write(adv7511->regmap_cec,
+ ADV7511_REG_CEC_RX_BUFFERS + offset, 0x00);
+
+ regmap_write(adv7511->regmap_cec,
+ ADV7511_REG_CEC_CLK_DIV + offset,
+ ((adv7511->cec_clk_freq / 750000) - 1) << 2);
+
+ ret = cec_register_adapter(adv7511->cec_adap, dev);
+ if (ret)
+ goto err_cec_register;
+ return 0;
+
+err_cec_register:
+ cec_delete_adapter(adv7511->cec_adap);
+ adv7511->cec_adap = NULL;
+err_cec_alloc:
+ dev_info(dev, "Initializing CEC failed with error %d, disabling CEC\n",
+ ret);
+err_cec_parse_dt:
+ regmap_write(adv7511->regmap, ADV7511_REG_CEC_CTRL + offset,
+ ADV7511_CEC_CTRL_POWER_DOWN);
+ return ret == -EPROBE_DEFER ? ret : 0;
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Analog Devices ADV7511 HDMI transmitter driver
+ *
+ * Copyright 2012 Analog Devices Inc.
+ */
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/slab.h>
+
+#include <media/cec.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+
+#include "adv7511.h"
+
+/* ADI recommended values for proper operation. */
+static const struct reg_sequence adv7511_fixed_registers[] = {
+ { 0x98, 0x03 },
+ { 0x9a, 0xe0 },
+ { 0x9c, 0x30 },
+ { 0x9d, 0x61 },
+ { 0xa2, 0xa4 },
+ { 0xa3, 0xa4 },
+ { 0xe0, 0xd0 },
+ { 0xf9, 0x00 },
+ { 0x55, 0x02 },
+};
+
+/* -----------------------------------------------------------------------------
+ * Register access
+ */
+
+static const uint8_t adv7511_register_defaults[] = {
+ 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 00 */
+ 0x00, 0x00, 0x01, 0x0e, 0xbc, 0x18, 0x01, 0x13,
+ 0x25, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 10 */
+ 0x46, 0x62, 0x04, 0xa8, 0x00, 0x00, 0x1c, 0x84,
+ 0x1c, 0xbf, 0x04, 0xa8, 0x1e, 0x70, 0x02, 0x1e, /* 20 */
+ 0x00, 0x00, 0x04, 0xa8, 0x08, 0x12, 0x1b, 0xac,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 30 */
+ 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xb0,
+ 0x00, 0x50, 0x90, 0x7e, 0x79, 0x70, 0x00, 0x00, /* 40 */
+ 0x00, 0xa8, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x02, 0x0d, 0x00, 0x00, 0x00, 0x00, /* 50 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 60 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 70 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 80 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, /* 90 */
+ 0x0b, 0x02, 0x00, 0x18, 0x5a, 0x60, 0x00, 0x00,
+ 0x00, 0x00, 0x80, 0x80, 0x08, 0x04, 0x00, 0x00, /* a0 */
+ 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0x14,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* b0 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* c0 */
+ 0x00, 0x03, 0x00, 0x00, 0x02, 0x00, 0x01, 0x04,
+ 0x30, 0xff, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, /* d0 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x01,
+ 0x80, 0x75, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, /* e0 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x75, 0x11, 0x00, /* f0 */
+ 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+static bool adv7511_register_volatile(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case ADV7511_REG_CHIP_REVISION:
+ case ADV7511_REG_SPDIF_FREQ:
+ case ADV7511_REG_CTS_AUTOMATIC1:
+ case ADV7511_REG_CTS_AUTOMATIC2:
+ case ADV7511_REG_VIC_DETECTED:
+ case ADV7511_REG_VIC_SEND:
+ case ADV7511_REG_AUX_VIC_DETECTED:
+ case ADV7511_REG_STATUS:
+ case ADV7511_REG_GC(1):
+ case ADV7511_REG_INT(0):
+ case ADV7511_REG_INT(1):
+ case ADV7511_REG_PLL_STATUS:
+ case ADV7511_REG_AN(0):
+ case ADV7511_REG_AN(1):
+ case ADV7511_REG_AN(2):
+ case ADV7511_REG_AN(3):
+ case ADV7511_REG_AN(4):
+ case ADV7511_REG_AN(5):
+ case ADV7511_REG_AN(6):
+ case ADV7511_REG_AN(7):
+ case ADV7511_REG_HDCP_STATUS:
+ case ADV7511_REG_BCAPS:
+ case ADV7511_REG_BKSV(0):
+ case ADV7511_REG_BKSV(1):
+ case ADV7511_REG_BKSV(2):
+ case ADV7511_REG_BKSV(3):
+ case ADV7511_REG_BKSV(4):
+ case ADV7511_REG_DDC_STATUS:
+ case ADV7511_REG_EDID_READ_CTRL:
+ case ADV7511_REG_BSTATUS(0):
+ case ADV7511_REG_BSTATUS(1):
+ case ADV7511_REG_CHIP_ID_HIGH:
+ case ADV7511_REG_CHIP_ID_LOW:
+ return true;
+ }
+
+ return false;
+}
+
+static const struct regmap_config adv7511_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = 0xff,
+ .cache_type = REGCACHE_RBTREE,
+ .reg_defaults_raw = adv7511_register_defaults,
+ .num_reg_defaults_raw = ARRAY_SIZE(adv7511_register_defaults),
+
+ .volatile_reg = adv7511_register_volatile,
+};
+
+/* -----------------------------------------------------------------------------
+ * Hardware configuration
+ */
+
+static void adv7511_set_colormap(struct adv7511 *adv7511, bool enable,
+ const uint16_t *coeff,
+ unsigned int scaling_factor)
+{
+ unsigned int i;
+
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_CSC_UPPER(1),
+ ADV7511_CSC_UPDATE_MODE, ADV7511_CSC_UPDATE_MODE);
+
+ if (enable) {
+ for (i = 0; i < 12; ++i) {
+ regmap_update_bits(adv7511->regmap,
+ ADV7511_REG_CSC_UPPER(i),
+ 0x1f, coeff[i] >> 8);
+ regmap_write(adv7511->regmap,
+ ADV7511_REG_CSC_LOWER(i),
+ coeff[i] & 0xff);
+ }
+ }
+
+ if (enable)
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_CSC_UPPER(0),
+ 0xe0, 0x80 | (scaling_factor << 5));
+ else
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_CSC_UPPER(0),
+ 0x80, 0x00);
+
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_CSC_UPPER(1),
+ ADV7511_CSC_UPDATE_MODE, 0);
+}
+
+static int adv7511_packet_enable(struct adv7511 *adv7511, unsigned int packet)
+{
+ if (packet & 0xff)
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_PACKET_ENABLE0,
+ packet, 0xff);
+
+ if (packet & 0xff00) {
+ packet >>= 8;
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_PACKET_ENABLE1,
+ packet, 0xff);
+ }
+
+ return 0;
+}
+
+static int adv7511_packet_disable(struct adv7511 *adv7511, unsigned int packet)
+{
+ if (packet & 0xff)
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_PACKET_ENABLE0,
+ packet, 0x00);
+
+ if (packet & 0xff00) {
+ packet >>= 8;
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_PACKET_ENABLE1,
+ packet, 0x00);
+ }
+
+ return 0;
+}
+
+/* Coefficients for adv7511 color space conversion */
+static const uint16_t adv7511_csc_ycbcr_to_rgb[] = {
+ 0x0734, 0x04ad, 0x0000, 0x1c1b,
+ 0x1ddc, 0x04ad, 0x1f24, 0x0135,
+ 0x0000, 0x04ad, 0x087c, 0x1b77,
+};
+
+static void adv7511_set_config_csc(struct adv7511 *adv7511,
+ struct drm_connector *connector,
+ bool rgb, bool hdmi_mode)
+{
+ struct adv7511_video_config config;
+ bool output_format_422, output_format_ycbcr;
+ unsigned int mode;
+ uint8_t infoframe[17];
+
+ config.hdmi_mode = hdmi_mode;
+
+ hdmi_avi_infoframe_init(&config.avi_infoframe);
+
+ config.avi_infoframe.scan_mode = HDMI_SCAN_MODE_UNDERSCAN;
+
+ if (rgb) {
+ config.csc_enable = false;
+ config.avi_infoframe.colorspace = HDMI_COLORSPACE_RGB;
+ } else {
+ config.csc_scaling_factor = ADV7511_CSC_SCALING_4;
+ config.csc_coefficents = adv7511_csc_ycbcr_to_rgb;
+
+ if ((connector->display_info.color_formats &
+ DRM_COLOR_FORMAT_YCRCB422) &&
+ config.hdmi_mode) {
+ config.csc_enable = false;
+ config.avi_infoframe.colorspace =
+ HDMI_COLORSPACE_YUV422;
+ } else {
+ config.csc_enable = true;
+ config.avi_infoframe.colorspace = HDMI_COLORSPACE_RGB;
+ }
+ }
+
+ if (config.hdmi_mode) {
+ mode = ADV7511_HDMI_CFG_MODE_HDMI;
+
+ switch (config.avi_infoframe.colorspace) {
+ case HDMI_COLORSPACE_YUV444:
+ output_format_422 = false;
+ output_format_ycbcr = true;
+ break;
+ case HDMI_COLORSPACE_YUV422:
+ output_format_422 = true;
+ output_format_ycbcr = true;
+ break;
+ default:
+ output_format_422 = false;
+ output_format_ycbcr = false;
+ break;
+ }
+ } else {
+ mode = ADV7511_HDMI_CFG_MODE_DVI;
+ output_format_422 = false;
+ output_format_ycbcr = false;
+ }
+
+ adv7511_packet_disable(adv7511, ADV7511_PACKET_ENABLE_AVI_INFOFRAME);
+
+ adv7511_set_colormap(adv7511, config.csc_enable,
+ config.csc_coefficents,
+ config.csc_scaling_factor);
+
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_VIDEO_INPUT_CFG1, 0x81,
+ (output_format_422 << 7) | output_format_ycbcr);
+
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_HDCP_HDMI_CFG,
+ ADV7511_HDMI_CFG_MODE_MASK, mode);
+
+ hdmi_avi_infoframe_pack(&config.avi_infoframe, infoframe,
+ sizeof(infoframe));
+
+ /* The AVI infoframe id is not configurable */
+ regmap_bulk_write(adv7511->regmap, ADV7511_REG_AVI_INFOFRAME_VERSION,
+ infoframe + 1, sizeof(infoframe) - 1);
+
+ adv7511_packet_enable(adv7511, ADV7511_PACKET_ENABLE_AVI_INFOFRAME);
+}
+
+static void adv7511_set_link_config(struct adv7511 *adv7511,
+ const struct adv7511_link_config *config)
+{
+ /*
+ * The input style values documented in the datasheet don't match the
+ * hardware register field values :-(
+ */
+ static const unsigned int input_styles[4] = { 0, 2, 1, 3 };
+
+ unsigned int clock_delay;
+ unsigned int color_depth;
+ unsigned int input_id;
+
+ clock_delay = (config->clock_delay + 1200) / 400;
+ color_depth = config->input_color_depth == 8 ? 3
+ : (config->input_color_depth == 10 ? 1 : 2);
+
+ /* TODO Support input ID 6 */
+ if (config->input_colorspace != HDMI_COLORSPACE_YUV422)
+ input_id = config->input_clock == ADV7511_INPUT_CLOCK_DDR
+ ? 5 : 0;
+ else if (config->input_clock == ADV7511_INPUT_CLOCK_DDR)
+ input_id = config->embedded_sync ? 8 : 7;
+ else if (config->input_clock == ADV7511_INPUT_CLOCK_2X)
+ input_id = config->embedded_sync ? 4 : 3;
+ else
+ input_id = config->embedded_sync ? 2 : 1;
+
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_I2C_FREQ_ID_CFG, 0xf,
+ input_id);
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_VIDEO_INPUT_CFG1, 0x7e,
+ (color_depth << 4) |
+ (input_styles[config->input_style] << 2));
+ regmap_write(adv7511->regmap, ADV7511_REG_VIDEO_INPUT_CFG2,
+ config->input_justification << 3);
+ regmap_write(adv7511->regmap, ADV7511_REG_TIMING_GEN_SEQ,
+ config->sync_pulse << 2);
+
+ regmap_write(adv7511->regmap, 0xba, clock_delay << 5);
+
+ adv7511->embedded_sync = config->embedded_sync;
+ adv7511->hsync_polarity = config->hsync_polarity;
+ adv7511->vsync_polarity = config->vsync_polarity;
+ adv7511->rgb = config->input_colorspace == HDMI_COLORSPACE_RGB;
+}
+
+static void __adv7511_power_on(struct adv7511 *adv7511)
+{
+ adv7511->current_edid_segment = -1;
+
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER,
+ ADV7511_POWER_POWER_DOWN, 0);
+ if (adv7511->i2c_main->irq) {
+ /*
+ * Documentation says the INT_ENABLE registers are reset in
+ * POWER_DOWN mode. My 7511w preserved the bits, however.
+ * Still, let's be safe and stick to the documentation.
+ */
+ regmap_write(adv7511->regmap, ADV7511_REG_INT_ENABLE(0),
+ ADV7511_INT0_EDID_READY | ADV7511_INT0_HPD);
+ regmap_update_bits(adv7511->regmap,
+ ADV7511_REG_INT_ENABLE(1),
+ ADV7511_INT1_DDC_ERROR,
+ ADV7511_INT1_DDC_ERROR);
+ }
+
+ /*
+ * 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
+ * reset and the outputs are disabled which might cause the monitor to
+ * go to standby again. To avoid this we ignore the HPD pin for the
+ * first few seconds after enabling the output.
+ */
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER2,
+ ADV7511_REG_POWER2_HPD_SRC_MASK,
+ ADV7511_REG_POWER2_HPD_SRC_NONE);
+}
+
+static void adv7511_power_on(struct adv7511 *adv7511)
+{
+ __adv7511_power_on(adv7511);
+
+ /*
+ * Most of the registers are reset during power down or when HPD is low.
+ */
+ regcache_sync(adv7511->regmap);
+
+ if (adv7511->type == ADV7533 || adv7511->type == ADV7535)
+ adv7533_dsi_power_on(adv7511);
+ adv7511->powered = true;
+}
+
+static void __adv7511_power_off(struct adv7511 *adv7511)
+{
+ /* TODO: setup additional power down modes */
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER,
+ ADV7511_POWER_POWER_DOWN,
+ ADV7511_POWER_POWER_DOWN);
+ regmap_update_bits(adv7511->regmap,
+ ADV7511_REG_INT_ENABLE(1),
+ ADV7511_INT1_DDC_ERROR, 0);
+ regcache_mark_dirty(adv7511->regmap);
+}
+
+static void adv7511_power_off(struct adv7511 *adv7511)
+{
+ __adv7511_power_off(adv7511);
+ if (adv7511->type == ADV7533 || adv7511->type == ADV7535)
+ adv7533_dsi_power_off(adv7511);
+ adv7511->powered = false;
+}
+
+/* -----------------------------------------------------------------------------
+ * Interrupt and hotplug detection
+ */
+
+static bool adv7511_hpd(struct adv7511 *adv7511)
+{
+ unsigned int irq0;
+ int ret;
+
+ ret = regmap_read(adv7511->regmap, ADV7511_REG_INT(0), &irq0);
+ if (ret < 0)
+ return false;
+
+ if (irq0 & ADV7511_INT0_HPD) {
+ regmap_write(adv7511->regmap, ADV7511_REG_INT(0),
+ ADV7511_INT0_HPD);
+ return true;
+ }
+
+ return false;
+}
+
+static void adv7511_hpd_work(struct work_struct *work)
+{
+ struct adv7511 *adv7511 = container_of(work, struct adv7511, hpd_work);
+ enum drm_connector_status status;
+ 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)
+ status = connector_status_connected;
+ else
+ status = connector_status_disconnected;
+
+ /*
+ * The bridge resets its registers on unplug. So when we get a plug
+ * event and we're already supposed to be powered, cycle the bridge to
+ * restore its state.
+ */
+ if (status == connector_status_connected &&
+ adv7511->connector.status == connector_status_disconnected &&
+ adv7511->powered) {
+ regcache_mark_dirty(adv7511->regmap);
+ adv7511_power_on(adv7511);
+ }
+
+ if (adv7511->connector.status != status) {
+ adv7511->connector.status = status;
+
+ if (adv7511->connector.dev) {
+ if (status == connector_status_disconnected)
+ cec_phys_addr_invalidate(adv7511->cec_adap);
+ drm_kms_helper_hotplug_event(adv7511->connector.dev);
+ } else {
+ drm_bridge_hpd_notify(&adv7511->bridge, status);
+ }
+ }
+}
+
+static int adv7511_irq_process(struct adv7511 *adv7511, bool process_hpd)
+{
+ unsigned int irq0, irq1;
+ int ret;
+
+ ret = regmap_read(adv7511->regmap, ADV7511_REG_INT(0), &irq0);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_read(adv7511->regmap, ADV7511_REG_INT(1), &irq1);
+ if (ret < 0)
+ return ret;
+
+ regmap_write(adv7511->regmap, ADV7511_REG_INT(0), irq0);
+ regmap_write(adv7511->regmap, ADV7511_REG_INT(1), irq1);
+
+ if (process_hpd && irq0 & ADV7511_INT0_HPD && adv7511->bridge.encoder)
+ schedule_work(&adv7511->hpd_work);
+
+ if (irq0 & ADV7511_INT0_EDID_READY || irq1 & ADV7511_INT1_DDC_ERROR) {
+ adv7511->edid_read = true;
+
+ if (adv7511->i2c_main->irq)
+ wake_up_all(&adv7511->wq);
+ }
+
+#ifdef CONFIG_DRM_I2C_ADV7511_CEC
+ adv7511_cec_irq_process(adv7511, irq1);
+#endif
+
+ return 0;
+}
+
+static irqreturn_t adv7511_irq_handler(int irq, void *devid)
+{
+ struct adv7511 *adv7511 = devid;
+ int ret;
+
+ ret = adv7511_irq_process(adv7511, true);
+ return ret < 0 ? IRQ_NONE : IRQ_HANDLED;
+}
+
+/* -----------------------------------------------------------------------------
+ * EDID retrieval
+ */
+
+static int adv7511_wait_for_edid(struct adv7511 *adv7511, int timeout)
+{
+ int ret;
+
+ if (adv7511->i2c_main->irq) {
+ ret = wait_event_interruptible_timeout(adv7511->wq,
+ adv7511->edid_read, msecs_to_jiffies(timeout));
+ } else {
+ for (; timeout > 0; timeout -= 25) {
+ ret = adv7511_irq_process(adv7511, false);
+ if (ret < 0)
+ break;
+
+ if (adv7511->edid_read)
+ break;
+
+ msleep(25);
+ }
+ }
+
+ return adv7511->edid_read ? 0 : -EIO;
+}
+
+static int adv7511_get_edid_block(void *data, u8 *buf, unsigned int block,
+ size_t len)
+{
+ struct adv7511 *adv7511 = data;
+ struct i2c_msg xfer[2];
+ uint8_t offset;
+ unsigned int i;
+ int ret;
+
+ if (len > 128)
+ return -EINVAL;
+
+ if (adv7511->current_edid_segment != block / 2) {
+ unsigned int status;
+
+ ret = regmap_read(adv7511->regmap, ADV7511_REG_DDC_STATUS,
+ &status);
+ if (ret < 0)
+ return ret;
+
+ if (status != 2) {
+ adv7511->edid_read = false;
+ regmap_write(adv7511->regmap, ADV7511_REG_EDID_SEGMENT,
+ block);
+ ret = adv7511_wait_for_edid(adv7511, 200);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* Break this apart, hopefully more I2C controllers will
+ * support 64 byte transfers than 256 byte transfers
+ */
+
+ xfer[0].addr = adv7511->i2c_edid->addr;
+ xfer[0].flags = 0;
+ xfer[0].len = 1;
+ xfer[0].buf = &offset;
+ xfer[1].addr = adv7511->i2c_edid->addr;
+ xfer[1].flags = I2C_M_RD;
+ xfer[1].len = 64;
+ xfer[1].buf = adv7511->edid_buf;
+
+ offset = 0;
+
+ for (i = 0; i < 4; ++i) {
+ ret = i2c_transfer(adv7511->i2c_edid->adapter, xfer,
+ ARRAY_SIZE(xfer));
+ if (ret < 0)
+ return ret;
+ else if (ret != 2)
+ return -EIO;
+
+ xfer[1].buf += 64;
+ offset += 64;
+ }
+
+ adv7511->current_edid_segment = block / 2;
+ }
+
+ if (block % 2 == 0)
+ memcpy(buf, adv7511->edid_buf, len);
+ else
+ memcpy(buf, adv7511->edid_buf + 128, len);
+
+ return 0;
+}
+
+/* -----------------------------------------------------------------------------
+ * ADV75xx helpers
+ */
+
+static struct edid *adv7511_get_edid(struct adv7511 *adv7511,
+ struct drm_connector *connector)
+{
+ struct edid *edid;
+
+ /* Reading the EDID only works if the device is powered */
+ if (!adv7511->powered) {
+ unsigned int edid_i2c_addr =
+ (adv7511->i2c_edid->addr << 1);
+
+ __adv7511_power_on(adv7511);
+
+ /* Reset the EDID_I2C_ADDR register as it might be cleared */
+ regmap_write(adv7511->regmap, ADV7511_REG_EDID_I2C_ADDR,
+ edid_i2c_addr);
+ }
+
+ edid = drm_do_get_edid(connector, adv7511_get_edid_block, adv7511);
+
+ if (!adv7511->powered)
+ __adv7511_power_off(adv7511);
+
+ adv7511_set_config_csc(adv7511, connector, adv7511->rgb,
+ drm_detect_hdmi_monitor(edid));
+
+ cec_s_phys_addr_from_edid(adv7511->cec_adap, edid);
+
+ return edid;
+}
+
+static int adv7511_get_modes(struct adv7511 *adv7511,
+ struct drm_connector *connector)
+{
+ struct edid *edid;
+ unsigned int count;
+
+ edid = adv7511_get_edid(adv7511, connector);
+
+ drm_connector_update_edid_property(connector, edid);
+ count = drm_add_edid_modes(connector, edid);
+
+ kfree(edid);
+
+ return count;
+}
+
+static enum drm_connector_status
+adv7511_detect(struct adv7511 *adv7511, struct drm_connector *connector)
+{
+ enum drm_connector_status status;
+ unsigned int val;
+ bool hpd;
+ int ret;
+
+ ret = regmap_read(adv7511->regmap, ADV7511_REG_STATUS, &val);
+ if (ret < 0)
+ return connector_status_disconnected;
+
+ if (val & ADV7511_STATUS_HPD)
+ status = connector_status_connected;
+ else
+ status = connector_status_disconnected;
+
+ hpd = adv7511_hpd(adv7511);
+
+ /* The chip resets itself when the cable is disconnected, so in case
+ * there is a pending HPD interrupt and the cable is connected there was
+ * at least one transition from disconnected to connected and the chip
+ * has to be reinitialized. */
+ if (status == connector_status_connected && hpd && adv7511->powered) {
+ regcache_mark_dirty(adv7511->regmap);
+ adv7511_power_on(adv7511);
+ if (connector)
+ adv7511_get_modes(adv7511, connector);
+ if (adv7511->status == connector_status_connected)
+ status = connector_status_disconnected;
+ } else {
+ /* Renable HPD sensing */
+ regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER2,
+ ADV7511_REG_POWER2_HPD_SRC_MASK,
+ ADV7511_REG_POWER2_HPD_SRC_BOTH);
+ }
+
+ adv7511->status = status;
+ return status;
+}
+
+static enum drm_mode_status adv7511_mode_valid(struct adv7511 *adv7511,
+ struct drm_display_mode *mode)
+{
+ if (mode->clock > 165000)
+ return MODE_CLOCK_HIGH;
+
+ return MODE_OK;
+}
+
+static void adv7511_mode_set(struct adv7511 *adv7511,
+ const struct drm_display_mode *mode,
+ const struct drm_display_mode *adj_mode)
+{
+ unsigned int low_refresh_rate;
+ unsigned int hsync_polarity = 0;
+ unsigned int vsync_polarity = 0;
+
+ if (adv7511->embedded_sync) {
+ unsigned int hsync_offset, hsync_len;
+ unsigned int vsync_offset, vsync_len;
+
+ hsync_offset = adj_mode->crtc_hsync_start -
+ adj_mode->crtc_hdisplay;
+ vsync_offset = adj_mode->crtc_vsync_start -
+ adj_mode->crtc_vdisplay;
+ hsync_len = adj_mode->crtc_hsync_end -
+ adj_mode->crtc_hsync_start;
+ vsync_len = adj_mode->crtc_vsync_end -
+ adj_mode->crtc_vsync_start;
+
+ /* The hardware vsync generator has a off-by-one bug */
+ vsync_offset += 1;
+
+ regmap_write(adv7511->regmap, ADV7511_REG_HSYNC_PLACEMENT_MSB,
+ ((hsync_offset >> 10) & 0x7) << 5);
+ regmap_write(adv7511->regmap, ADV7511_REG_SYNC_DECODER(0),
+ (hsync_offset >> 2) & 0xff);
+ regmap_write(adv7511->regmap, ADV7511_REG_SYNC_DECODER(1),
+ ((hsync_offset & 0x3) << 6) |
+ ((hsync_len >> 4) & 0x3f));
+ regmap_write(adv7511->regmap, ADV7511_REG_SYNC_DECODER(2),
+ ((hsync_len & 0xf) << 4) |
+ ((vsync_offset >> 6) & 0xf));
+ regmap_write(adv7511->regmap, ADV7511_REG_SYNC_DECODER(3),
+ ((vsync_offset & 0x3f) << 2) |
+ ((vsync_len >> 8) & 0x3));
+ regmap_write(adv7511->regmap, ADV7511_REG_SYNC_DECODER(4),
+ vsync_len & 0xff);
+
+ hsync_polarity = !(adj_mode->flags & DRM_MODE_FLAG_PHSYNC);
+ vsync_polarity = !(adj_mode->flags & DRM_MODE_FLAG_PVSYNC);
+ } else {
+ enum adv7511_sync_polarity mode_hsync_polarity;
+ enum adv7511_sync_polarity mode_vsync_polarity;
+
+ /**
+ * If the input signal is always low or always high we want to
+ * invert or let it passthrough depending on the polarity of the
+ * current mode.
+ **/
+ if (adj_mode->flags & DRM_MODE_FLAG_NHSYNC)
+ mode_hsync_polarity = ADV7511_SYNC_POLARITY_LOW;
+ else
+ mode_hsync_polarity = ADV7511_SYNC_POLARITY_HIGH;
+
+ if (adj_mode->flags & DRM_MODE_FLAG_NVSYNC)
+ mode_vsync_polarity = ADV7511_SYNC_POLARITY_LOW;
+ else
+ mode_vsync_polarity = ADV7511_SYNC_POLARITY_HIGH;
+
+ if (adv7511->hsync_polarity != mode_hsync_polarity &&
+ adv7511->hsync_polarity !=
+ ADV7511_SYNC_POLARITY_PASSTHROUGH)
+ hsync_polarity = 1;
+
+ if (adv7511->vsync_polarity != mode_vsync_polarity &&
+ adv7511->vsync_polarity !=
+ ADV7511_SYNC_POLARITY_PASSTHROUGH)
+ vsync_polarity = 1;
+ }
+
+ if (drm_mode_vrefresh(mode) <= 24)
+ low_refresh_rate = ADV7511_LOW_REFRESH_RATE_24HZ;
+ else if (drm_mode_vrefresh(mode) <= 25)
+ low_refresh_rate = ADV7511_LOW_REFRESH_RATE_25HZ;
+ else if (drm_mode_vrefresh(mode) <= 30)
+ low_refresh_rate = ADV7511_LOW_REFRESH_RATE_30HZ;
+ else
+ low_refresh_rate = ADV7511_LOW_REFRESH_RATE_NONE;
+
+ regmap_update_bits(adv7511->regmap, 0xfb,
+ 0x6, low_refresh_rate << 1);
+ regmap_update_bits(adv7511->regmap, 0x17,
+ 0x60, (vsync_polarity << 6) | (hsync_polarity << 5));
+
+ if (adv7511->type == ADV7533 || adv7511->type == ADV7535)
+ adv7533_mode_set(adv7511, adj_mode);
+
+ drm_mode_copy(&adv7511->curr_mode, adj_mode);
+
+ /*
+ * TODO Test first order 4:2:2 to 4:4:4 up conversion method, which is
+ * supposed to give better results.
+ */
+
+ adv7511->f_tmds = mode->clock;
+}
+
+/* -----------------------------------------------------------------------------
+ * DRM Connector Operations
+ */
+
+static struct adv7511 *connector_to_adv7511(struct drm_connector *connector)
+{
+ return container_of(connector, struct adv7511, connector);
+}
+
+static int adv7511_connector_get_modes(struct drm_connector *connector)
+{
+ struct adv7511 *adv = connector_to_adv7511(connector);
+
+ return adv7511_get_modes(adv, connector);
+}
+
+static enum drm_mode_status
+adv7511_connector_mode_valid(struct drm_connector *connector,
+ struct drm_display_mode *mode)
+{
+ struct adv7511 *adv = connector_to_adv7511(connector);
+
+ return adv7511_mode_valid(adv, mode);
+}
+
+static struct drm_connector_helper_funcs adv7511_connector_helper_funcs = {
+ .get_modes = adv7511_connector_get_modes,
+ .mode_valid = adv7511_connector_mode_valid,
+};
+
+static enum drm_connector_status
+adv7511_connector_detect(struct drm_connector *connector, bool force)
+{
+ struct adv7511 *adv = connector_to_adv7511(connector);
+
+ return adv7511_detect(adv, connector);
+}
+
+static const struct drm_connector_funcs adv7511_connector_funcs = {
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .detect = adv7511_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 int adv7511_connector_init(struct adv7511 *adv)
+{
+ struct drm_bridge *bridge = &adv->bridge;
+ int ret;
+
+ if (!bridge->encoder) {
+ DRM_ERROR("Parent encoder object not found");
+ return -ENODEV;
+ }
+
+ if (adv->i2c_main->irq)
+ adv->connector.polled = DRM_CONNECTOR_POLL_HPD;
+ else
+ adv->connector.polled = DRM_CONNECTOR_POLL_CONNECT |
+ DRM_CONNECTOR_POLL_DISCONNECT;
+
+ ret = drm_connector_init(bridge->dev, &adv->connector,
+ &adv7511_connector_funcs,
+ DRM_MODE_CONNECTOR_HDMIA);
+ if (ret < 0) {
+ DRM_ERROR("Failed to initialize connector with drm\n");
+ return ret;
+ }
+ drm_connector_helper_add(&adv->connector,
+ &adv7511_connector_helper_funcs);
+ drm_connector_attach_encoder(&adv->connector, bridge->encoder);
+
+ return 0;
+}
+
+/* -----------------------------------------------------------------------------
+ * DRM Bridge Operations
+ */
+
+static struct adv7511 *bridge_to_adv7511(struct drm_bridge *bridge)
+{
+ return container_of(bridge, struct adv7511, bridge);
+}
+
+static void adv7511_bridge_enable(struct drm_bridge *bridge)
+{
+ struct adv7511 *adv = bridge_to_adv7511(bridge);
+
+ adv7511_power_on(adv);
+}
+
+static void adv7511_bridge_disable(struct drm_bridge *bridge)
+{
+ struct adv7511 *adv = bridge_to_adv7511(bridge);
+
+ adv7511_power_off(adv);
+}
+
+static void adv7511_bridge_mode_set(struct drm_bridge *bridge,
+ const struct drm_display_mode *mode,
+ const struct drm_display_mode *adj_mode)
+{
+ struct adv7511 *adv = bridge_to_adv7511(bridge);
+
+ adv7511_mode_set(adv, mode, adj_mode);
+}
+
+static int adv7511_bridge_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
+{
+ struct adv7511 *adv = bridge_to_adv7511(bridge);
+ int ret = 0;
+
+ if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) {
+ ret = adv7511_connector_init(adv);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (adv->type == ADV7533 || adv->type == ADV7535)
+ ret = adv7533_attach_dsi(adv);
+
+ if (adv->i2c_main->irq)
+ regmap_write(adv->regmap, ADV7511_REG_INT_ENABLE(0),
+ ADV7511_INT0_HPD);
+
+ return ret;
+}
+
+static enum drm_connector_status adv7511_bridge_detect(struct drm_bridge *bridge)
+{
+ struct adv7511 *adv = bridge_to_adv7511(bridge);
+
+ return adv7511_detect(adv, NULL);
+}
+
+static struct edid *adv7511_bridge_get_edid(struct drm_bridge *bridge,
+ struct drm_connector *connector)
+{
+ struct adv7511 *adv = bridge_to_adv7511(bridge);
+
+ return adv7511_get_edid(adv, connector);
+}
+
+static void adv7511_bridge_hpd_notify(struct drm_bridge *bridge,
+ enum drm_connector_status status)
+{
+ struct adv7511 *adv = bridge_to_adv7511(bridge);
+
+ if (status == connector_status_disconnected)
+ cec_phys_addr_invalidate(adv->cec_adap);
+}
+
+static const struct drm_bridge_funcs adv7511_bridge_funcs = {
+ .enable = adv7511_bridge_enable,
+ .disable = adv7511_bridge_disable,
+ .mode_set = adv7511_bridge_mode_set,
+ .attach = adv7511_bridge_attach,
+ .detect = adv7511_bridge_detect,
+ .get_edid = adv7511_bridge_get_edid,
+ .hpd_notify = adv7511_bridge_hpd_notify,
+};
+
+/* -----------------------------------------------------------------------------
+ * Probe & remove
+ */
+
+static const char * const adv7511_supply_names[] = {
+ "avdd",
+ "dvdd",
+ "pvdd",
+ "bgvdd",
+ "dvdd-3v",
+};
+
+static const char * const adv7533_supply_names[] = {
+ "avdd",
+ "dvdd",
+ "pvdd",
+ "a2vdd",
+ "v3p3",
+ "v1p2",
+};
+
+static int adv7511_init_regulators(struct adv7511 *adv)
+{
+ struct device *dev = &adv->i2c_main->dev;
+ const char * const *supply_names;
+ unsigned int i;
+ int ret;
+
+ if (adv->type == ADV7511) {
+ supply_names = adv7511_supply_names;
+ adv->num_supplies = ARRAY_SIZE(adv7511_supply_names);
+ } else {
+ supply_names = adv7533_supply_names;
+ adv->num_supplies = ARRAY_SIZE(adv7533_supply_names);
+ }
+
+ adv->supplies = devm_kcalloc(dev, adv->num_supplies,
+ sizeof(*adv->supplies), GFP_KERNEL);
+ if (!adv->supplies)
+ return -ENOMEM;
+
+ for (i = 0; i < adv->num_supplies; i++)
+ adv->supplies[i].supply = supply_names[i];
+
+ ret = devm_regulator_bulk_get(dev, adv->num_supplies, adv->supplies);
+ if (ret)
+ return ret;
+
+ return regulator_bulk_enable(adv->num_supplies, adv->supplies);
+}
+
+static void adv7511_uninit_regulators(struct adv7511 *adv)
+{
+ regulator_bulk_disable(adv->num_supplies, adv->supplies);
+}
+
+static bool adv7511_cec_register_volatile(struct device *dev, unsigned int reg)
+{
+ struct i2c_client *i2c = to_i2c_client(dev);
+ struct adv7511 *adv7511 = i2c_get_clientdata(i2c);
+
+ if (adv7511->type == ADV7533 || adv7511->type == ADV7535)
+ reg -= ADV7533_REG_CEC_OFFSET;
+
+ switch (reg) {
+ case ADV7511_REG_CEC_RX_FRAME_HDR:
+ case ADV7511_REG_CEC_RX_FRAME_DATA0...
+ ADV7511_REG_CEC_RX_FRAME_DATA0 + 14:
+ case ADV7511_REG_CEC_RX_FRAME_LEN:
+ case ADV7511_REG_CEC_RX_BUFFERS:
+ case ADV7511_REG_CEC_TX_LOW_DRV_CNT:
+ return true;
+ }
+
+ return false;
+}
+
+static const struct regmap_config adv7511_cec_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = 0xff,
+ .cache_type = REGCACHE_RBTREE,
+ .volatile_reg = adv7511_cec_register_volatile,
+};
+
+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);
+ if (IS_ERR(adv->i2c_cec))
+ return PTR_ERR(adv->i2c_cec);
+ i2c_set_clientdata(adv->i2c_cec, adv);
+
+ adv->regmap_cec = devm_regmap_init_i2c(adv->i2c_cec,
+ &adv7511_cec_regmap_config);
+ if (IS_ERR(adv->regmap_cec)) {
+ ret = PTR_ERR(adv->regmap_cec);
+ goto err;
+ }
+
+ if (adv->type == ADV7533 || adv->type == ADV7535) {
+ ret = adv7533_patch_cec_registers(adv);
+ if (ret)
+ goto err;
+ }
+
+ return 0;
+err:
+ i2c_unregister_device(adv->i2c_cec);
+ return ret;
+}
+
+static int adv7511_parse_dt(struct device_node *np,
+ struct adv7511_link_config *config)
+{
+ const char *str;
+ int ret;
+
+ of_property_read_u32(np, "adi,input-depth", &config->input_color_depth);
+ if (config->input_color_depth != 8 && config->input_color_depth != 10 &&
+ config->input_color_depth != 12)
+ return -EINVAL;
+
+ ret = of_property_read_string(np, "adi,input-colorspace", &str);
+ if (ret < 0)
+ return ret;
+
+ if (!strcmp(str, "rgb"))
+ config->input_colorspace = HDMI_COLORSPACE_RGB;
+ else if (!strcmp(str, "yuv422"))
+ config->input_colorspace = HDMI_COLORSPACE_YUV422;
+ else if (!strcmp(str, "yuv444"))
+ config->input_colorspace = HDMI_COLORSPACE_YUV444;
+ else
+ return -EINVAL;
+
+ ret = of_property_read_string(np, "adi,input-clock", &str);
+ if (ret < 0)
+ return ret;
+
+ if (!strcmp(str, "1x"))
+ config->input_clock = ADV7511_INPUT_CLOCK_1X;
+ else if (!strcmp(str, "2x"))
+ config->input_clock = ADV7511_INPUT_CLOCK_2X;
+ else if (!strcmp(str, "ddr"))
+ config->input_clock = ADV7511_INPUT_CLOCK_DDR;
+ else
+ return -EINVAL;
+
+ if (config->input_colorspace == HDMI_COLORSPACE_YUV422 ||
+ config->input_clock != ADV7511_INPUT_CLOCK_1X) {
+ ret = of_property_read_u32(np, "adi,input-style",
+ &config->input_style);
+ if (ret)
+ return ret;
+
+ if (config->input_style < 1 || config->input_style > 3)
+ return -EINVAL;
+
+ ret = of_property_read_string(np, "adi,input-justification",
+ &str);
+ if (ret < 0)
+ return ret;
+
+ if (!strcmp(str, "left"))
+ config->input_justification =
+ ADV7511_INPUT_JUSTIFICATION_LEFT;
+ else if (!strcmp(str, "evenly"))
+ config->input_justification =
+ ADV7511_INPUT_JUSTIFICATION_EVENLY;
+ else if (!strcmp(str, "right"))
+ config->input_justification =
+ ADV7511_INPUT_JUSTIFICATION_RIGHT;
+ else
+ return -EINVAL;
+
+ } else {
+ config->input_style = 1;
+ config->input_justification = ADV7511_INPUT_JUSTIFICATION_LEFT;
+ }
+
+ of_property_read_u32(np, "adi,clock-delay", &config->clock_delay);
+ if (config->clock_delay < -1200 || config->clock_delay > 1600)
+ return -EINVAL;
+
+ config->embedded_sync = of_property_read_bool(np, "adi,embedded-sync");
+
+ /* Hardcode the sync pulse configurations for now. */
+ config->sync_pulse = ADV7511_INPUT_SYNC_PULSE_NONE;
+ config->vsync_polarity = ADV7511_SYNC_POLARITY_PASSTHROUGH;
+ config->hsync_polarity = ADV7511_SYNC_POLARITY_PASSTHROUGH;
+
+ return 0;
+}
+
+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;
+ unsigned int val;
+ int ret;
+ if (!dev->of_node)
+ return -EINVAL;
+
+ adv7511 = devm_kzalloc(dev, sizeof(*adv7511), GFP_KERNEL);
+ if (!adv7511)
+ return -ENOMEM;
+
+ adv7511->i2c_main = i2c;
+ adv7511->powered = false;
+ adv7511->status = connector_status_disconnected;
+
+ if (dev->of_node)
+ adv7511->type = (enum adv7511_type)of_device_get_match_data(dev);
+ else
+ adv7511->type = id->driver_data;
+
+ memset(&link_config, 0, sizeof(link_config));
+
+ if (adv7511->type == ADV7511)
+ ret = adv7511_parse_dt(dev->of_node, &link_config);
+ else
+ ret = adv7533_parse_dt(dev->of_node, adv7511);
+ if (ret)
+ return ret;
+
+ ret = adv7511_init_regulators(adv7511);
+ if (ret) {
+ dev_err(dev, "failed to init regulators\n");
+ return ret;
+ }
+
+ /*
+ * The power down GPIO is optional. If present, toggle it from active to
+ * inactive to wake up the encoder.
+ */
+ adv7511->gpio_pd = devm_gpiod_get_optional(dev, "pd", GPIOD_OUT_HIGH);
+ if (IS_ERR(adv7511->gpio_pd)) {
+ ret = PTR_ERR(adv7511->gpio_pd);
+ goto uninit_regulators;
+ }
+
+ if (adv7511->gpio_pd) {
+ usleep_range(5000, 6000);
+ gpiod_set_value_cansleep(adv7511->gpio_pd, 0);
+ }
+
+ adv7511->regmap = devm_regmap_init_i2c(i2c, &adv7511_regmap_config);
+ if (IS_ERR(adv7511->regmap)) {
+ ret = PTR_ERR(adv7511->regmap);
+ goto uninit_regulators;
+ }
+
+ ret = regmap_read(adv7511->regmap, ADV7511_REG_CHIP_REVISION, &val);
+ if (ret)
+ goto uninit_regulators;
+ dev_dbg(dev, "Rev. %d\n", val);
+
+ if (adv7511->type == ADV7511)
+ ret = regmap_register_patch(adv7511->regmap,
+ adv7511_fixed_registers,
+ ARRAY_SIZE(adv7511_fixed_registers));
+ else
+ ret = adv7533_patch_registers(adv7511);
+ if (ret)
+ goto uninit_regulators;
+
+ adv7511_packet_disable(adv7511, 0xffff);
+
+ adv7511->i2c_edid = i2c_new_ancillary_device(i2c, "edid",
+ ADV7511_EDID_I2C_ADDR_DEFAULT);
+ 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);
+
+ adv7511->i2c_packet = i2c_new_ancillary_device(i2c, "packet",
+ ADV7511_PACKET_I2C_ADDR_DEFAULT);
+ 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);
+
+ ret = adv7511_init_cec_regmap(adv7511);
+ if (ret)
+ goto err_i2c_unregister_packet;
+
+ regmap_write(adv7511->regmap, ADV7511_REG_CEC_I2C_ADDR,
+ adv7511->i2c_cec->addr << 1);
+
+ INIT_WORK(&adv7511->hpd_work, adv7511_hpd_work);
+
+ if (i2c->irq) {
+ init_waitqueue_head(&adv7511->wq);
+
+ ret = devm_request_threaded_irq(dev, i2c->irq, NULL,
+ adv7511_irq_handler,
+ IRQF_ONESHOT, dev_name(dev),
+ adv7511);
+ if (ret)
+ goto err_unregister_cec;
+ }
+
+ adv7511_power_off(adv7511);
+
+ i2c_set_clientdata(i2c, adv7511);
+
+ if (adv7511->type == ADV7511)
+ adv7511_set_link_config(adv7511, &link_config);
+
+ ret = adv7511_cec_init(dev, adv7511);
+ if (ret)
+ goto err_unregister_cec;
+
+ adv7511->bridge.funcs = &adv7511_bridge_funcs;
+ adv7511->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID
+ | DRM_BRIDGE_OP_HPD;
+ adv7511->bridge.of_node = dev->of_node;
+ adv7511->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
+
+ drm_bridge_add(&adv7511->bridge);
+
+ adv7511_audio_init(dev, adv7511);
+ return 0;
+
+err_unregister_cec:
+ i2c_unregister_device(adv7511->i2c_cec);
+ if (adv7511->cec_clk)
+ clk_disable_unprepare(adv7511->cec_clk);
+err_i2c_unregister_packet:
+ i2c_unregister_device(adv7511->i2c_packet);
+err_i2c_unregister_edid:
+ i2c_unregister_device(adv7511->i2c_edid);
+uninit_regulators:
+ adv7511_uninit_regulators(adv7511);
+
+ return ret;
+}
+
+static int adv7511_remove(struct i2c_client *i2c)
+{
+ struct adv7511 *adv7511 = i2c_get_clientdata(i2c);
+
+ if (adv7511->type == ADV7533 || adv7511->type == ADV7535)
+ adv7533_detach_dsi(adv7511);
+ i2c_unregister_device(adv7511->i2c_cec);
+ if (adv7511->cec_clk)
+ clk_disable_unprepare(adv7511->cec_clk);
+
+ adv7511_uninit_regulators(adv7511);
+
+ drm_bridge_remove(&adv7511->bridge);
+
+ adv7511_audio_exit(adv7511);
+
+ cec_unregister_adapter(adv7511->cec_adap);
+
+ i2c_unregister_device(adv7511->i2c_packet);
+ i2c_unregister_device(adv7511->i2c_edid);
+
+ return 0;
+}
+
+static const struct i2c_device_id adv7511_i2c_ids[] = {
+ { "adv7511", ADV7511 },
+ { "adv7511w", ADV7511 },
+ { "adv7513", ADV7511 },
+ { "adv7533", ADV7533 },
+ { "adv7535", ADV7535 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, adv7511_i2c_ids);
+
+static const struct of_device_id adv7511_of_ids[] = {
+ { .compatible = "adi,adv7511", .data = (void *)ADV7511 },
+ { .compatible = "adi,adv7511w", .data = (void *)ADV7511 },
+ { .compatible = "adi,adv7513", .data = (void *)ADV7511 },
+ { .compatible = "adi,adv7533", .data = (void *)ADV7533 },
+ { .compatible = "adi,adv7535", .data = (void *)ADV7535 },
+ { }
+};
+MODULE_DEVICE_TABLE(of, adv7511_of_ids);
+
+static struct mipi_dsi_driver adv7533_dsi_driver = {
+ .driver.name = "adv7533",
+};
+
+static struct i2c_driver adv7511_driver = {
+ .driver = {
+ .name = "adv7511",
+ .of_match_table = adv7511_of_ids,
+ },
+ .id_table = adv7511_i2c_ids,
+ .probe = adv7511_probe,
+ .remove = adv7511_remove,
+};
+
+static int __init adv7511_init(void)
+{
+ if (IS_ENABLED(CONFIG_DRM_MIPI_DSI))
+ mipi_dsi_driver_register(&adv7533_dsi_driver);
+
+ return i2c_add_driver(&adv7511_driver);
+}
+fs_initcall(adv7511_init);
+
+static void __exit adv7511_exit(void)
+{
+ i2c_del_driver(&adv7511_driver);
+
+ if (IS_ENABLED(CONFIG_DRM_MIPI_DSI))
+ mipi_dsi_driver_unregister(&adv7533_dsi_driver);
+}
+module_exit(adv7511_exit);
+
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
+MODULE_DESCRIPTION("ADV7511 HDMI transmitter driver");
+MODULE_LICENSE("GPL");
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/of_graph.h>
+
+#include "adv7511.h"
+
+static const struct reg_sequence adv7533_fixed_registers[] = {
+ { 0x16, 0x20 },
+ { 0x9a, 0xe0 },
+ { 0xba, 0x70 },
+ { 0xde, 0x82 },
+ { 0xe4, 0x40 },
+ { 0xe5, 0x80 },
+};
+
+static const struct reg_sequence adv7533_cec_fixed_registers[] = {
+ { 0x15, 0xd0 },
+ { 0x17, 0xd0 },
+ { 0x24, 0x20 },
+ { 0x57, 0x11 },
+ { 0x05, 0xc8 },
+};
+
+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;
+ hbp = mode->htotal - mode->hsync_end;
+ vsw = mode->vsync_end - mode->vsync_start;
+ 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);
+
+ /* horizontal porch params */
+ regmap_write(adv->regmap_cec, 0x28, mode->htotal >> 4);
+ regmap_write(adv->regmap_cec, 0x29, (mode->htotal << 4) & 0xff);
+ regmap_write(adv->regmap_cec, 0x2a, hsw >> 4);
+ regmap_write(adv->regmap_cec, 0x2b, (hsw << 4) & 0xff);
+ regmap_write(adv->regmap_cec, 0x2c, hfp >> 4);
+ regmap_write(adv->regmap_cec, 0x2d, (hfp << 4) & 0xff);
+ regmap_write(adv->regmap_cec, 0x2e, hbp >> 4);
+ regmap_write(adv->regmap_cec, 0x2f, (hbp << 4) & 0xff);
+
+ /* vertical porch params */
+ regmap_write(adv->regmap_cec, 0x30, mode->vtotal >> 4);
+ regmap_write(adv->regmap_cec, 0x31, (mode->vtotal << 4) & 0xff);
+ regmap_write(adv->regmap_cec, 0x32, vsw >> 4);
+ regmap_write(adv->regmap_cec, 0x33, (vsw << 4) & 0xff);
+ regmap_write(adv->regmap_cec, 0x34, vfp >> 4);
+ 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);
+}
+
+void adv7533_dsi_power_on(struct adv7511 *adv)
+{
+ struct mipi_dsi_device *dsi = adv->dsi;
+
+ if (adv->use_timing_gen)
+ adv7511_dsi_config_timing_gen(adv);
+
+ /* set number of 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 */
+ regmap_write(adv->regmap_cec, 0x27, 0x0b);
+ }
+
+ /* enable hdmi */
+ 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)
+{
+ /* disable hdmi */
+ regmap_write(adv->regmap_cec, 0x03, 0x0b);
+ /* disable internal timing generator */
+ regmap_write(adv->regmap_cec, 0x27, 0x0b);
+}
+
+void adv7533_mode_set(struct adv7511 *adv, const struct drm_display_mode *mode)
+{
+ struct mipi_dsi_device *dsi = adv->dsi;
+ int lanes, ret;
+
+ if (adv->num_dsi_lanes != 4)
+ return;
+
+ if (mode->clock > 80000)
+ lanes = 4;
+ else
+ lanes = 3;
+
+ if (lanes != dsi->lanes) {
+ mipi_dsi_detach(dsi);
+ dsi->lanes = lanes;
+ ret = mipi_dsi_attach(dsi);
+ if (ret)
+ dev_err(&dsi->dev, "failed to change host lanes\n");
+ }
+}
+
+int adv7533_patch_registers(struct adv7511 *adv)
+{
+ return regmap_register_patch(adv->regmap,
+ adv7533_fixed_registers,
+ ARRAY_SIZE(adv7533_fixed_registers));
+}
+
+int adv7533_patch_cec_registers(struct adv7511 *adv)
+{
+ return regmap_register_patch(adv->regmap_cec,
+ adv7533_cec_fixed_registers,
+ ARRAY_SIZE(adv7533_cec_fixed_registers));
+}
+
+int adv7533_attach_dsi(struct adv7511 *adv)
+{
+ struct device *dev = &adv->i2c_main->dev;
+ struct mipi_dsi_host *host;
+ struct mipi_dsi_device *dsi;
+ int ret = 0;
+ const struct mipi_dsi_device_info info = { .type = "adv7533",
+ .channel = 0,
+ .node = NULL,
+ };
+
+ host = of_find_mipi_dsi_host_by_node(adv->host_node);
+ if (!host) {
+ dev_err(dev, "failed to find dsi host\n");
+ return -EPROBE_DEFER;
+ }
+
+ dsi = mipi_dsi_device_register_full(host, &info);
+ if (IS_ERR(dsi)) {
+ dev_err(dev, "failed to create dsi device\n");
+ ret = PTR_ERR(dsi);
+ goto err_dsi_device;
+ }
+
+ adv->dsi = dsi;
+
+ dsi->lanes = adv->num_dsi_lanes;
+ 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) {
+ dev_err(dev, "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;
+}
+
+void adv7533_detach_dsi(struct adv7511 *adv)
+{
+ mipi_dsi_detach(adv->dsi);
+ mipi_dsi_device_unregister(adv->dsi);
+}
+
+int adv7533_parse_dt(struct device_node *np, struct adv7511 *adv)
+{
+ u32 num_lanes;
+
+ of_property_read_u32(np, "adi,dsi-lanes", &num_lanes);
+
+ if (num_lanes < 1 || num_lanes > 4)
+ return -EINVAL;
+
+ adv->num_dsi_lanes = num_lanes;
+
+ adv->host_node = of_graph_get_remote_node(np, 0, 0);
+ if (!adv->host_node)
+ return -ENODEV;
+
+ of_node_put(adv->host_node);
+
+ adv->use_timing_gen = !of_property_read_bool(np,
+ "adi,disable-timing-generator");
+
+ /* TODO: Check if these need to be parsed by DT or not */
+ adv->rgb = true;
+ adv->embedded_sync = false;
+
+ return 0;
+}
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020 VeriSilicon Holdings Co., Ltd.
+ *
+ * refer to bridge/synopsys/dw-mipi-dsi.c
+ */
+
+#include <linux/version.h>
+#include <linux/component.h>
+#include <linux/clk.h>
+#include <linux/of_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/reset.h>
+#include <linux/phy/phy.h>
+#include <linux/iopoll.h>
+#include <linux/phy.h>
+#include <linux/phy/phy-mipi-dphy.h>
+#include <linux/delay.h>
+
+#include <drm/drm_bridge.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_of.h>
+#include <drm/drm_mipi_dsi.h>
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 5, 0)
+#include <drm/drm_print.h>
+#else
+#include <drm/drmP.h>
+#endif
+
+#include "dw_mipi_dsi.h"
+
+#define DSI_VERSION 0x00
+
+#define DSI_PWR_UP 0x04
+#define RESET 0
+#define POWERUP BIT(0)
+
+#define DSI_CLKMGR_CFG 0x08
+#define TO_CLK_DIVISION(div) (((div) & 0xff) << 8)
+#define TX_ESC_CLK_DIVISION(div) ((div) & 0xff)
+
+#define DSI_DPI_VCID 0x0c
+#define DPI_VCID(vcid) ((vcid) & 0x3)
+
+#define DSI_DPI_COLOR_CODING 0x10
+#define LOOSELY18_EN BIT(8)
+#define DPI_COLOR_CODING_16BIT_1 0x0
+#define DPI_COLOR_CODING_16BIT_2 0x1
+#define DPI_COLOR_CODING_16BIT_3 0x2
+#define DPI_COLOR_CODING_18BIT_1 0x3
+#define DPI_COLOR_CODING_18BIT_2 0x4
+#define DPI_COLOR_CODING_24BIT 0x5
+
+#define DSI_DPI_CFG_POL 0x14
+#define COLORM_ACTIVE_LOW BIT(4)
+#define SHUTD_ACTIVE_LOW BIT(3)
+#define HSYNC_ACTIVE_LOW BIT(2)
+#define VSYNC_ACTIVE_LOW BIT(1)
+#define DATAEN_ACTIVE_LOW BIT(0)
+
+#define DSI_DPI_LP_CMD_TIM 0x18
+#define OUTVACT_LPCMD_TIME(p) (((p) & 0xff) << 16)
+#define INVACT_LPCMD_TIME(p) ((p) & 0xff)
+
+#define DSI_DBI_VCID 0x1c
+#define DSI_DBI_CFG 0x20
+#define DSI_DBI_PARTITIONING_EN 0x24
+#define DSI_DBI_CMDSIZE 0x28
+
+#define DSI_PCKHDL_CFG 0x2c
+#define EOTP_TX_LP_EN BIT(5)
+#define CRC_RX_EN BIT(4)
+#define ECC_RX_EN BIT(3)
+#define BTA_EN BIT(2)
+#define EOTP_RX_EN BIT(1)
+#define EOTP_TX_EN BIT(0)
+
+#define DSI_GEN_VCID 0x30
+
+#define DSI_MODE_CFG 0x34
+#define ENABLE_VIDEO_MODE 0
+#define ENABLE_CMD_MODE BIT(0)
+
+#define DSI_VID_MODE_CFG 0x38
+#define ENABLE_LOW_POWER (0x3f << 8)
+#define ENABLE_LOW_POWER_MASK (0x3f << 8)
+#define VID_MODE_TYPE_NON_BURST_SYNC_PULSES 0x0
+#define VID_MODE_TYPE_NON_BURST_SYNC_EVENTS 0x1
+#define VID_MODE_TYPE_BURST 0x2
+#define VID_MODE_TYPE_MASK 0x3
+
+#define DSI_VID_PKT_SIZE 0x3c
+#define VID_PKT_SIZE(p) ((p) & 0x3fff)
+
+#define DSI_VID_NUM_CHUNKS 0x40
+#define VID_NUM_CHUNKS(c) ((c) & 0x1fff)
+
+#define DSI_VID_NULL_SIZE 0x44
+#define VID_NULL_SIZE(b) ((b) & 0x1fff)
+
+#define DSI_VID_HSA_TIME 0x48
+#define DSI_VID_HBP_TIME 0x4c
+#define DSI_VID_HLINE_TIME 0x50
+#define DSI_VID_VSA_LINES 0x54
+#define DSI_VID_VBP_LINES 0x58
+#define DSI_VID_VFP_LINES 0x5c
+#define DSI_VID_VACTIVE_LINES 0x60
+#define DSI_EDPI_CMD_SIZE 0x64
+
+#define DSI_CMD_MODE_CFG 0x68
+#define MAX_RD_PKT_SIZE_LP BIT(24)
+#define DCS_LW_TX_LP BIT(19)
+#define DCS_SR_0P_TX_LP BIT(18)
+#define DCS_SW_1P_TX_LP BIT(17)
+#define DCS_SW_0P_TX_LP BIT(16)
+#define GEN_LW_TX_LP BIT(14)
+#define GEN_SR_2P_TX_LP BIT(13)
+#define GEN_SR_1P_TX_LP BIT(12)
+#define GEN_SR_0P_TX_LP BIT(11)
+#define GEN_SW_2P_TX_LP BIT(10)
+#define GEN_SW_1P_TX_LP BIT(9)
+#define GEN_SW_0P_TX_LP BIT(8)
+#define ACK_RQST_EN BIT(1)
+#define TEAR_FX_EN BIT(0)
+
+#define CMD_MODE_ALL_LP (MAX_RD_PKT_SIZE_LP | \
+ DCS_LW_TX_LP | \
+ DCS_SR_0P_TX_LP | \
+ DCS_SW_1P_TX_LP | \
+ DCS_SW_0P_TX_LP | \
+ GEN_LW_TX_LP | \
+ GEN_SR_2P_TX_LP | \
+ GEN_SR_1P_TX_LP | \
+ GEN_SR_0P_TX_LP | \
+ GEN_SW_2P_TX_LP | \
+ GEN_SW_1P_TX_LP | \
+ GEN_SW_0P_TX_LP)
+
+#define DSI_GEN_HDR 0x6c
+#define DSI_GEN_PLD_DATA 0x70
+
+#define DSI_CMD_PKT_STATUS 0x74
+#define GEN_RD_CMD_BUSY BIT(6)
+#define GEN_PLD_R_FULL BIT(5)
+#define GEN_PLD_R_EMPTY BIT(4)
+#define GEN_PLD_W_FULL BIT(3)
+#define GEN_PLD_W_EMPTY BIT(2)
+#define GEN_CMD_FULL BIT(1)
+#define GEN_CMD_EMPTY BIT(0)
+
+#define DSI_TO_CNT_CFG 0x78
+#define HSTX_TO_CNT(p) (((p) & 0xffff) << 16)
+#define LPRX_TO_CNT(p) ((p) & 0xffff)
+
+#define DSI_HS_RD_TO_CNT 0x7c
+#define DSI_LP_RD_TO_CNT 0x80
+#define DSI_HS_WR_TO_CNT 0x84
+#define DSI_LP_WR_TO_CNT 0x88
+#define DSI_BTA_TO_CNT 0x8c
+
+#define DSI_LPCLK_CTRL 0x94
+#define AUTO_CLKLANE_CTRL BIT(1)
+#define PHY_TXREQUESTCLKHS BIT(0)
+
+#define DSI_PHY_TMR_LPCLK_CFG 0x98
+#define PHY_CLKHS2LP_TIME(lbcc) (((lbcc) & 0x3ff) << 16)
+#define PHY_CLKLP2HS_TIME(lbcc) ((lbcc) & 0x3ff)
+
+#define DSI_PHY_TMR_CFG 0x9c
+#define PHY_HS2LP_TIME(lbcc) (((lbcc) & 0x3ff) << 16)
+#define PHY_LP2HS_TIME(lbcc) ((lbcc) & 0x3ff)
+
+#define DSI_PHY_RSTZ 0xa0
+#define PHY_DISFORCEPLL 0
+#define PHY_ENFORCEPLL BIT(3)
+#define PHY_DISABLECLK 0
+#define PHY_ENABLECLK BIT(2)
+#define PHY_RSTZ 0
+#define PHY_UNRSTZ BIT(1)
+#define PHY_SHUTDOWNZ 0
+#define PHY_UNSHUTDOWNZ BIT(0)
+
+#define DSI_PHY_IF_CFG 0xa4
+#define PHY_STOP_WAIT_TIME(cycle) (((cycle) & 0xff) << 8)
+#define N_LANES(n) (((n) - 1) & 0x3)
+
+#define DSI_PHY_ULPS_CTRL 0xa8
+#define DSI_PHY_TX_TRIGGERS 0xac
+
+#define DSI_PHY_STATUS 0xb0
+#define PHY_STOP_STATE_CLK_LANE BIT(2)
+#define PHY_LOCK BIT(0)
+
+#define DSI_INT_ST0 0xbc
+#define DSI_INT_ST1 0xc0
+#define DSI_INT_MSK0 0xc4
+#define DSI_INT_MSK1 0xc8
+
+#define DSI_PHY_TMR_RD_CFG 0xf4
+#define MAX_RD_TIME(lbcc) ((lbcc) & 0x7fff)
+
+#define PHY_STATUS_TIMEOUT_US 10000
+#define CMD_PKT_STATUS_TIMEOUT_US 20000
+
+#define MAX_LANE_COUNT 4
+
+struct dw_mipi_dsi;
+
+struct dw_mipi_dsi_funcs {
+ struct dw_mipi_dsi *(*get_dsi)(struct device *dev);
+ int (*bind)(struct dw_mipi_dsi *dsi);
+ void (*unbind)(struct dw_mipi_dsi *dsi);
+};
+
+struct dw_mipi_dsi {
+ struct device *dev;
+ void __iomem *base;
+ struct clk *pclk;
+ struct phy *dphy;
+
+ struct mipi_dsi_host host;
+ unsigned int channel;
+ unsigned int lanes;
+ enum mipi_dsi_pixel_format format;
+ unsigned long mode_flags;
+
+ unsigned int lane_link_rate; /* kHz */
+
+ const struct dw_mipi_dsi_funcs *funcs;
+};
+
+struct dw_mipi_dsi_primary {
+ struct dw_mipi_dsi dsi;
+ struct dw_mipi_dsi *secondary_dsi;
+
+ struct drm_bridge bridge;
+ struct drm_bridge *panel_bridge;
+ u32 bus_format;
+};
+
+static inline struct dw_mipi_dsi *host_to_dsi(struct mipi_dsi_host *host)
+{
+ return container_of(host, struct dw_mipi_dsi, host);
+}
+
+static inline struct
+dw_mipi_dsi_primary *dsi_to_primary(struct dw_mipi_dsi *dsi)
+{
+ return container_of(dsi, struct dw_mipi_dsi_primary, dsi);
+}
+
+static inline struct
+dw_mipi_dsi_primary *bridge_to_primary(struct drm_bridge *bridge)
+{
+ return container_of(bridge, struct dw_mipi_dsi_primary, bridge);
+}
+
+static inline void dsi_write(struct dw_mipi_dsi *dsi, u32 reg, u32 val)
+{
+ writel(val, dsi->base + reg);
+}
+
+static inline u32 dsi_read(struct dw_mipi_dsi *dsi, u32 reg)
+{
+ return readl(dsi->base + reg);
+}
+
+static int dsi_host_attach(struct mipi_dsi_host *host,
+ struct mipi_dsi_device *device)
+{
+ struct dw_mipi_dsi *dsi = host_to_dsi(host);
+
+ if (device->lanes > MAX_LANE_COUNT) {
+ DRM_ERROR("the number of data lanes(%u) is too many\n",
+ device->lanes);
+ return -EINVAL;
+ }
+
+ if (!(device->mode_flags & MIPI_DSI_MODE_VIDEO_BURST))
+ DRM_WARN("This DSI driver only support burst mode\n");
+
+ dsi->lanes = device->lanes;
+ dsi->channel = device->channel;
+ dsi->format = device->format;
+ dsi->mode_flags = device->mode_flags;
+
+ return 0;
+}
+
+static int dsi_host_detach(struct mipi_dsi_host *host,
+ struct mipi_dsi_device *device)
+{
+ return 0;
+}
+
+static void dw_mipi_message_config(struct dw_mipi_dsi *dsi,
+ const struct mipi_dsi_msg *msg)
+{
+ bool lpm = msg->flags & MIPI_DSI_MSG_USE_LPM;
+ u32 val = 0;
+
+ if (msg->flags & MIPI_DSI_MSG_REQ_ACK)
+ val |= ACK_RQST_EN;
+ if (lpm)
+ val |= CMD_MODE_ALL_LP;
+
+ dsi_write(dsi, DSI_LPCLK_CTRL, lpm ? 0 : PHY_TXREQUESTCLKHS);
+ dsi_write(dsi, DSI_CMD_MODE_CFG, val);
+}
+
+static int dw_mipi_dsi_gen_pkt_hdr_write(struct dw_mipi_dsi *dsi, u32 hdr_val)
+{
+ int ret;
+ u32 val, mask;
+
+ ret = readl_poll_timeout(dsi->base + DSI_CMD_PKT_STATUS,
+ val, !(val & GEN_CMD_FULL), 1000,
+ CMD_PKT_STATUS_TIMEOUT_US);
+ if (ret) {
+ DRM_ERROR("failed to get available command FIFO\n");
+ return ret;
+ }
+
+ dsi_write(dsi, DSI_GEN_HDR, hdr_val);
+
+ mask = GEN_CMD_EMPTY | GEN_PLD_W_EMPTY;
+ ret = readl_poll_timeout(dsi->base + DSI_CMD_PKT_STATUS,
+ val, (val & mask) == mask,
+ 1000, CMD_PKT_STATUS_TIMEOUT_US);
+ if (ret) {
+ DRM_ERROR("failed to write command FIFO\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int dw_mipi_dsi_write(struct dw_mipi_dsi *dsi,
+ const struct mipi_dsi_packet *packet)
+{
+ const u8 *tx_buf = packet->payload;
+ int len = packet->payload_length, pld_data_bytes = sizeof(u32), ret;
+ __le32 word;
+ u32 val;
+
+ while (len) {
+ if (len < pld_data_bytes) {
+ word = 0;
+ memcpy(&word, tx_buf, len);
+ dsi_write(dsi, DSI_GEN_PLD_DATA, le32_to_cpu(word));
+ len = 0;
+ } else {
+ memcpy(&word, tx_buf, pld_data_bytes);
+ dsi_write(dsi, DSI_GEN_PLD_DATA, le32_to_cpu(word));
+ tx_buf += pld_data_bytes;
+ len -= pld_data_bytes;
+ }
+
+ ret = readl_poll_timeout(dsi->base + DSI_CMD_PKT_STATUS,
+ val, !(val & GEN_PLD_W_FULL), 1000,
+ CMD_PKT_STATUS_TIMEOUT_US);
+ if (ret) {
+ DRM_ERROR("failed to get write payload FIFO\n");
+ return ret;
+ }
+ }
+
+ word = 0;
+ memcpy(&word, packet->header, sizeof(packet->header));
+ return dw_mipi_dsi_gen_pkt_hdr_write(dsi, le32_to_cpu(word));
+}
+
+static int dw_mipi_dsi_read(struct dw_mipi_dsi *dsi,
+ const struct mipi_dsi_msg *msg)
+{
+ int i, j, ret, len = msg->rx_len;
+ u8 *buf = msg->rx_buf;
+ u32 val;
+
+ /* Wait end of the read operation */
+ ret = readl_poll_timeout(dsi->base + DSI_CMD_PKT_STATUS,
+ val, !(val & GEN_RD_CMD_BUSY),
+ 1000, CMD_PKT_STATUS_TIMEOUT_US);
+ if (ret) {
+ DRM_ERROR("Timeout during read operation\n");
+ return ret;
+ }
+
+ for (i = 0; i < len; i += 4) {
+ /* Read fifo must not be empty before all bytes are read */
+ ret = readl_poll_timeout(dsi->base + DSI_CMD_PKT_STATUS,
+ val, !(val & GEN_PLD_R_EMPTY),
+ 1000, CMD_PKT_STATUS_TIMEOUT_US);
+ if (ret) {
+ DRM_ERROR("Read payload FIFO is empty\n");
+ return ret;
+ }
+
+ val = dsi_read(dsi, DSI_GEN_PLD_DATA);
+ for (j = 0; j < 4 && j + i < len; j++)
+ buf[i + j] = val >> (8 * j);
+ }
+
+ return ret;
+}
+
+static ssize_t dsi_host_transfer(struct mipi_dsi_host *host,
+ const struct mipi_dsi_msg *msg)
+{
+ struct dw_mipi_dsi *dsi = host_to_dsi(host);
+ struct mipi_dsi_packet packet;
+ int ret, nb_bytes;
+
+ ret = mipi_dsi_create_packet(&packet, msg);
+ if (ret) {
+ DRM_ERROR("failed to create packet: %d\n", ret);
+ return ret;
+ }
+
+ dw_mipi_message_config(dsi, msg);
+
+ ret = dw_mipi_dsi_write(dsi, &packet);
+ if (ret)
+ return ret;
+
+ if (msg->rx_buf && msg->rx_len) {
+ ret = dw_mipi_dsi_read(dsi, msg);
+ if (ret)
+ return ret;
+ nb_bytes = msg->rx_len;
+ } else {
+ nb_bytes = packet.size;
+ }
+
+ return nb_bytes;
+}
+
+static const struct mipi_dsi_host_ops dw_mipi_dsi_host_ops = {
+ .attach = dsi_host_attach,
+ .detach = dsi_host_detach,
+ .transfer = dsi_host_transfer,
+};
+
+static void dw_mipi_dsi_video_config(struct dw_mipi_dsi *dsi)
+{
+ dsi_write(dsi, DSI_VID_MODE_CFG,
+ ENABLE_LOW_POWER | VID_MODE_TYPE_BURST);
+}
+
+static void dw_mipi_dsi_set_mode(struct dw_mipi_dsi *dsi,
+ unsigned long mode_flags)
+{
+ dsi_write(dsi, DSI_PWR_UP, RESET);
+
+ if (mode_flags & MIPI_DSI_MODE_VIDEO) {
+ dsi_write(dsi, DSI_MODE_CFG, ENABLE_VIDEO_MODE);
+ dsi_write(dsi, DSI_LPCLK_CTRL, PHY_TXREQUESTCLKHS);
+ } else {
+ dsi_write(dsi, DSI_MODE_CFG, ENABLE_CMD_MODE);
+ }
+
+ dsi_write(dsi, DSI_PWR_UP, POWERUP);
+}
+
+static void dw_mipi_dsi_init(struct dw_mipi_dsi *dsi)
+{
+ u8 esc_clk;
+ u32 esc_clk_division;
+
+ /* limit esc clk on FPGA */
+ if (!dsi->pclk)
+ esc_clk = 5; /* 5MHz */
+ else
+ esc_clk = 20; /* 20MHz */
+
+ /*
+ * The maximum permitted escape clock is 20MHz and it is derived from
+ * lanebyteclk, which is running at "lane_link_rate / 8". Thus we want:
+ *
+ * (lane_link_rate >> 3) / esc_clk_division < 20
+ * which is:
+ * (lane_link_rate >> 3) / 20 > esc_clk_division
+ */
+ esc_clk_division = ((dsi->lane_link_rate / 1000) >> 3) / esc_clk + 1;
+
+ dsi_write(dsi, DSI_PWR_UP, RESET);
+
+ /*
+ * TODO dw drv improvements
+ * 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) |
+ TX_ESC_CLK_DIVISION(esc_clk_division));
+}
+
+static void dw_mipi_dsi_dpi_config(struct dw_mipi_dsi *dsi,
+ const struct drm_display_mode *mode)
+{
+ u32 val = 0, color = 0;
+
+ switch (dsi->format) {
+ case MIPI_DSI_FMT_RGB888:
+ color = DPI_COLOR_CODING_24BIT;
+ break;
+ case MIPI_DSI_FMT_RGB666:
+ color = DPI_COLOR_CODING_18BIT_2 | LOOSELY18_EN;
+ break;
+ case MIPI_DSI_FMT_RGB666_PACKED:
+ color = DPI_COLOR_CODING_18BIT_1;
+ break;
+ case MIPI_DSI_FMT_RGB565:
+ color = DPI_COLOR_CODING_16BIT_1;
+ break;
+ }
+
+ if (!(mode->flags & DRM_MODE_FLAG_PVSYNC))
+ val |= VSYNC_ACTIVE_LOW;
+ if (!(mode->flags & DRM_MODE_FLAG_PHSYNC))
+ val |= HSYNC_ACTIVE_LOW;
+
+ dsi_write(dsi, DSI_DPI_VCID, DPI_VCID(dsi->channel));
+ dsi_write(dsi, DSI_DPI_COLOR_CODING, color);
+ dsi_write(dsi, DSI_DPI_CFG_POL, val);
+ /*
+ * TODO dw drv improvements
+ * largest packet sizes during hfp or during vsa/vpb/vfp
+ * should be computed according to byte lane, lane number and only
+ * if sending lp cmds in high speed is enable (PHY_TXREQUESTCLKHS)
+ */
+ dsi_write(dsi, DSI_DPI_LP_CMD_TIM, OUTVACT_LPCMD_TIME(4)
+ | INVACT_LPCMD_TIME(4));
+}
+
+static void dw_mipi_dsi_packet_handler_config(struct dw_mipi_dsi *dsi)
+{
+ dsi_write(dsi, DSI_PCKHDL_CFG, CRC_RX_EN | ECC_RX_EN | BTA_EN);
+}
+
+static void dw_mipi_dsi_command_mode_config(struct dw_mipi_dsi *dsi)
+{
+
+ /*
+ * TODO dw drv improvements
+ * compute high speed transmission counter timeout according
+ * to the timeout clock division (TO_CLK_DIVISION) and byte lane...
+ */
+ dsi_write(dsi, DSI_TO_CNT_CFG,
+ HSTX_TO_CNT(1000) | LPRX_TO_CNT(1000));
+ /*
+ * TODO dw drv improvements
+ * the Bus-Turn-Around Timeout Counter should be computed
+ * according to byte lane...
+ */
+ dsi_write(dsi, DSI_BTA_TO_CNT, 0xd00);
+
+}
+
+/* Get lane byte clock cycles. */
+static u32 dw_mipi_dsi_get_hcomponent_lbcc(struct dw_mipi_dsi *dsi,
+ const struct drm_display_mode *mode,
+ u32 hcomponent)
+{
+ u32 frac, lbcc;
+
+ lbcc = hcomponent * dsi->lane_link_rate / 8;
+
+ frac = lbcc % mode->clock;
+ lbcc = lbcc / mode->clock;
+ if (frac)
+ lbcc++;
+
+ return lbcc;
+}
+
+static void dw_mipi_dsi_timing_config(struct dw_mipi_dsi *dsi,
+ const struct drm_display_mode *mode)
+{
+ u32 htotal, hsa, hbp, lbcc;
+
+ htotal = mode->htotal;
+ hsa = mode->hsync_end - mode->hsync_start;
+ hbp = mode->htotal - mode->hsync_end;
+
+ lbcc = dw_mipi_dsi_get_hcomponent_lbcc(dsi, mode, htotal);
+ dsi_write(dsi, DSI_VID_HLINE_TIME, lbcc);
+
+ lbcc = dw_mipi_dsi_get_hcomponent_lbcc(dsi, mode, hsa);
+ dsi_write(dsi, DSI_VID_HSA_TIME, lbcc);
+
+ lbcc = dw_mipi_dsi_get_hcomponent_lbcc(dsi, mode, hbp);
+ dsi_write(dsi, DSI_VID_HBP_TIME, lbcc);
+
+ dsi_write(dsi, DSI_VID_PKT_SIZE, VID_PKT_SIZE(mode->hdisplay));
+
+ /* vertical */
+ dsi_write(dsi, DSI_VID_VACTIVE_LINES, mode->vdisplay);
+ dsi_write(dsi, DSI_VID_VSA_LINES, mode->vsync_end - mode->vsync_start);
+ dsi_write(dsi, DSI_VID_VFP_LINES, mode->vsync_start - mode->vdisplay);
+ dsi_write(dsi, DSI_VID_VBP_LINES, mode->vtotal - mode->vsync_end);
+}
+
+static void dw_mipi_dsi_dphy_timing_config(struct dw_mipi_dsi *dsi)
+{
+ /*
+ * TODO dw drv improvements
+ * data & clock lane timers should be computed according to panel
+ * blankings and to the automatic clock lane control mode...
+ * note: DSI_PHY_TMR_CFG.MAX_RD_TIME should be in line with
+ * DSI_CMD_MODE_CFG.MAX_RD_PKT_SIZE_LP (see CMD_MODE_ALL_LP)
+ */
+
+ dsi_write(dsi, DSI_PHY_TMR_CFG, PHY_HS2LP_TIME(0x40) |
+ PHY_LP2HS_TIME(0x40));
+ dsi_write(dsi, DSI_PHY_TMR_RD_CFG, MAX_RD_TIME(10000));
+
+ dsi_write(dsi, DSI_PHY_TMR_LPCLK_CFG, PHY_CLKHS2LP_TIME(0x40)
+ | PHY_CLKLP2HS_TIME(0x40));
+}
+
+static void dw_mipi_dsi_dphy_interface_config(struct dw_mipi_dsi *dsi)
+{
+ /*
+ * TODO dw drv improvements
+ * stop wait time should be the maximum between host dsi
+ * and panel stop wait times
+ */
+ dsi_write(dsi, DSI_PHY_IF_CFG, PHY_STOP_WAIT_TIME(0x20) |
+ N_LANES(dsi->lanes));
+}
+
+static void dw_mipi_dsi_dphy_init(struct dw_mipi_dsi *dsi)
+{
+ /* Clear PHY state */
+ dsi_write(dsi, DSI_PHY_RSTZ, PHY_DISFORCEPLL | PHY_DISABLECLK
+ | PHY_RSTZ | PHY_SHUTDOWNZ);
+}
+
+static void dw_mipi_dsi_dphy_enable(struct dw_mipi_dsi *dsi)
+{
+ u32 val;
+ int ret;
+
+ phy_power_on(dsi->dphy);
+ dsi_write(dsi, DSI_PHY_RSTZ, PHY_ENFORCEPLL | PHY_ENABLECLK |
+ PHY_UNSHUTDOWNZ);
+ usleep_range(500, 600);
+ dsi_write(dsi, DSI_PHY_RSTZ, PHY_ENFORCEPLL | PHY_ENABLECLK |
+ PHY_UNRSTZ | PHY_UNSHUTDOWNZ);
+
+ ret = readl_poll_timeout(dsi->base + DSI_PHY_STATUS, val,
+ val & PHY_LOCK, 1000, PHY_STATUS_TIMEOUT_US);
+ if (ret)
+ DRM_DEBUG_DRIVER("failed to wait phy lock state\n");
+
+ ret = readl_poll_timeout(dsi->base + DSI_PHY_STATUS,
+ val, val & PHY_STOP_STATE_CLK_LANE, 1000,
+ PHY_STATUS_TIMEOUT_US);
+ if (ret)
+ DRM_DEBUG_DRIVER("failed to wait phy clk lane stop state\n");
+}
+
+/*
+ * The controller should generate 2 frames before
+ * preparing the peripheral.
+ */
+static void dw_mipi_dsi_wait_for_two_frames(const struct drm_display_mode *mode)
+{
+ int refresh, two_frames;
+
+ refresh = drm_mode_vrefresh(mode);
+ two_frames = DIV_ROUND_UP(MSEC_PER_SEC, refresh) * 2;
+ msleep(two_frames);
+}
+
+static void dw_mipi_dsi_mode_set(struct dw_mipi_dsi *dsi,
+ const struct drm_display_mode *mode)
+{
+ struct phy_configure_opts_mipi_dphy dphy_cfg;
+ int bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
+
+ phy_init(dsi->dphy);
+ phy_mipi_dphy_get_default_config(mode->clock * 1000, bpp,
+ dsi->lanes, &dphy_cfg);
+ phy_validate(dsi->dphy, PHY_MODE_MIPI_DPHY, 0,
+ (union phy_configure_opts *)&dphy_cfg);
+ phy_configure(dsi->dphy, (union phy_configure_opts *)&dphy_cfg);
+
+ dsi->lane_link_rate = dphy_cfg.hs_clk_rate / 1000;
+
+ clk_prepare_enable(dsi->pclk);
+ pm_runtime_get_sync(dsi->dev);
+
+ dw_mipi_dsi_init(dsi);
+ dw_mipi_dsi_dpi_config(dsi, mode);
+ dw_mipi_dsi_packet_handler_config(dsi);
+ dw_mipi_dsi_video_config(dsi);
+ dw_mipi_dsi_command_mode_config(dsi);
+ dw_mipi_dsi_timing_config(dsi, mode);
+
+ dw_mipi_dsi_dphy_init(dsi);
+ dw_mipi_dsi_dphy_timing_config(dsi);
+ dw_mipi_dsi_dphy_interface_config(dsi);
+
+ dw_mipi_dsi_dphy_enable(dsi);
+
+ dw_mipi_dsi_wait_for_two_frames(mode);
+
+ /* Switch to cmd mode for panel-bridge pre_enable & panel prepare */
+ dw_mipi_dsi_set_mode(dsi, 0);
+}
+
+static void bridge_mode_set(struct drm_bridge *bridge,
+ const struct drm_display_mode *mode,
+ const struct drm_display_mode *adjusted_mode)
+{
+ struct dw_mipi_dsi_primary *primary = bridge_to_primary(bridge);
+ struct drm_display_mode *new_mode =
+ drm_mode_duplicate(bridge->dev, adjusted_mode);
+
+ if (primary->secondary_dsi) {
+ new_mode->hdisplay /= 2;
+ new_mode->hsync_start /= 2;
+ new_mode->hsync_end /= 2;
+ new_mode->htotal /= 2;
+ new_mode->clock /= 2;
+ }
+
+ dw_mipi_dsi_mode_set(&primary->dsi, new_mode);
+ if (primary->secondary_dsi)
+ dw_mipi_dsi_mode_set(primary->secondary_dsi, new_mode);
+
+ drm_mode_destroy(bridge->dev, new_mode);
+}
+
+static void bridge_enable(struct drm_bridge *bridge)
+{
+ struct dw_mipi_dsi_primary *primary = bridge_to_primary(bridge);
+
+ dw_mipi_dsi_set_mode(&primary->dsi, MIPI_DSI_MODE_VIDEO);
+ if (primary->secondary_dsi)
+ dw_mipi_dsi_set_mode(primary->secondary_dsi,
+ MIPI_DSI_MODE_VIDEO);
+}
+
+static void dw_mipi_dsi_disable(struct dw_mipi_dsi *dsi)
+{
+ phy_power_off(dsi->dphy);
+
+ dsi_write(dsi, DSI_PWR_UP, RESET);
+ dsi_write(dsi, DSI_PHY_RSTZ, PHY_RSTZ);
+
+ pm_runtime_put(dsi->dev);
+ clk_disable_unprepare(dsi->pclk);
+}
+
+static void bridge_post_disable(struct drm_bridge *bridge)
+{
+ struct dw_mipi_dsi_primary *primary = bridge_to_primary(bridge);
+
+ /*
+ * Switch to command mode before panel-bridge post_disable &
+ * panel unprepare.
+ * Note: panel-bridge disable & panel disable has been called
+ * before by the drm framework.
+ */
+ dw_mipi_dsi_set_mode(&primary->dsi, 0);
+
+ /*
+ * TODO Only way found to call panel-bridge post_disable &
+ * panel unprepare before the dsi "final" disable...
+ * This needs to be fixed in the drm_bridge framework and the API
+ * needs to be updated to manage our own call chains...
+ */
+ primary->panel_bridge->funcs->post_disable(primary->panel_bridge);
+
+ if (primary->secondary_dsi)
+ dw_mipi_dsi_disable(primary->secondary_dsi);
+
+ dw_mipi_dsi_disable(&primary->dsi);
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 7, 0)
+static int bridge_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
+#else
+static int bridge_attach(struct drm_bridge *bridge)
+#endif
+{
+ struct dw_mipi_dsi_primary *primary = bridge_to_primary(bridge);
+
+ if (!bridge->encoder) {
+ DRM_ERROR("Parent encoder object not found\n");
+ return -ENODEV;
+ }
+
+ /* Attach the panel-bridge to the dsi bridge */
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 7, 0)
+ return drm_bridge_attach(bridge->encoder, primary->panel_bridge,
+ bridge, 0);
+#else
+ return drm_bridge_attach(bridge->encoder, primary->panel_bridge,
+ bridge);
+#endif
+}
+
+static void bridge_update_bus_format(struct drm_bridge *bridge)
+{
+ struct dw_mipi_dsi_primary *primary = bridge_to_primary(bridge);
+ struct drm_connector *connector;
+ struct drm_connector_list_iter conn_iter;
+ struct drm_encoder *encoder;
+ u32 bus_format;
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 5, 0)
+ int i;
+#endif
+
+ /* set bus format according to DSI format */
+ switch (primary->dsi.format) {
+ case MIPI_DSI_FMT_RGB888:
+ bus_format = MEDIA_BUS_FMT_RBG888_1X24;
+ break;
+ case MIPI_DSI_FMT_RGB666:
+ bus_format = MEDIA_BUS_FMT_RGB666_1X24_CPADHI;
+ break;
+ case MIPI_DSI_FMT_RGB666_PACKED:
+ bus_format = MEDIA_BUS_FMT_RGB666_1X18;
+ break;
+ case MIPI_DSI_FMT_RGB565:
+ bus_format = MEDIA_BUS_FMT_RGB565_1X16;
+ break;
+ default:
+ bus_format = MEDIA_BUS_FMT_RBG888_1X24;
+ }
+
+ if (bus_format == primary->bus_format)
+ return;
+
+ drm_connector_list_iter_begin(bridge->dev, &conn_iter);
+
+ drm_for_each_connector_iter(connector, &conn_iter) {
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 5, 0)
+ drm_connector_for_each_possible_encoder(connector, encoder, i) {
+#else
+ drm_connector_for_each_possible_encoder(connector, encoder) {
+#endif
+ if (encoder == bridge->encoder) {
+ drm_display_info_set_bus_formats(
+ &connector->display_info,
+ &bus_format, 1);
+ primary->bus_format = bus_format;
+ drm_connector_list_iter_end(&conn_iter);
+ return;
+ }
+ }
+ }
+ drm_connector_list_iter_end(&conn_iter);
+}
+
+static bool bridge_mode_fixup(struct drm_bridge *bridge,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ bridge_update_bus_format(bridge);
+ return true;
+}
+
+static const struct drm_bridge_funcs dw_mipi_dsi_bridge_funcs = {
+ .mode_set = bridge_mode_set,
+ .enable = bridge_enable,
+ .post_disable = bridge_post_disable,
+ .attach = bridge_attach,
+ .mode_fixup = bridge_mode_fixup,
+};
+
+static int dsi_attach_primary(struct dw_mipi_dsi *secondary, struct device *dev)
+{
+ struct dw_mipi_dsi *dsi = dev_get_drvdata(dev);
+ struct dw_mipi_dsi_primary *primary = dsi_to_primary(dsi);
+
+ if (!of_device_is_compatible(dev->of_node, "verisilicon,dw-mipi-dsi"))
+ return -EINVAL;
+
+ primary->secondary_dsi = secondary;
+ return 0;
+}
+
+struct dw_mipi_dsi *get_primary_dsi(struct device *dev)
+{
+ struct dw_mipi_dsi_primary *primary;
+
+ primary = devm_kzalloc(dev, sizeof(*primary), GFP_KERNEL);
+ if (!primary)
+ return NULL;
+
+ primary->dsi.dev = dev;
+
+ return &primary->dsi;
+}
+
+static int primary_bind(struct dw_mipi_dsi *dsi)
+{
+ struct dw_mipi_dsi_primary *primary = dsi_to_primary(dsi);
+ struct device *dev = primary->dsi.dev;
+ struct drm_bridge *bridge;
+ struct drm_panel *panel;
+ int ret;
+
+ ret = drm_of_find_panel_or_bridge(dev->of_node, 1, 0, &panel, &bridge);
+ if (ret)
+ return ret;
+
+ if (panel) {
+ bridge = drm_panel_bridge_add_typed(panel,
+ DRM_MODE_CONNECTOR_DSI);
+ if (IS_ERR(bridge))
+ return PTR_ERR(bridge);
+ }
+
+ primary->panel_bridge = bridge;
+
+ primary->bridge.funcs = &dw_mipi_dsi_bridge_funcs;
+ primary->bridge.of_node = dev->of_node;
+ drm_bridge_add(&primary->bridge);
+ return 0;
+}
+
+static void primary_unbind(struct dw_mipi_dsi *dsi)
+{
+ struct dw_mipi_dsi_primary *primary = dsi_to_primary(dsi);
+ struct device *dev = primary->dsi.dev;
+
+ drm_bridge_remove(&primary->bridge);
+ drm_of_panel_bridge_remove(dev->of_node, 1, 0);
+}
+
+static const struct dw_mipi_dsi_funcs primary = {
+ .get_dsi = &get_primary_dsi,
+ .bind = &primary_bind,
+ .unbind = &primary_unbind,
+};
+
+struct dw_mipi_dsi *get_secondary_dsi(struct device *dev)
+{
+ struct dw_mipi_dsi *dsi;
+
+ dsi = devm_kzalloc(dev, sizeof(*dsi), GFP_KERNEL);
+ if (!dsi)
+ return NULL;
+
+ dsi->dev = dev;
+ return dsi;
+}
+
+static int secondary_bind(struct dw_mipi_dsi *dsi)
+{
+ struct device_node *np;
+ struct platform_device *pdev;
+
+ np = of_find_compatible_node(NULL, NULL, "verisilicon,dw-mipi-dsi");
+ if (!np)
+ return -ENODEV;
+
+ pdev = of_find_device_by_node(np);
+ of_node_put(np);
+ if (!pdev)
+ return -ENODEV;
+
+ return dsi_attach_primary(dsi, &pdev->dev);
+}
+
+static void secondary_unbind(struct dw_mipi_dsi *dsi)
+{
+
+}
+
+static const struct dw_mipi_dsi_funcs secondary = {
+ .get_dsi = &get_secondary_dsi,
+ .bind = &secondary_bind,
+ .unbind = &secondary_unbind,
+};
+
+static int dsi_bind(struct device *dev, struct device *primary, void *data)
+{
+ struct dw_mipi_dsi *dsi = dev_get_drvdata(dev);
+
+ return dsi->funcs->bind(dsi);
+}
+
+static void dsi_unbind(struct device *dev, struct device *primary, void *data)
+{
+ struct dw_mipi_dsi *dsi = dev_get_drvdata(dev);
+
+ dsi->funcs->unbind(dsi);
+}
+
+static const struct component_ops dsi_component_ops = {
+ .bind = dsi_bind,
+ .unbind = dsi_unbind,
+};
+
+static const struct of_device_id dw_mipi_dsi_dt_match[] = {
+ { .compatible = "verisilicon,dw-mipi-dsi", .data = &primary},
+ { .compatible = "verisilicon,dw-mipi-dsi-2nd", .data = &secondary},
+ {},
+};
+MODULE_DEVICE_TABLE(of, dw_mipi_dsi_dt_match);
+
+static int dsi_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ const struct dw_mipi_dsi_funcs *funcs;
+ struct dw_mipi_dsi *dsi;
+ struct resource *res;
+ int ret;
+
+ funcs = of_device_get_match_data(dev);
+ dsi = funcs->get_dsi(dev);
+ if (!dsi)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ dsi->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(dsi->base))
+ return -ENODEV;
+
+ dsi->pclk = devm_clk_get_optional(dev, "pclk");
+ if (IS_ERR(dsi->pclk))
+ return PTR_ERR(dsi->pclk);
+
+ dsi->dphy = devm_phy_get(dev, "dphy");
+ if (IS_ERR(dsi->dphy))
+ return PTR_ERR(dsi->dphy);
+
+ dsi->host.ops = &dw_mipi_dsi_host_ops;
+ dsi->host.dev = dev;
+ ret = mipi_dsi_host_register(&dsi->host);
+ if (ret)
+ return ret;
+
+ dsi->funcs = funcs;
+ dev_set_drvdata(dev, dsi);
+
+ pm_runtime_enable(dev);
+
+ return component_add(dev, &dsi_component_ops);
+}
+
+static int dsi_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct dw_mipi_dsi *dsi = dev_get_drvdata(dev);
+
+ mipi_dsi_host_unregister(&dsi->host);
+
+ pm_runtime_disable(dev);
+
+ component_del(dev, &dsi_component_ops);
+
+ dev_set_drvdata(dev, NULL);
+
+ return 0;
+}
+
+struct platform_driver dw_mipi_dsi_driver = {
+ .probe = dsi_probe,
+ .remove = dsi_remove,
+ .driver = {
+ .name = "dw-mipi-dsi",
+ .of_match_table = of_match_ptr(dw_mipi_dsi_dt_match),
+ },
+};
+
+MODULE_DESCRIPTION("DW MIPI DSI Controller Driver");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2020 VeriSilicon Holdings Co., Ltd.
+ */
+
+#ifndef __DW_MIPI_DSI_H_
+#define __DW_MIPI_DSI_H_
+
+extern struct platform_driver dw_mipi_dsi_driver;
+#endif /* __DW_MIPI_DSI_H_ */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Zheng Yang <zhengyang@rock-chips.com>
+ * Yakir Yang <ykk@rock-chips.com>
+ */
+
+#include <linux/irq.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/hdmi.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of_device.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_of.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+
+#include "vs_drv.h"
+
+#include "inno_hdmi.h"
+
+#define to_inno_hdmi(x) container_of(x, struct inno_hdmi, x)
+
+struct hdmi_data_info {
+ int vic;
+ bool sink_is_hdmi;
+ bool sink_has_audio;
+ unsigned int enc_in_format;
+ unsigned int enc_out_format;
+ unsigned int colorimetry;
+};
+
+struct inno_hdmi_i2c {
+ struct i2c_adapter adap;
+
+ u8 ddc_addr;
+ u8 segment_addr;
+
+ struct mutex lock;
+ struct completion cmp;
+};
+
+struct inno_hdmi {
+ struct device *dev;
+ struct drm_device *drm_dev;
+
+ int irq;
+ struct clk *pclk;
+ void __iomem *regs;
+
+ struct drm_connector connector;
+ struct drm_encoder encoder;
+
+ struct inno_hdmi_i2c *i2c;
+ struct i2c_adapter *ddc;
+
+ unsigned int tmds_rate;
+
+ struct hdmi_data_info hdmi_data;
+ struct drm_display_mode previous_mode;
+};
+
+enum {
+ CSC_ITU601_16_235_TO_RGB_0_255_8BIT,
+ CSC_ITU601_0_255_TO_RGB_0_255_8BIT,
+ CSC_ITU709_16_235_TO_RGB_0_255_8BIT,
+ CSC_RGB_0_255_TO_ITU601_16_235_8BIT,
+ CSC_RGB_0_255_TO_ITU709_16_235_8BIT,
+ CSC_RGB_0_255_TO_RGB_16_235_8BIT,
+};
+
+static const char coeff_csc[][24] = {
+ /*
+ * YUV2RGB:601 SD mode(Y[16:235], UV[16:240], RGB[0:255]):
+ * R = 1.164*Y + 1.596*V - 204
+ * G = 1.164*Y - 0.391*U - 0.813*V + 154
+ * B = 1.164*Y + 2.018*U - 258
+ */
+ {
+ 0x04, 0xa7, 0x00, 0x00, 0x06, 0x62, 0x02, 0xcc,
+ 0x04, 0xa7, 0x11, 0x90, 0x13, 0x40, 0x00, 0x9a,
+ 0x04, 0xa7, 0x08, 0x12, 0x00, 0x00, 0x03, 0x02
+ },
+ /*
+ * YUV2RGB:601 SD mode(YUV[0:255],RGB[0:255]):
+ * R = Y + 1.402*V - 248
+ * G = Y - 0.344*U - 0.714*V + 135
+ * B = Y + 1.772*U - 227
+ */
+ {
+ 0x04, 0x00, 0x00, 0x00, 0x05, 0x9b, 0x02, 0xf8,
+ 0x04, 0x00, 0x11, 0x60, 0x12, 0xdb, 0x00, 0x87,
+ 0x04, 0x00, 0x07, 0x16, 0x00, 0x00, 0x02, 0xe3
+ },
+ /*
+ * YUV2RGB:709 HD mode(Y[16:235],UV[16:240],RGB[0:255]):
+ * R = 1.164*Y + 1.793*V - 248
+ * G = 1.164*Y - 0.213*U - 0.534*V + 77
+ * B = 1.164*Y + 2.115*U - 289
+ */
+ {
+ 0x04, 0xa7, 0x00, 0x00, 0x07, 0x2c, 0x02, 0xf8,
+ 0x04, 0xa7, 0x10, 0xda, 0x12, 0x22, 0x00, 0x4d,
+ 0x04, 0xa7, 0x08, 0x74, 0x00, 0x00, 0x03, 0x21
+ },
+
+ /*
+ * RGB2YUV:601 SD mode:
+ * Cb = -0.291G - 0.148R + 0.439B + 128
+ * Y = 0.504G + 0.257R + 0.098B + 16
+ * Cr = -0.368G + 0.439R - 0.071B + 128
+ */
+ {
+ 0x11, 0x5f, 0x01, 0x82, 0x10, 0x23, 0x00, 0x80,
+ 0x02, 0x1c, 0x00, 0xa1, 0x00, 0x36, 0x00, 0x1e,
+ 0x11, 0x29, 0x10, 0x59, 0x01, 0x82, 0x00, 0x80
+ },
+ /*
+ * RGB2YUV:709 HD mode:
+ * Cb = - 0.338G - 0.101R + 0.439B + 128
+ * Y = 0.614G + 0.183R + 0.062B + 16
+ * Cr = - 0.399G + 0.439R - 0.040B + 128
+ */
+ {
+ 0x11, 0x98, 0x01, 0xc1, 0x10, 0x28, 0x00, 0x80,
+ 0x02, 0x74, 0x00, 0xbb, 0x00, 0x3f, 0x00, 0x10,
+ 0x11, 0x5a, 0x10, 0x67, 0x01, 0xc1, 0x00, 0x80
+ },
+ /*
+ * RGB[0:255]2RGB[16:235]:
+ * R' = R x (235-16)/255 + 16;
+ * G' = G x (235-16)/255 + 16;
+ * B' = B x (235-16)/255 + 16;
+ */
+ {
+ 0x00, 0x00, 0x03, 0x6F, 0x00, 0x00, 0x00, 0x10,
+ 0x03, 0x6F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,
+ 0x00, 0x00, 0x00, 0x00, 0x03, 0x6F, 0x00, 0x10
+ },
+};
+
+static inline u8 hdmi_readb(struct inno_hdmi *hdmi, u16 offset)
+{
+ return readl_relaxed(hdmi->regs + (offset) * 0x04);
+}
+
+static inline void hdmi_writeb(struct inno_hdmi *hdmi, u16 offset, u32 val)
+{
+ writel_relaxed(val, hdmi->regs + (offset) * 0x04);
+}
+
+static inline void hdmi_modb(struct inno_hdmi *hdmi, u16 offset,
+ u32 msk, u32 val)
+{
+ u8 temp = hdmi_readb(hdmi, offset) & ~msk;
+
+ temp |= val & msk;
+ hdmi_writeb(hdmi, offset, temp);
+}
+
+static void inno_hdmi_i2c_init(struct inno_hdmi *hdmi)
+{
+ int ddc_bus_freq;
+
+ ddc_bus_freq = (hdmi->tmds_rate >> 2) / HDMI_SCL_RATE;
+
+ hdmi_writeb(hdmi, DDC_BUS_FREQ_L, ddc_bus_freq & 0xFF);
+ hdmi_writeb(hdmi, DDC_BUS_FREQ_H, (ddc_bus_freq >> 8) & 0xFF);
+
+ /* Clear the EDID interrupt flag and mute the interrupt */
+ hdmi_writeb(hdmi, HDMI_INTERRUPT_MASK1, 0);
+ hdmi_writeb(hdmi, HDMI_INTERRUPT_STATUS1, m_INT_EDID_READY);
+}
+
+static void inno_hdmi_sys_power(struct inno_hdmi *hdmi, bool enable)
+{
+ if (enable)
+ hdmi_modb(hdmi, HDMI_SYS_CTRL, m_POWER, v_PWR_ON);
+ else
+ hdmi_modb(hdmi, HDMI_SYS_CTRL, m_POWER, v_PWR_OFF);
+}
+
+static void inno_hdmi_set_pwr_mode(struct inno_hdmi *hdmi, int mode)
+{
+ switch (mode) {
+ case NORMAL:
+ inno_hdmi_sys_power(hdmi, false);
+
+ hdmi_writeb(hdmi, HDMI_PHY_PRE_EMPHASIS, 0x6f);
+ hdmi_writeb(hdmi, HDMI_PHY_DRIVER, 0xbb);
+
+ hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, 0x15);
+ hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, 0x14);
+ hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, 0x10);
+ hdmi_writeb(hdmi, HDMI_PHY_CHG_PWR, 0x0f);
+ hdmi_writeb(hdmi, HDMI_PHY_SYNC, 0x00);
+ hdmi_writeb(hdmi, HDMI_PHY_SYNC, 0x01);
+
+ inno_hdmi_sys_power(hdmi, true);
+ break;
+
+ case LOWER_PWR:
+ inno_hdmi_sys_power(hdmi, false);
+ hdmi_writeb(hdmi, HDMI_PHY_DRIVER, 0x00);
+ hdmi_writeb(hdmi, HDMI_PHY_PRE_EMPHASIS, 0x00);
+ hdmi_writeb(hdmi, HDMI_PHY_CHG_PWR, 0x00);
+ hdmi_writeb(hdmi, HDMI_PHY_SYS_CTL, 0x15);
+
+ break;
+
+ default:
+ DRM_DEV_ERROR(hdmi->dev, "Unknown power mode %d\n", mode);
+ }
+}
+
+static void inno_hdmi_reset(struct inno_hdmi *hdmi)
+{
+ u32 val;
+ u32 msk;
+
+ hdmi_modb(hdmi, HDMI_SYS_CTRL, m_RST_DIGITAL, v_NOT_RST_DIGITAL);
+ udelay(100);
+
+ hdmi_modb(hdmi, HDMI_SYS_CTRL, m_RST_ANALOG, v_NOT_RST_ANALOG);
+ udelay(100);
+
+ msk = m_REG_CLK_INV | m_REG_CLK_SOURCE | m_POWER | m_INT_POL;
+ val = v_REG_CLK_INV | v_REG_CLK_SOURCE_SYS | v_PWR_ON | v_INT_POL_HIGH;
+ hdmi_modb(hdmi, HDMI_SYS_CTRL, msk, val);
+
+ inno_hdmi_set_pwr_mode(hdmi, NORMAL);
+}
+
+static int inno_hdmi_upload_frame(struct inno_hdmi *hdmi, int setup_rc,
+ union hdmi_infoframe *frame, u32 frame_index,
+ u32 mask, u32 disable, u32 enable)
+{
+ if (mask)
+ hdmi_modb(hdmi, HDMI_PACKET_SEND_AUTO, mask, disable);
+
+ hdmi_writeb(hdmi, HDMI_CONTROL_PACKET_BUF_INDEX, frame_index);
+
+ if (setup_rc >= 0) {
+ u8 packed_frame[HDMI_MAXIMUM_INFO_FRAME_SIZE];
+ ssize_t rc, i;
+
+ rc = hdmi_infoframe_pack(frame, packed_frame,
+ sizeof(packed_frame));
+ if (rc < 0)
+ return rc;
+
+ for (i = 0; i < rc; i++)
+ hdmi_writeb(hdmi, HDMI_CONTROL_PACKET_ADDR + i,
+ packed_frame[i]);
+
+ if (mask)
+ hdmi_modb(hdmi, HDMI_PACKET_SEND_AUTO, mask, enable);
+ }
+
+ return setup_rc;
+}
+
+static int inno_hdmi_config_video_vsi(struct inno_hdmi *hdmi,
+ struct drm_display_mode *mode)
+{
+ union hdmi_infoframe frame;
+ int rc;
+
+ rc = drm_hdmi_vendor_infoframe_from_display_mode(&frame.vendor.hdmi,
+ &hdmi->connector,
+ mode);
+
+ return inno_hdmi_upload_frame(hdmi, rc, &frame, INFOFRAME_VSI,
+ m_PACKET_VSI_EN, v_PACKET_VSI_EN(0), v_PACKET_VSI_EN(1));
+}
+
+static int inno_hdmi_config_video_avi(struct inno_hdmi *hdmi,
+ struct drm_display_mode *mode)
+{
+ union hdmi_infoframe frame;
+ int rc;
+
+ rc = drm_hdmi_avi_infoframe_from_display_mode(&frame.avi,
+ &hdmi->connector,
+ mode);
+
+ if (hdmi->hdmi_data.enc_out_format == HDMI_COLORSPACE_YUV444)
+ frame.avi.colorspace = HDMI_COLORSPACE_YUV444;
+ else if (hdmi->hdmi_data.enc_out_format == HDMI_COLORSPACE_YUV422)
+ frame.avi.colorspace = HDMI_COLORSPACE_YUV422;
+ else
+ frame.avi.colorspace = HDMI_COLORSPACE_RGB;
+
+ return inno_hdmi_upload_frame(hdmi, rc, &frame, INFOFRAME_AVI, 0, 0, 0);
+}
+
+static int inno_hdmi_config_video_csc(struct inno_hdmi *hdmi)
+{
+ struct hdmi_data_info *data = &hdmi->hdmi_data;
+ int c0_c2_change = 0;
+ int csc_enable = 0;
+ int csc_mode = 0;
+ int auto_csc = 0;
+ int value;
+ int i;
+
+ /* Input video mode is SDR RGB24bit, data enable signal from external */
+ hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL1, v_DE_EXTERNAL |
+ v_VIDEO_INPUT_FORMAT(VIDEO_INPUT_SDR_RGB444));
+
+ /* Input color hardcode to RGB, and output color hardcode to RGB888 */
+ value = v_VIDEO_INPUT_BITS(VIDEO_INPUT_8BITS) |
+ v_VIDEO_OUTPUT_COLOR(0) |
+ v_VIDEO_INPUT_CSP(0);
+ hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL2, value);
+
+ if (data->enc_in_format == data->enc_out_format) {
+ if ((data->enc_in_format == HDMI_COLORSPACE_RGB) ||
+ (data->enc_in_format >= HDMI_COLORSPACE_YUV444)) {
+ value = v_SOF_DISABLE | v_COLOR_DEPTH_NOT_INDICATED(1);
+ hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL3, value);
+
+ hdmi_modb(hdmi, HDMI_VIDEO_CONTRL,
+ m_VIDEO_AUTO_CSC | m_VIDEO_C0_C2_SWAP,
+ v_VIDEO_AUTO_CSC(AUTO_CSC_DISABLE) |
+ v_VIDEO_C0_C2_SWAP(C0_C2_CHANGE_DISABLE));
+ return 0;
+ }
+ }
+
+ if (data->colorimetry == HDMI_COLORIMETRY_ITU_601) {
+ if ((data->enc_in_format == HDMI_COLORSPACE_RGB) &&
+ (data->enc_out_format == HDMI_COLORSPACE_YUV444)) {
+ csc_mode = CSC_RGB_0_255_TO_ITU601_16_235_8BIT;
+ auto_csc = AUTO_CSC_DISABLE;
+ c0_c2_change = C0_C2_CHANGE_DISABLE;
+ csc_enable = v_CSC_ENABLE;
+ } else if ((data->enc_in_format == HDMI_COLORSPACE_YUV444) &&
+ (data->enc_out_format == HDMI_COLORSPACE_RGB)) {
+ csc_mode = CSC_ITU601_16_235_TO_RGB_0_255_8BIT;
+ auto_csc = AUTO_CSC_ENABLE;
+ c0_c2_change = C0_C2_CHANGE_DISABLE;
+ csc_enable = v_CSC_DISABLE;
+ }
+ } else {
+ if ((data->enc_in_format == HDMI_COLORSPACE_RGB) &&
+ (data->enc_out_format == HDMI_COLORSPACE_YUV444)) {
+ csc_mode = CSC_RGB_0_255_TO_ITU709_16_235_8BIT;
+ auto_csc = AUTO_CSC_DISABLE;
+ c0_c2_change = C0_C2_CHANGE_DISABLE;
+ csc_enable = v_CSC_ENABLE;
+ } else if ((data->enc_in_format == HDMI_COLORSPACE_YUV444) &&
+ (data->enc_out_format == HDMI_COLORSPACE_RGB)) {
+ csc_mode = CSC_ITU709_16_235_TO_RGB_0_255_8BIT;
+ auto_csc = AUTO_CSC_ENABLE;
+ c0_c2_change = C0_C2_CHANGE_DISABLE;
+ csc_enable = v_CSC_DISABLE;
+ }
+ }
+
+ for (i = 0; i < 24; i++)
+ hdmi_writeb(hdmi, HDMI_VIDEO_CSC_COEF + i,
+ coeff_csc[csc_mode][i]);
+
+ value = v_SOF_DISABLE | csc_enable | v_COLOR_DEPTH_NOT_INDICATED(1);
+ hdmi_writeb(hdmi, HDMI_VIDEO_CONTRL3, value);
+ hdmi_modb(hdmi, HDMI_VIDEO_CONTRL, m_VIDEO_AUTO_CSC |
+ m_VIDEO_C0_C2_SWAP, v_VIDEO_AUTO_CSC(auto_csc) |
+ v_VIDEO_C0_C2_SWAP(c0_c2_change));
+
+ return 0;
+}
+
+static int inno_hdmi_config_video_timing(struct inno_hdmi *hdmi,
+ struct drm_display_mode *mode)
+{
+ int value;
+
+ /* Set detail external video timing polarity and interlace mode */
+ value = v_EXTERANL_VIDEO(1);
+ value |= mode->flags & DRM_MODE_FLAG_PHSYNC ?
+ v_HSYNC_POLARITY(1) : v_HSYNC_POLARITY(0);
+ value |= mode->flags & DRM_MODE_FLAG_PVSYNC ?
+ v_VSYNC_POLARITY(1) : v_VSYNC_POLARITY(0);
+ value |= mode->flags & DRM_MODE_FLAG_INTERLACE ?
+ v_INETLACE(1) : v_INETLACE(0);
+ hdmi_writeb(hdmi, HDMI_VIDEO_TIMING_CTL, value);
+
+ /* Set detail external video timing */
+ value = mode->htotal;
+ hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HTOTAL_L, value & 0xFF);
+ hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HTOTAL_H, (value >> 8) & 0xFF);
+
+ value = mode->htotal - mode->hdisplay;
+ hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HBLANK_L, value & 0xFF);
+ hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HBLANK_H, (value >> 8) & 0xFF);
+
+ value = mode->hsync_start - mode->hdisplay;
+ hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDELAY_L, value & 0xFF);
+ hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDELAY_H, (value >> 8) & 0xFF);
+
+ value = mode->hsync_end - mode->hsync_start;
+ hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDURATION_L, value & 0xFF);
+ hdmi_writeb(hdmi, HDMI_VIDEO_EXT_HDURATION_H, (value >> 8) & 0xFF);
+
+ value = mode->vtotal;
+ hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VTOTAL_L, value & 0xFF);
+ hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VTOTAL_H, (value >> 8) & 0xFF);
+
+ value = mode->vtotal - mode->vdisplay;
+ hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VBLANK, value & 0xFF);
+
+ value = mode->vsync_start - mode->vdisplay;
+ hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VDELAY, value & 0xFF);
+
+ value = mode->vsync_end - mode->vsync_start;
+ hdmi_writeb(hdmi, HDMI_VIDEO_EXT_VDURATION, value & 0xFF);
+
+ hdmi_writeb(hdmi, HDMI_PHY_PRE_DIV_RATIO, 0x1e);
+ hdmi_writeb(hdmi, HDMI_PHY_FEEDBACK_DIV_RATIO_LOW, 0x2c);
+ hdmi_writeb(hdmi, HDMI_PHY_FEEDBACK_DIV_RATIO_HIGH, 0x01);
+
+ return 0;
+}
+
+static int inno_hdmi_setup(struct inno_hdmi *hdmi,
+ struct drm_display_mode *mode)
+{
+ hdmi->hdmi_data.vic = drm_match_cea_mode(mode);
+
+ hdmi->hdmi_data.enc_in_format = HDMI_COLORSPACE_RGB;
+ hdmi->hdmi_data.enc_out_format = HDMI_COLORSPACE_RGB;
+
+ if ((hdmi->hdmi_data.vic == 6) || (hdmi->hdmi_data.vic == 7) ||
+ (hdmi->hdmi_data.vic == 21) || (hdmi->hdmi_data.vic == 22) ||
+ (hdmi->hdmi_data.vic == 2) || (hdmi->hdmi_data.vic == 3) ||
+ (hdmi->hdmi_data.vic == 17) || (hdmi->hdmi_data.vic == 18))
+ hdmi->hdmi_data.colorimetry = HDMI_COLORIMETRY_ITU_601;
+ else
+ hdmi->hdmi_data.colorimetry = HDMI_COLORIMETRY_ITU_709;
+
+ /* Mute video and audio output */
+ hdmi_modb(hdmi, HDMI_AV_MUTE, m_AUDIO_MUTE | m_VIDEO_BLACK,
+ v_AUDIO_MUTE(1) | v_VIDEO_MUTE(1));
+
+ /* Set HDMI Mode */
+ hdmi_writeb(hdmi, HDMI_HDCP_CTRL,
+ v_HDMI_DVI(hdmi->hdmi_data.sink_is_hdmi));
+
+ inno_hdmi_config_video_timing(hdmi, mode);
+
+ inno_hdmi_config_video_csc(hdmi);
+
+ if (hdmi->hdmi_data.sink_is_hdmi) {
+ inno_hdmi_config_video_avi(hdmi, mode);
+ inno_hdmi_config_video_vsi(hdmi, mode);
+ }
+
+ /*
+ * When IP controller have configured to an accurate video
+ * timing, then the TMDS clock source would be switched to
+ * DCLK_LCDC, so we need to init the TMDS rate to mode pixel
+ * clock rate, and reconfigure the DDC clock.
+ */
+ hdmi->tmds_rate = mode->clock * 1000;
+ inno_hdmi_i2c_init(hdmi);
+
+ /* Unmute video and audio output */
+ hdmi_modb(hdmi, HDMI_AV_MUTE, m_AUDIO_MUTE | m_VIDEO_BLACK,
+ v_AUDIO_MUTE(0) | v_VIDEO_MUTE(0));
+
+ return 0;
+}
+
+static void inno_hdmi_encoder_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adj_mode)
+{
+ struct inno_hdmi *hdmi = to_inno_hdmi(encoder);
+
+ inno_hdmi_setup(hdmi, adj_mode);
+
+ /* Store the display mode for plugin/DPMS poweron events */
+ memcpy(&hdmi->previous_mode, adj_mode, sizeof(hdmi->previous_mode));
+}
+
+static void inno_hdmi_encoder_enable(struct drm_encoder *encoder)
+{
+ struct inno_hdmi *hdmi = to_inno_hdmi(encoder);
+
+ inno_hdmi_set_pwr_mode(hdmi, NORMAL);
+}
+
+static void inno_hdmi_encoder_disable(struct drm_encoder *encoder)
+{
+ struct inno_hdmi *hdmi = to_inno_hdmi(encoder);
+
+ inno_hdmi_set_pwr_mode(hdmi, LOWER_PWR);
+}
+
+static bool inno_hdmi_encoder_mode_fixup(struct drm_encoder *encoder,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adj_mode)
+{
+ return true;
+}
+
+static int
+inno_hdmi_encoder_atomic_check(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ //struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state);
+
+ //s->output_mode = ROCKCHIP_OUT_MODE_P888;
+ //s->output_type = DRM_MODE_CONNECTOR_HDMIA;
+
+ return 0;
+}
+
+static struct drm_encoder_helper_funcs inno_hdmi_encoder_helper_funcs = {
+ .enable = inno_hdmi_encoder_enable,
+ .disable = inno_hdmi_encoder_disable,
+ .mode_fixup = inno_hdmi_encoder_mode_fixup,
+ .mode_set = inno_hdmi_encoder_mode_set,
+ .atomic_check = inno_hdmi_encoder_atomic_check,
+};
+
+static enum drm_connector_status
+inno_hdmi_connector_detect(struct drm_connector *connector, bool force)
+{
+ struct inno_hdmi *hdmi = to_inno_hdmi(connector);
+
+ return (hdmi_readb(hdmi, HDMI_STATUS) & m_HOTPLUG) ?
+ connector_status_connected : connector_status_disconnected;
+}
+
+static int inno_hdmi_connector_get_modes(struct drm_connector *connector)
+{
+ struct inno_hdmi *hdmi = to_inno_hdmi(connector);
+ struct edid *edid;
+ int ret = 0;
+
+ if (!hdmi->ddc)
+ return 0;
+
+ edid = drm_get_edid(connector, hdmi->ddc);
+ if (edid) {
+ hdmi->hdmi_data.sink_is_hdmi = drm_detect_hdmi_monitor(edid);
+ hdmi->hdmi_data.sink_has_audio = drm_detect_monitor_audio(edid);
+ drm_connector_update_edid_property(connector, edid);
+ ret = drm_add_edid_modes(connector, edid);
+ kfree(edid);
+ }
+
+ return ret;
+}
+
+static enum drm_mode_status
+inno_hdmi_connector_mode_valid(struct drm_connector *connector,
+ struct drm_display_mode *mode)
+{
+ return MODE_OK;
+}
+
+static int
+inno_hdmi_probe_single_connector_modes(struct drm_connector *connector,
+ uint32_t maxX, uint32_t maxY)
+{
+ return drm_helper_probe_single_connector_modes(connector, 1920, 1080);
+}
+
+static void inno_hdmi_connector_destroy(struct drm_connector *connector)
+{
+ drm_connector_unregister(connector);
+ drm_connector_cleanup(connector);
+}
+
+static const struct drm_connector_funcs inno_hdmi_connector_funcs = {
+ .fill_modes = inno_hdmi_probe_single_connector_modes,
+ .detect = inno_hdmi_connector_detect,
+ .destroy = inno_hdmi_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 struct drm_connector_helper_funcs inno_hdmi_connector_helper_funcs = {
+ .get_modes = inno_hdmi_connector_get_modes,
+ .mode_valid = inno_hdmi_connector_mode_valid,
+};
+
+static int inno_hdmi_register(struct drm_device *drm, struct inno_hdmi *hdmi)
+{
+ struct drm_encoder *encoder = &hdmi->encoder;
+ struct device *dev = hdmi->dev;
+
+ encoder->possible_crtcs = 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 (encoder->possible_crtcs == 0)
+ return -EPROBE_DEFER;
+
+ drm_encoder_helper_add(encoder, &inno_hdmi_encoder_helper_funcs);
+ drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS);
+
+ hdmi->connector.polled = DRM_CONNECTOR_POLL_HPD;
+
+ drm_connector_helper_add(&hdmi->connector,
+ &inno_hdmi_connector_helper_funcs);
+ drm_connector_init_with_ddc(drm, &hdmi->connector,
+ &inno_hdmi_connector_funcs,
+ DRM_MODE_CONNECTOR_HDMIA,
+ hdmi->ddc);
+
+ drm_connector_attach_encoder(&hdmi->connector, encoder);
+
+ return 0;
+}
+
+static irqreturn_t inno_hdmi_i2c_irq(struct inno_hdmi *hdmi)
+{
+ struct inno_hdmi_i2c *i2c = hdmi->i2c;
+ u8 stat;
+
+ stat = hdmi_readb(hdmi, HDMI_INTERRUPT_STATUS1);
+ if (!(stat & m_INT_EDID_READY))
+ return IRQ_NONE;
+
+ /* Clear HDMI EDID interrupt flag */
+ hdmi_writeb(hdmi, HDMI_INTERRUPT_STATUS1, m_INT_EDID_READY);
+
+ complete(&i2c->cmp);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t inno_hdmi_hardirq(int irq, void *dev_id)
+{
+ struct inno_hdmi *hdmi = dev_id;
+ irqreturn_t ret = IRQ_NONE;
+ u8 interrupt;
+
+ if (hdmi->i2c)
+ ret = inno_hdmi_i2c_irq(hdmi);
+
+ interrupt = hdmi_readb(hdmi, HDMI_STATUS);
+ if (interrupt & m_INT_HOTPLUG) {
+ hdmi_modb(hdmi, HDMI_STATUS, m_INT_HOTPLUG, m_INT_HOTPLUG);
+ ret = IRQ_WAKE_THREAD;
+ }
+
+ return ret;
+}
+
+static irqreturn_t inno_hdmi_irq(int irq, void *dev_id)
+{
+ struct inno_hdmi *hdmi = dev_id;
+
+ drm_helper_hpd_irq_event(hdmi->connector.dev);
+
+ return IRQ_HANDLED;
+}
+
+static int inno_hdmi_i2c_read(struct inno_hdmi *hdmi, struct i2c_msg *msgs)
+{
+ int length = msgs->len;
+ u8 *buf = msgs->buf;
+ int ret;
+
+ ret = wait_for_completion_timeout(&hdmi->i2c->cmp, HZ / 10);
+ if (!ret)
+ return -EAGAIN;
+
+ while (length--)
+ *buf++ = hdmi_readb(hdmi, HDMI_EDID_FIFO_ADDR);
+
+ return 0;
+}
+
+static int inno_hdmi_i2c_write(struct inno_hdmi *hdmi, struct i2c_msg *msgs)
+{
+ /*
+ * The DDC module only support read EDID message, so
+ * we assume that each word write to this i2c adapter
+ * should be the offset of EDID word address.
+ */
+ if ((msgs->len != 1) ||
+ ((msgs->addr != DDC_ADDR) && (msgs->addr != DDC_SEGMENT_ADDR)))
+ return -EINVAL;
+
+ reinit_completion(&hdmi->i2c->cmp);
+
+ if (msgs->addr == DDC_SEGMENT_ADDR)
+ hdmi->i2c->segment_addr = msgs->buf[0];
+ if (msgs->addr == DDC_ADDR)
+ hdmi->i2c->ddc_addr = msgs->buf[0];
+
+ /* Set edid fifo first addr */
+ hdmi_writeb(hdmi, HDMI_EDID_FIFO_OFFSET, 0x00);
+
+ /* Set edid word address 0x00/0x80 */
+ hdmi_writeb(hdmi, HDMI_EDID_WORD_ADDR, hdmi->i2c->ddc_addr);
+
+ /* Set edid segment pointer */
+ hdmi_writeb(hdmi, HDMI_EDID_SEGMENT_POINTER, hdmi->i2c->segment_addr);
+
+ return 0;
+}
+
+static int inno_hdmi_i2c_xfer(struct i2c_adapter *adap,
+ struct i2c_msg *msgs, int num)
+{
+ struct inno_hdmi *hdmi = i2c_get_adapdata(adap);
+ struct inno_hdmi_i2c *i2c = hdmi->i2c;
+ int i, ret = 0;
+
+ mutex_lock(&i2c->lock);
+
+ /* Clear the EDID interrupt flag and unmute the interrupt */
+ hdmi_writeb(hdmi, HDMI_INTERRUPT_MASK1, m_INT_EDID_READY);
+ hdmi_writeb(hdmi, HDMI_INTERRUPT_STATUS1, m_INT_EDID_READY);
+
+ for (i = 0; i < num; i++) {
+ DRM_DEV_DEBUG(hdmi->dev,
+ "xfer: num: %d/%d, len: %d, flags: %#x\n",
+ i + 1, num, msgs[i].len, msgs[i].flags);
+
+ if (msgs[i].flags & I2C_M_RD)
+ ret = inno_hdmi_i2c_read(hdmi, &msgs[i]);
+ else
+ ret = inno_hdmi_i2c_write(hdmi, &msgs[i]);
+
+ if (ret < 0)
+ break;
+ }
+
+ if (!ret)
+ ret = num;
+
+ /* Mute HDMI EDID interrupt */
+ hdmi_writeb(hdmi, HDMI_INTERRUPT_MASK1, 0);
+
+ mutex_unlock(&i2c->lock);
+
+ return ret;
+}
+
+static u32 inno_hdmi_i2c_func(struct i2c_adapter *adapter)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm inno_hdmi_algorithm = {
+ .master_xfer = inno_hdmi_i2c_xfer,
+ .functionality = inno_hdmi_i2c_func,
+};
+
+static struct i2c_adapter *inno_hdmi_i2c_adapter(struct inno_hdmi *hdmi)
+{
+ struct i2c_adapter *adap;
+ struct inno_hdmi_i2c *i2c;
+ int ret;
+
+ i2c = devm_kzalloc(hdmi->dev, sizeof(*i2c), GFP_KERNEL);
+ if (!i2c)
+ return ERR_PTR(-ENOMEM);
+
+ mutex_init(&i2c->lock);
+ init_completion(&i2c->cmp);
+
+ adap = &i2c->adap;
+ adap->class = I2C_CLASS_DDC;
+ adap->owner = THIS_MODULE;
+ adap->dev.parent = hdmi->dev;
+ adap->dev.of_node = hdmi->dev->of_node;
+ adap->algo = &inno_hdmi_algorithm;
+ strlcpy(adap->name, "Inno HDMI", sizeof(adap->name));
+ i2c_set_adapdata(adap, hdmi);
+
+ ret = i2c_add_adapter(adap);
+ if (ret) {
+ dev_warn(hdmi->dev, "cannot add %s I2C adapter\n", adap->name);
+ devm_kfree(hdmi->dev, i2c);
+ return ERR_PTR(ret);
+ }
+
+ hdmi->i2c = i2c;
+
+ DRM_DEV_INFO(hdmi->dev, "registered %s I2C bus driver\n", adap->name);
+
+ return adap;
+}
+
+static int inno_hdmi_bind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct drm_device *drm = data;
+ struct inno_hdmi *hdmi;
+ struct resource *iores;
+ int irq;
+ int ret;
+
+ hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
+ if (!hdmi)
+ return -ENOMEM;
+
+ hdmi->dev = dev;
+ hdmi->drm_dev = drm;
+
+ iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ hdmi->regs = devm_ioremap_resource(dev, iores);
+ if (IS_ERR(hdmi->regs))
+ return PTR_ERR(hdmi->regs);
+
+ hdmi->pclk = devm_clk_get(hdmi->dev, "pclk");
+ if (IS_ERR(hdmi->pclk)) {
+ DRM_DEV_ERROR(hdmi->dev, "Unable to get HDMI pclk clk\n");
+ return PTR_ERR(hdmi->pclk);
+ }
+
+ ret = clk_prepare_enable(hdmi->pclk);
+ if (ret) {
+ DRM_DEV_ERROR(hdmi->dev,
+ "Cannot enable HDMI pclk clock: %d\n", ret);
+ return ret;
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ ret = irq;
+ goto err_disable_clk;
+ }
+
+ inno_hdmi_reset(hdmi);
+
+ hdmi->ddc = inno_hdmi_i2c_adapter(hdmi);
+ if (IS_ERR(hdmi->ddc)) {
+ ret = PTR_ERR(hdmi->ddc);
+ hdmi->ddc = NULL;
+ goto err_disable_clk;
+ }
+
+ /*
+ * When IP controller haven't configured to an accurate video
+ * timing, then the TMDS clock source would be switched to
+ * PCLK_HDMI, so we need to init the TMDS rate to PCLK rate,
+ * and reconfigure the DDC clock.
+ */
+ hdmi->tmds_rate = clk_get_rate(hdmi->pclk);
+ inno_hdmi_i2c_init(hdmi);
+
+ ret = inno_hdmi_register(drm, hdmi);
+ if (ret)
+ goto err_put_adapter;
+
+ dev_set_drvdata(dev, hdmi);
+
+ /* Unmute hotplug interrupt */
+ hdmi_modb(hdmi, HDMI_STATUS, m_MASK_INT_HOTPLUG, v_MASK_INT_HOTPLUG(1));
+
+ ret = devm_request_threaded_irq(dev, irq, inno_hdmi_hardirq,
+ inno_hdmi_irq, IRQF_SHARED,
+ dev_name(dev), hdmi);
+ if (ret < 0)
+ goto err_cleanup_hdmi;
+
+ return 0;
+err_cleanup_hdmi:
+ hdmi->connector.funcs->destroy(&hdmi->connector);
+ hdmi->encoder.funcs->destroy(&hdmi->encoder);
+err_put_adapter:
+ i2c_put_adapter(hdmi->ddc);
+err_disable_clk:
+ clk_disable_unprepare(hdmi->pclk);
+ return ret;
+}
+
+static void inno_hdmi_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct inno_hdmi *hdmi = dev_get_drvdata(dev);
+
+ hdmi->connector.funcs->destroy(&hdmi->connector);
+ hdmi->encoder.funcs->destroy(&hdmi->encoder);
+
+ i2c_put_adapter(hdmi->ddc);
+ clk_disable_unprepare(hdmi->pclk);
+}
+
+static const struct component_ops inno_hdmi_ops = {
+ .bind = inno_hdmi_bind,
+ .unbind = inno_hdmi_unbind,
+};
+
+static int inno_hdmi_probe(struct platform_device *pdev)
+{
+ return component_add(&pdev->dev, &inno_hdmi_ops);
+}
+
+static int inno_hdmi_remove(struct platform_device *pdev)
+{
+ component_del(&pdev->dev, &inno_hdmi_ops);
+
+ return 0;
+}
+
+static const struct of_device_id inno_hdmi_dt_ids[] = {
+ { .compatible = "rockchip,rk3036-inno-hdmi",
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, inno_hdmi_dt_ids);
+
+struct platform_driver inno_hdmi_driver = {
+ .probe = inno_hdmi_probe,
+ .remove = inno_hdmi_remove,
+ .driver = {
+ .name = "innohdmi-rockchip",
+ .of_match_table = inno_hdmi_dt_ids,
+ },
+};
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
+ * Zheng Yang <zhengyang@rock-chips.com>
+ * Yakir Yang <ykk@rock-chips.com>
+ */
+
+#ifndef __INNO_HDMI_H__
+#define __INNO_HDMI_H__
+
+#define DDC_SEGMENT_ADDR 0x30
+
+enum PWR_MODE {
+ NORMAL,
+ LOWER_PWR,
+};
+
+#define HDMI_SCL_RATE (100*1000)
+#define DDC_BUS_FREQ_L 0x4b
+#define DDC_BUS_FREQ_H 0x4c
+
+#define HDMI_SYS_CTRL 0x00
+#define m_RST_ANALOG (1 << 6)
+#define v_RST_ANALOG (0 << 6)
+#define v_NOT_RST_ANALOG (1 << 6)
+#define m_RST_DIGITAL (1 << 5)
+#define v_RST_DIGITAL (0 << 5)
+#define v_NOT_RST_DIGITAL (1 << 5)
+#define m_REG_CLK_INV (1 << 4)
+#define v_REG_CLK_NOT_INV (0 << 4)
+#define v_REG_CLK_INV (1 << 4)
+#define m_VCLK_INV (1 << 3)
+#define v_VCLK_NOT_INV (0 << 3)
+#define v_VCLK_INV (1 << 3)
+#define m_REG_CLK_SOURCE (1 << 2)
+#define v_REG_CLK_SOURCE_TMDS (0 << 2)
+#define v_REG_CLK_SOURCE_SYS (1 << 2)
+#define m_POWER (1 << 1)
+#define v_PWR_ON (0 << 1)
+#define v_PWR_OFF (1 << 1)
+#define m_INT_POL (1 << 0)
+#define v_INT_POL_HIGH 1
+#define v_INT_POL_LOW 0
+
+#define HDMI_VIDEO_CONTRL1 0x01
+#define m_VIDEO_INPUT_FORMAT (7 << 1)
+#define m_DE_SOURCE (1 << 0)
+#define v_VIDEO_INPUT_FORMAT(n) (n << 1)
+#define v_DE_EXTERNAL 1
+#define v_DE_INTERNAL 0
+enum {
+ VIDEO_INPUT_SDR_RGB444 = 0,
+ VIDEO_INPUT_DDR_RGB444 = 5,
+ VIDEO_INPUT_DDR_YCBCR422 = 6
+};
+
+#define HDMI_VIDEO_CONTRL2 0x02
+#define m_VIDEO_OUTPUT_COLOR (3 << 6)
+#define m_VIDEO_INPUT_BITS (3 << 4)
+#define m_VIDEO_INPUT_CSP (1 << 0)
+#define v_VIDEO_OUTPUT_COLOR(n) (((n) & 0x3) << 6)
+#define v_VIDEO_INPUT_BITS(n) (n << 4)
+#define v_VIDEO_INPUT_CSP(n) (n << 0)
+enum {
+ VIDEO_INPUT_12BITS = 0,
+ VIDEO_INPUT_10BITS = 1,
+ VIDEO_INPUT_REVERT = 2,
+ VIDEO_INPUT_8BITS = 3,
+};
+
+#define HDMI_VIDEO_CONTRL 0x03
+#define m_VIDEO_AUTO_CSC (1 << 7)
+#define v_VIDEO_AUTO_CSC(n) (n << 7)
+#define m_VIDEO_C0_C2_SWAP (1 << 0)
+#define v_VIDEO_C0_C2_SWAP(n) (n << 0)
+enum {
+ C0_C2_CHANGE_ENABLE = 0,
+ C0_C2_CHANGE_DISABLE = 1,
+ AUTO_CSC_DISABLE = 0,
+ AUTO_CSC_ENABLE = 1,
+};
+
+#define HDMI_VIDEO_CONTRL3 0x04
+#define m_COLOR_DEPTH_NOT_INDICATED (1 << 4)
+#define m_SOF (1 << 3)
+#define m_COLOR_RANGE (1 << 2)
+#define m_CSC (1 << 0)
+#define v_COLOR_DEPTH_NOT_INDICATED(n) ((n) << 4)
+#define v_SOF_ENABLE (0 << 3)
+#define v_SOF_DISABLE (1 << 3)
+#define v_COLOR_RANGE_FULL (1 << 2)
+#define v_COLOR_RANGE_LIMITED (0 << 2)
+#define v_CSC_ENABLE 1
+#define v_CSC_DISABLE 0
+
+#define HDMI_AV_MUTE 0x05
+#define m_AVMUTE_CLEAR (1 << 7)
+#define m_AVMUTE_ENABLE (1 << 6)
+#define m_AUDIO_MUTE (1 << 1)
+#define m_VIDEO_BLACK (1 << 0)
+#define v_AVMUTE_CLEAR(n) (n << 7)
+#define v_AVMUTE_ENABLE(n) (n << 6)
+#define v_AUDIO_MUTE(n) (n << 1)
+#define v_VIDEO_MUTE(n) (n << 0)
+
+#define HDMI_VIDEO_TIMING_CTL 0x08
+#define v_HSYNC_POLARITY(n) (n << 3)
+#define v_VSYNC_POLARITY(n) (n << 2)
+#define v_INETLACE(n) (n << 1)
+#define v_EXTERANL_VIDEO(n) (n << 0)
+
+#define HDMI_VIDEO_EXT_HTOTAL_L 0x09
+#define HDMI_VIDEO_EXT_HTOTAL_H 0x0a
+#define HDMI_VIDEO_EXT_HBLANK_L 0x0b
+#define HDMI_VIDEO_EXT_HBLANK_H 0x0c
+#define HDMI_VIDEO_EXT_HDELAY_L 0x0d
+#define HDMI_VIDEO_EXT_HDELAY_H 0x0e
+#define HDMI_VIDEO_EXT_HDURATION_L 0x0f
+#define HDMI_VIDEO_EXT_HDURATION_H 0x10
+#define HDMI_VIDEO_EXT_VTOTAL_L 0x11
+#define HDMI_VIDEO_EXT_VTOTAL_H 0x12
+#define HDMI_VIDEO_EXT_VBLANK 0x13
+#define HDMI_VIDEO_EXT_VDELAY 0x14
+#define HDMI_VIDEO_EXT_VDURATION 0x15
+
+#define HDMI_VIDEO_CSC_COEF 0x18
+
+#define HDMI_AUDIO_CTRL1 0x35
+enum {
+ CTS_SOURCE_INTERNAL = 0,
+ CTS_SOURCE_EXTERNAL = 1,
+};
+#define v_CTS_SOURCE(n) (n << 7)
+
+enum {
+ DOWNSAMPLE_DISABLE = 0,
+ DOWNSAMPLE_1_2 = 1,
+ DOWNSAMPLE_1_4 = 2,
+};
+#define v_DOWN_SAMPLE(n) (n << 5)
+
+enum {
+ AUDIO_SOURCE_IIS = 0,
+ AUDIO_SOURCE_SPDIF = 1,
+};
+#define v_AUDIO_SOURCE(n) (n << 3)
+
+#define v_MCLK_ENABLE(n) (n << 2)
+enum {
+ MCLK_128FS = 0,
+ MCLK_256FS = 1,
+ MCLK_384FS = 2,
+ MCLK_512FS = 3,
+};
+#define v_MCLK_RATIO(n) (n)
+
+#define AUDIO_SAMPLE_RATE 0x37
+enum {
+ AUDIO_32K = 0x3,
+ AUDIO_441K = 0x0,
+ AUDIO_48K = 0x2,
+ AUDIO_882K = 0x8,
+ AUDIO_96K = 0xa,
+ AUDIO_1764K = 0xc,
+ AUDIO_192K = 0xe,
+};
+
+#define AUDIO_I2S_MODE 0x38
+enum {
+ I2S_CHANNEL_1_2 = 1,
+ I2S_CHANNEL_3_4 = 3,
+ I2S_CHANNEL_5_6 = 7,
+ I2S_CHANNEL_7_8 = 0xf
+};
+#define v_I2S_CHANNEL(n) ((n) << 2)
+enum {
+ I2S_STANDARD = 0,
+ I2S_LEFT_JUSTIFIED = 1,
+ I2S_RIGHT_JUSTIFIED = 2,
+};
+#define v_I2S_MODE(n) (n)
+
+#define AUDIO_I2S_MAP 0x39
+#define AUDIO_I2S_SWAPS_SPDIF 0x3a
+#define v_SPIDF_FREQ(n) (n)
+
+#define N_32K 0x1000
+#define N_441K 0x1880
+#define N_882K 0x3100
+#define N_1764K 0x6200
+#define N_48K 0x1800
+#define N_96K 0x3000
+#define N_192K 0x6000
+
+#define HDMI_AUDIO_CHANNEL_STATUS 0x3e
+#define m_AUDIO_STATUS_NLPCM (1 << 7)
+#define m_AUDIO_STATUS_USE (1 << 6)
+#define m_AUDIO_STATUS_COPYRIGHT (1 << 5)
+#define m_AUDIO_STATUS_ADDITION (3 << 2)
+#define m_AUDIO_STATUS_CLK_ACCURACY (2 << 0)
+#define v_AUDIO_STATUS_NLPCM(n) ((n & 1) << 7)
+#define AUDIO_N_H 0x3f
+#define AUDIO_N_M 0x40
+#define AUDIO_N_L 0x41
+
+#define HDMI_AUDIO_CTS_H 0x45
+#define HDMI_AUDIO_CTS_M 0x46
+#define HDMI_AUDIO_CTS_L 0x47
+
+#define HDMI_DDC_CLK_L 0x4b
+#define HDMI_DDC_CLK_H 0x4c
+
+#define HDMI_EDID_SEGMENT_POINTER 0x4d
+#define HDMI_EDID_WORD_ADDR 0x4e
+#define HDMI_EDID_FIFO_OFFSET 0x4f
+#define HDMI_EDID_FIFO_ADDR 0x50
+
+#define HDMI_PACKET_SEND_MANUAL 0x9c
+#define HDMI_PACKET_SEND_AUTO 0x9d
+#define m_PACKET_GCP_EN (1 << 7)
+#define m_PACKET_MSI_EN (1 << 6)
+#define m_PACKET_SDI_EN (1 << 5)
+#define m_PACKET_VSI_EN (1 << 4)
+#define v_PACKET_GCP_EN(n) ((n & 1) << 7)
+#define v_PACKET_MSI_EN(n) ((n & 1) << 6)
+#define v_PACKET_SDI_EN(n) ((n & 1) << 5)
+#define v_PACKET_VSI_EN(n) ((n & 1) << 4)
+
+#define HDMI_CONTROL_PACKET_BUF_INDEX 0x9f
+enum {
+ INFOFRAME_VSI = 0x05,
+ INFOFRAME_AVI = 0x06,
+ INFOFRAME_AAI = 0x08,
+};
+
+#define HDMI_CONTROL_PACKET_ADDR 0xa0
+#define HDMI_MAXIMUM_INFO_FRAME_SIZE 0x11
+enum {
+ AVI_COLOR_MODE_RGB = 0,
+ AVI_COLOR_MODE_YCBCR422 = 1,
+ AVI_COLOR_MODE_YCBCR444 = 2,
+ AVI_COLORIMETRY_NO_DATA = 0,
+
+ AVI_COLORIMETRY_SMPTE_170M = 1,
+ AVI_COLORIMETRY_ITU709 = 2,
+ AVI_COLORIMETRY_EXTENDED = 3,
+
+ AVI_CODED_FRAME_ASPECT_NO_DATA = 0,
+ AVI_CODED_FRAME_ASPECT_4_3 = 1,
+ AVI_CODED_FRAME_ASPECT_16_9 = 2,
+
+ ACTIVE_ASPECT_RATE_SAME_AS_CODED_FRAME = 0x08,
+ ACTIVE_ASPECT_RATE_4_3 = 0x09,
+ ACTIVE_ASPECT_RATE_16_9 = 0x0A,
+ ACTIVE_ASPECT_RATE_14_9 = 0x0B,
+};
+
+#define HDMI_HDCP_CTRL 0x52
+#define m_HDMI_DVI (1 << 1)
+#define v_HDMI_DVI(n) (n << 1)
+
+#define HDMI_INTERRUPT_MASK1 0xc0
+#define HDMI_INTERRUPT_STATUS1 0xc1
+#define m_INT_ACTIVE_VSYNC (1 << 5)
+#define m_INT_EDID_READY (1 << 2)
+
+#define HDMI_INTERRUPT_MASK2 0xc2
+#define HDMI_INTERRUPT_STATUS2 0xc3
+#define m_INT_HDCP_ERR (1 << 7)
+#define m_INT_BKSV_FLAG (1 << 6)
+#define m_INT_HDCP_OK (1 << 4)
+
+#define HDMI_STATUS 0xc8
+#define m_HOTPLUG (1 << 7)
+#define m_MASK_INT_HOTPLUG (1 << 5)
+#define m_INT_HOTPLUG (1 << 1)
+#define v_MASK_INT_HOTPLUG(n) ((n & 0x1) << 5)
+
+#define HDMI_COLORBAR 0xc9
+
+#define HDMI_PHY_SYNC 0xce
+#define HDMI_PHY_SYS_CTL 0xe0
+#define m_TMDS_CLK_SOURCE (1 << 5)
+#define v_TMDS_FROM_PLL (0 << 5)
+#define v_TMDS_FROM_GEN (1 << 5)
+#define m_PHASE_CLK (1 << 4)
+#define v_DEFAULT_PHASE (0 << 4)
+#define v_SYNC_PHASE (1 << 4)
+#define m_TMDS_CURRENT_PWR (1 << 3)
+#define v_TURN_ON_CURRENT (0 << 3)
+#define v_CAT_OFF_CURRENT (1 << 3)
+#define m_BANDGAP_PWR (1 << 2)
+#define v_BANDGAP_PWR_UP (0 << 2)
+#define v_BANDGAP_PWR_DOWN (1 << 2)
+#define m_PLL_PWR (1 << 1)
+#define v_PLL_PWR_UP (0 << 1)
+#define v_PLL_PWR_DOWN (1 << 1)
+#define m_TMDS_CHG_PWR (1 << 0)
+#define v_TMDS_CHG_PWR_UP (0 << 0)
+#define v_TMDS_CHG_PWR_DOWN (1 << 0)
+
+#define HDMI_PHY_CHG_PWR 0xe1
+#define v_CLK_CHG_PWR(n) ((n & 1) << 3)
+#define v_DATA_CHG_PWR(n) ((n & 7) << 0)
+
+#define HDMI_PHY_DRIVER 0xe2
+#define v_CLK_MAIN_DRIVER(n) (n << 4)
+#define v_DATA_MAIN_DRIVER(n) (n << 0)
+
+#define HDMI_PHY_PRE_EMPHASIS 0xe3
+#define v_PRE_EMPHASIS(n) ((n & 7) << 4)
+#define v_CLK_PRE_DRIVER(n) ((n & 3) << 2)
+#define v_DATA_PRE_DRIVER(n) ((n & 3) << 0)
+
+#define HDMI_PHY_FEEDBACK_DIV_RATIO_LOW 0xe7
+#define v_FEEDBACK_DIV_LOW(n) (n & 0xff)
+#define HDMI_PHY_FEEDBACK_DIV_RATIO_HIGH 0xe8
+#define v_FEEDBACK_DIV_HIGH(n) (n & 1)
+
+#define HDMI_PHY_PRE_DIV_RATIO 0xed
+#define v_PRE_DIV_RATIO(n) (n & 0x1f)
+
+#define HDMI_CEC_CTRL 0xd0
+#define m_ADJUST_FOR_HISENSE (1 << 6)
+#define m_REJECT_RX_BROADCAST (1 << 5)
+#define m_BUSFREETIME_ENABLE (1 << 2)
+#define m_REJECT_RX (1 << 1)
+#define m_START_TX (1 << 0)
+
+#define HDMI_CEC_DATA 0xd1
+#define HDMI_CEC_TX_OFFSET 0xd2
+#define HDMI_CEC_RX_OFFSET 0xd3
+#define HDMI_CEC_CLK_H 0xd4
+#define HDMI_CEC_CLK_L 0xd5
+#define HDMI_CEC_TX_LENGTH 0xd6
+#define HDMI_CEC_RX_LENGTH 0xd7
+#define HDMI_CEC_TX_INT_MASK 0xd8
+#define m_TX_DONE (1 << 3)
+#define m_TX_NOACK (1 << 2)
+#define m_TX_BROADCAST_REJ (1 << 1)
+#define m_TX_BUSNOTFREE (1 << 0)
+
+#define HDMI_CEC_RX_INT_MASK 0xd9
+#define m_RX_LA_ERR (1 << 4)
+#define m_RX_GLITCH (1 << 3)
+#define m_RX_DONE (1 << 0)
+
+#define HDMI_CEC_TX_INT 0xda
+#define HDMI_CEC_RX_INT 0xdb
+#define HDMI_CEC_BUSFREETIME_L 0xdc
+#define HDMI_CEC_BUSFREETIME_H 0xdd
+#define HDMI_CEC_LOGICADDR 0xde
+
+#endif /* __INNO_HDMI_H__ */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright: 2017 Cadence Design Systems, Inc.
+ *
+ * Author: Boris Brezillon <boris.brezillon@bootlin.com>
+ */
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_probe_helper.h>
+#include <video/mipi_display.h>
+#include <linux/clk.h>
+#include <linux/interrupt.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/reset.h>
+#include <linux/component.h>
+#include <linux/phy/phy.h>
+#include <linux/phy/phy-mipi-dphy.h>
+#include "vs_drv.h"
+
+
+//sysrst registers
+#define SRST_ASSERT0 0x00
+#define SRST_STATUS0 0x04
+
+#define IP_CONF 0x0
+#define SP_HS_FIFO_DEPTH(x) (((x) & GENMASK(30, 26)) >> 26)
+#define SP_LP_FIFO_DEPTH(x) (((x) & GENMASK(25, 21)) >> 21)
+#define VRS_FIFO_DEPTH(x) (((x) & GENMASK(20, 16)) >> 16)
+#define DIRCMD_FIFO_DEPTH(x) (((x) & GENMASK(15, 13)) >> 13)
+#define SDI_IFACE_32 BIT(12)
+#define INTERNAL_DATAPATH_32 (0 << 10)
+#define INTERNAL_DATAPATH_16 (1 << 10)
+#define INTERNAL_DATAPATH_8 (3 << 10)
+#define INTERNAL_DATAPATH_SIZE ((x) & GENMASK(11, 10))
+#define NUM_IFACE(x) ((((x) & GENMASK(9, 8)) >> 8) + 1)
+#define MAX_LANE_NB(x) (((x) & GENMASK(7, 6)) >> 6)
+#define RX_FIFO_DEPTH(x) ((x) & GENMASK(5, 0))
+
+#define MCTL_MAIN_DATA_CTL 0x4
+#define TE_MIPI_POLLING_EN BIT(25)
+#define TE_HW_POLLING_EN BIT(24)
+#define DISP_EOT_GEN BIT(18)
+#define HOST_EOT_GEN BIT(17)
+#define DISP_GEN_CHECKSUM BIT(16)
+#define DISP_GEN_ECC BIT(15)
+#define BTA_EN BIT(14)
+#define READ_EN BIT(13)
+#define REG_TE_EN BIT(12)
+#define IF_TE_EN(x) BIT(8 + (x))
+#define TVG_SEL BIT(6)
+#define VID_EN BIT(5)
+#define IF_VID_SELECT(x) ((x) << 2)
+#define IF_VID_SELECT_MASK GENMASK(3, 2)
+#define IF_VID_MODE BIT(1)
+#define LINK_EN BIT(0)
+
+#define MCTL_MAIN_PHY_CTL 0x8
+#define HS_INVERT_DAT(x) BIT(19 + ((x) * 2))
+#define SWAP_PINS_DAT(x) BIT(18 + ((x) * 2))
+#define HS_INVERT_CLK BIT(17)
+#define SWAP_PINS_CLK BIT(16)
+#define HS_SKEWCAL_EN BIT(15)
+#define WAIT_BURST_TIME(x) ((x) << 10)
+#define DATA_ULPM_EN(x) BIT(6 + (x))
+#define CLK_ULPM_EN BIT(5)
+#define CLK_CONTINUOUS BIT(4)
+#define DATA_LANE_EN(x) BIT((x) - 1)
+
+#define MCTL_MAIN_EN 0xc
+#define DATA_FORCE_STOP BIT(17)
+#define CLK_FORCE_STOP BIT(16)
+#define IF_EN(x) BIT(13 + (x))
+#define DATA_LANE_ULPM_REQ(l) BIT(9 + (l))
+#define CLK_LANE_ULPM_REQ BIT(8)
+#define DATA_LANE_START(x) BIT(4 + (x))
+#define CLK_LANE_EN BIT(3)
+#define PLL_START BIT(0)
+
+#define MCTL_DPHY_CFG0 0x10
+#define DPHY_C_RSTB BIT(20)
+#define DPHY_D_RSTB(x) GENMASK(15 + (x), 16)
+#define DPHY_PLL_PDN BIT(10)
+#define DPHY_CMN_PDN BIT(9)
+#define DPHY_C_PDN BIT(8)
+#define DPHY_D_PDN(x) GENMASK(3 + (x), 4)
+#define DPHY_ALL_D_PDN GENMASK(7, 4)
+#define DPHY_PLL_PSO BIT(1)
+#define DPHY_CMN_PSO BIT(0)
+
+#define MCTL_DPHY_TIMEOUT1 0x14
+#define HSTX_TIMEOUT(x) ((x) << 4)
+#define HSTX_TIMEOUT_MAX GENMASK(17, 0)
+#define CLK_DIV(x) (x)
+#define CLK_DIV_MAX GENMASK(3, 0)
+
+#define MCTL_DPHY_TIMEOUT2 0x18
+#define LPRX_TIMEOUT(x) (x)
+
+#define MCTL_ULPOUT_TIME 0x1c
+#define DATA_LANE_ULPOUT_TIME(x) ((x) << 9)
+#define CLK_LANE_ULPOUT_TIME(x) (x)
+
+#define MCTL_3DVIDEO_CTL 0x20
+#define VID_VSYNC_3D_EN BIT(7)
+#define VID_VSYNC_3D_LR BIT(5)
+#define VID_VSYNC_3D_SECOND_EN BIT(4)
+#define VID_VSYNC_3DFORMAT_LINE (0 << 2)
+#define VID_VSYNC_3DFORMAT_FRAME (1 << 2)
+#define VID_VSYNC_3DFORMAT_PIXEL (2 << 2)
+#define VID_VSYNC_3DMODE_OFF 0
+#define VID_VSYNC_3DMODE_PORTRAIT 1
+#define VID_VSYNC_3DMODE_LANDSCAPE 2
+
+#define MCTL_MAIN_STS 0x24
+#define MCTL_MAIN_STS_CTL 0x130
+#define MCTL_MAIN_STS_CLR 0x150
+#define MCTL_MAIN_STS_FLAG 0x170
+#define HS_SKEWCAL_DONE BIT(11)
+#define IF_UNTERM_PKT_ERR(x) BIT(8 + (x))
+#define LPRX_TIMEOUT_ERR BIT(7)
+#define HSTX_TIMEOUT_ERR BIT(6)
+#define DATA_LANE_RDY(l) BIT(2 + (l))
+#define CLK_LANE_RDY BIT(1)
+#define PLL_LOCKED BIT(0)
+
+#define MCTL_DPHY_ERR 0x28
+#define MCTL_DPHY_ERR_CTL1 0x148
+#define MCTL_DPHY_ERR_CLR 0x168
+#define MCTL_DPHY_ERR_FLAG 0x188
+#define ERR_CONT_LP(x, l) BIT(18 + ((x) * 4) + (l))
+#define ERR_CONTROL(l) BIT(14 + (l))
+#define ERR_SYNESC(l) BIT(10 + (l))
+#define ERR_ESC(l) BIT(6 + (l))
+
+#define MCTL_DPHY_ERR_CTL2 0x14c
+#define ERR_CONT_LP_EDGE(x, l) BIT(12 + ((x) * 4) + (l))
+#define ERR_CONTROL_EDGE(l) BIT(8 + (l))
+#define ERR_SYN_ESC_EDGE(l) BIT(4 + (l))
+#define ERR_ESC_EDGE(l) BIT(0 + (l))
+
+#define MCTL_LANE_STS 0x2c
+#define PPI_C_TX_READY_HS BIT(18)
+#define DPHY_PLL_LOCK BIT(17)
+#define PPI_D_RX_ULPS_ESC(x) (((x) & GENMASK(15, 12)) >> 12)
+#define LANE_STATE_START 0
+#define LANE_STATE_IDLE 1
+#define LANE_STATE_WRITE 2
+#define LANE_STATE_ULPM 3
+#define LANE_STATE_READ 4
+#define DATA_LANE_STATE(l, val) \
+ (((val) >> (2 + 2 * (l) + ((l) ? 1 : 0))) & GENMASK((l) ? 1 : 2, 0))
+#define CLK_LANE_STATE_HS 2
+#define CLK_LANE_STATE(val) ((val) & GENMASK(1, 0))
+
+#define DSC_MODE_CTL 0x30
+#define DSC_MODE_EN BIT(0)
+
+#define DSC_CMD_SEND 0x34
+#define DSC_SEND_PPS BIT(0)
+#define DSC_EXECUTE_QUEUE BIT(1)
+
+#define DSC_PPS_WRDAT 0x38
+
+#define DSC_MODE_STS 0x3c
+#define DSC_PPS_DONE BIT(1)
+#define DSC_EXEC_DONE BIT(2)
+
+#define CMD_MODE_CTL 0x70
+#define IF_LP_EN(x) BIT(9 + (x))
+#define IF_VCHAN_ID(x, c) ((c) << ((x) * 2))
+
+#define CMD_MODE_CTL2 0x74
+#define TE_TIMEOUT(x) ((x) << 11)
+#define FILL_VALUE(x) ((x) << 3)
+#define ARB_IF_WITH_HIGHEST_PRIORITY(x) ((x) << 1)
+#define ARB_ROUND_ROBIN_MODE BIT(0)
+
+#define CMD_MODE_STS 0x78
+#define CMD_MODE_STS_CTL 0x134
+#define CMD_MODE_STS_CLR 0x154
+#define CMD_MODE_STS_FLAG 0x174
+#define ERR_IF_UNDERRUN(x) BIT(4 + (x))
+#define ERR_UNWANTED_READ BIT(3)
+#define ERR_TE_MISS BIT(2)
+#define ERR_NO_TE BIT(1)
+#define CSM_RUNNING BIT(0)
+
+#define DIRECT_CMD_SEND 0x80
+
+#define DIRECT_CMD_MAIN_SETTINGS 0x84
+#define TRIGGER_VAL(x) ((x) << 25)
+#define CMD_LP_EN BIT(24)
+#define CMD_SIZE(x) ((x) << 16)
+#define CMD_VCHAN_ID(x) ((x) << 14)
+#define CMD_DATATYPE(x) ((x) << 8)
+#define CMD_LONG BIT(3)
+#define WRITE_CMD 0
+#define READ_CMD 1
+#define TE_REQ 4
+#define TRIGGER_REQ 5
+#define BTA_REQ 6
+
+#define DIRECT_CMD_STS 0x88
+#define DIRECT_CMD_STS_CTL 0x138
+#define DIRECT_CMD_STS_CLR 0x158
+#define DIRECT_CMD_STS_FLAG 0x178
+#define RCVD_ACK_VAL(val) ((val) >> 16)
+#define RCVD_TRIGGER_VAL(val) (((val) & GENMASK(14, 11)) >> 11)
+#define READ_COMPLETED_WITH_ERR BIT(10)
+#define BTA_FINISHED BIT(9)
+#define BTA_COMPLETED BIT(8)
+#define TE_RCVD BIT(7)
+#define TRIGGER_RCVD BIT(6)
+#define ACK_WITH_ERR_RCVD BIT(5)
+#define ACK_RCVD BIT(4)
+#define READ_COMPLETED BIT(3)
+#define TRIGGER_COMPLETED BIT(2)
+#define WRITE_COMPLETED BIT(1)
+
+#define SENDING_CMD BIT(0)
+
+#define DIRECT_CMD_STOP_READ 0x8c
+
+#define DIRECT_CMD_WRDATA 0x90
+
+#define DIRECT_CMD_FIFO_RST 0x94
+
+#define DIRECT_CMD_RDDATA 0xa0
+
+#define DIRECT_CMD_RD_PROPS 0xa4
+#define RD_DCS BIT(18)
+#define RD_VCHAN_ID(val) (((val) >> 16) & GENMASK(1, 0))
+#define RD_SIZE(val) ((val) & GENMASK(15, 0))
+
+#define DIRECT_CMD_RD_STS 0xa8
+#define DIRECT_CMD_RD_STS_CTL 0x13c
+#define DIRECT_CMD_RD_STS_CLR 0x15c
+#define DIRECT_CMD_RD_STS_FLAG 0x17c
+#define ERR_EOT_WITH_ERR BIT(8)
+#define ERR_MISSING_EOT BIT(7)
+#define ERR_WRONG_LENGTH BIT(6)
+#define ERR_OVERSIZE BIT(5)
+#define ERR_RECEIVE BIT(4)
+#define ERR_UNDECODABLE BIT(3)
+#define ERR_CHECKSUM BIT(2)
+#define ERR_UNCORRECTABLE BIT(1)
+#define ERR_FIXED BIT(0)
+
+#define VID_MAIN_CTL 0xb0
+#define VID_IGNORE_MISS_VSYNC BIT(31)
+#define VID_FIELD_SW BIT(28)
+#define VID_INTERLACED_EN BIT(27)
+#define RECOVERY_MODE(x) ((x) << 25)
+#define RECOVERY_MODE_NEXT_HSYNC 0
+#define RECOVERY_MODE_NEXT_STOP_POINT 2
+#define RECOVERY_MODE_NEXT_VSYNC 3
+#define REG_BLKEOL_MODE(x) ((x) << 23)
+#define REG_BLKLINE_MODE(x) ((x) << 21)
+#define REG_BLK_MODE_NULL_PKT 0
+#define REG_BLK_MODE_BLANKING_PKT 1
+#define REG_BLK_MODE_LP 2
+#define SYNC_PULSE_HORIZONTAL BIT(20)
+#define SYNC_PULSE_ACTIVE BIT(19)
+#define BURST_MODE BIT(18)
+#define VID_PIXEL_MODE_MASK GENMASK(17, 14)
+#define VID_PIXEL_MODE_RGB565 (0 << 14)
+#define VID_PIXEL_MODE_RGB666_PACKED (1 << 14)
+#define VID_PIXEL_MODE_RGB666 (2 << 14)
+#define VID_PIXEL_MODE_RGB888 (3 << 14)
+#define VID_PIXEL_MODE_RGB101010 (4 << 14)
+#define VID_PIXEL_MODE_RGB121212 (5 << 14)
+#define VID_PIXEL_MODE_YUV420 (8 << 14)
+#define VID_PIXEL_MODE_YUV422_PACKED (9 << 14)
+#define VID_PIXEL_MODE_YUV422 (10 << 14)
+#define VID_PIXEL_MODE_YUV422_24B (11 << 14)
+#define VID_PIXEL_MODE_DSC_COMP (12 << 14)
+#define VID_DATATYPE(x) ((x) << 8)
+#define VID_VIRTCHAN_ID(iface, x) ((x) << (4 + (iface) * 2))
+#define STOP_MODE(x) ((x) << 2)
+#define START_MODE(x) (x)
+
+#define VID_VSIZE1 0xb4
+#define VFP_LEN(x) ((x) << 12)
+#define VBP_LEN(x) ((x) << 6)
+#define VSA_LEN(x) (x)
+
+#define VID_VSIZE2 0xb8
+#define VACT_LEN(x) (x)
+
+#define VID_HSIZE1 0xc0
+#define HBP_LEN(x) ((x) << 16)
+#define HSA_LEN(x) (x)
+
+#define VID_HSIZE2 0xc4
+#define HFP_LEN(x) ((x) << 16)
+#define HACT_LEN(x) (x)
+
+#define VID_BLKSIZE1 0xcc
+#define BLK_EOL_PKT_LEN(x) ((x) << 15)
+#define BLK_LINE_EVENT_PKT_LEN(x) (x)
+
+#define VID_BLKSIZE2 0xd0
+#define BLK_LINE_PULSE_PKT_LEN(x) (x)
+
+#define VID_PKT_TIME 0xd8
+#define BLK_EOL_DURATION(x) (x)
+
+#define VID_DPHY_TIME 0xdc
+#define REG_WAKEUP_TIME(x) ((x) << 17)
+#define REG_LINE_DURATION(x) (x)
+
+#define VID_ERR_COLOR1 0xe0
+#define COL_GREEN(x) ((x) << 12)
+#define COL_RED(x) (x)
+
+#define VID_ERR_COLOR2 0xe4
+#define PAD_VAL(x) ((x) << 12)
+#define COL_BLUE(x) (x)
+
+#define VID_VPOS 0xe8
+#define LINE_VAL(val) (((val) & GENMASK(14, 2)) >> 2)
+#define LINE_POS(val) ((val) & GENMASK(1, 0))
+
+#define VID_HPOS 0xec
+#define HORIZ_VAL(val) (((val) & GENMASK(17, 3)) >> 3)
+#define HORIZ_POS(val) ((val) & GENMASK(2, 0))
+
+#define VID_MODE_STS 0xf0
+#define VID_MODE_STS_CTL 0x140
+#define VID_MODE_STS_CLR 0x160
+#define VID_MODE_STS_FLAG 0x180
+#define VSG_RECOVERY BIT(10)
+#define ERR_VRS_WRONG_LEN BIT(9)
+#define ERR_LONG_READ BIT(8)
+#define ERR_LINE_WRITE BIT(7)
+#define ERR_BURST_WRITE BIT(6)
+#define ERR_SMALL_HEIGHT BIT(5)
+#define ERR_SMALL_LEN BIT(4)
+#define ERR_MISSING_VSYNC BIT(3)
+#define ERR_MISSING_HSYNC BIT(2)
+#define ERR_MISSING_DATA BIT(1)
+#define VSG_RUNNING BIT(0)
+
+#define VID_VCA_SETTING1 0xf4
+#define BURST_LP BIT(16)
+#define MAX_BURST_LIMIT(x) (x)
+
+#define VID_VCA_SETTING2 0xf8
+#define MAX_LINE_LIMIT(x) ((x) << 16)
+#define EXACT_BURST_LIMIT(x) (x)
+
+#define TVG_CTL 0xfc
+#define TVG_STRIPE_SIZE(x) ((x) << 5)
+#define TVG_MODE_MASK GENMASK(4, 3)
+#define TVG_MODE_SINGLE_COLOR (0 << 3)
+#define TVG_MODE_VSTRIPES (2 << 3)
+#define TVG_MODE_HSTRIPES (3 << 3)
+#define TVG_STOPMODE_MASK GENMASK(2, 1)
+#define TVG_STOPMODE_EOF (0 << 1)
+#define TVG_STOPMODE_EOL (1 << 1)
+#define TVG_STOPMODE_NOW (2 << 1)
+#define TVG_RUN BIT(0)
+
+#define TVG_IMG_SIZE 0x100
+#define TVG_NBLINES(x) ((x) << 16)
+#define TVG_LINE_SIZE(x) (x)
+
+#define TVG_COLOR1 0x104
+#define TVG_COL1_GREEN(x) ((x) << 12)
+#define TVG_COL1_RED(x) (x)
+
+#define TVG_COLOR1_BIS 0x108
+#define TVG_COL1_BLUE(x) (x)
+
+#define TVG_COLOR2 0x10c
+#define TVG_COL2_GREEN(x) ((x) << 12)
+#define TVG_COL2_RED(x) (x)
+
+#define TVG_COLOR2_BIS 0x110
+#define TVG_COL2_BLUE(x) (x)
+
+#define TVG_STS 0x114
+#define TVG_STS_CTL 0x144
+#define TVG_STS_CLR 0x164
+#define TVG_STS_FLAG 0x184
+#define TVG_STS_RUNNING BIT(0)
+
+#define STS_CTL_EDGE(e) ((e) << 16)
+
+#define DPHY_LANES_MAP 0x198
+#define DAT_REMAP_CFG(b, l) ((l) << ((b) * 8))
+
+#define DPI_IRQ_EN 0x1a0
+#define DPI_IRQ_CLR 0x1a4
+#define DPI_IRQ_STS 0x1a8
+#define PIXEL_BUF_OVERFLOW BIT(0)
+
+#define DPI_CFG 0x1ac
+#define DPI_CFG_FIFO_DEPTH(x) ((x) >> 16)
+#define DPI_CFG_FIFO_LEVEL(x) ((x) & GENMASK(15, 0))
+
+#define TEST_GENERIC 0x1f0
+#define TEST_STATUS(x) ((x) >> 16)
+#define TEST_CTRL(x) (x)
+
+#define ID_REG 0x1fc
+#define REV_VENDOR_ID(x) (((x) & GENMASK(31, 20)) >> 20)
+#define REV_PRODUCT_ID(x) (((x) & GENMASK(19, 12)) >> 12)
+#define REV_HW(x) (((x) & GENMASK(11, 8)) >> 8)
+#define REV_MAJOR(x) (((x) & GENMASK(7, 4)) >> 4)
+#define REV_MINOR(x) ((x) & GENMASK(3, 0))
+
+#define DSI_OUTPUT_PORT 0
+#define DSI_INPUT_PORT(inputid) (1 + (inputid))
+
+#define DSI_HBP_FRAME_OVERHEAD 12
+#define DSI_HSA_FRAME_OVERHEAD 14
+#define DSI_HFP_FRAME_OVERHEAD 6
+#define DSI_HSS_VSS_VSE_FRAME_OVERHEAD 4
+#define DSI_BLANKING_FRAME_OVERHEAD 6
+#define DSI_NULL_FRAME_OVERHEAD 6
+#define DSI_EOT_PKT_SIZE 4
+
+struct cdns_dsi_output {
+ struct mipi_dsi_device *dev;
+ struct drm_panel *panel;
+ struct drm_bridge *bridge;
+ union phy_configure_opts phy_opts;
+};
+
+enum cdns_dsi_input_id {
+ CDNS_SDI_INPUT,
+ CDNS_DPI_INPUT,
+ CDNS_DSC_INPUT,
+};
+
+struct cdns_dsi_cfg {
+ unsigned int hfp;
+ unsigned int hsa;
+ unsigned int hbp;
+ unsigned int hact;
+ unsigned int htotal;
+};
+
+struct cdns_dsi_input {
+ enum cdns_dsi_input_id id;
+ struct drm_bridge bridge;
+};
+
+struct cdns_dsi {
+ struct mipi_dsi_host base;
+ void __iomem *regs;
+ struct cdns_dsi_input input;
+ struct cdns_dsi_output output;
+ unsigned int direct_cmd_fifo_depth;
+ unsigned int rx_fifo_depth;
+ struct completion direct_cmd_comp;
+ struct clk *dsi_p_clk;
+ struct reset_control *dsi_p_rst;
+ struct clk *dsi_sys_clk;
+ bool link_initialized;
+ struct phy *dphy;
+};
+
+static inline struct cdns_dsi *input_to_dsi(struct cdns_dsi_input *input)
+{
+ return container_of(input, struct cdns_dsi, input);
+}
+
+static inline struct cdns_dsi *to_cdns_dsi(struct mipi_dsi_host *host)
+{
+ return container_of(host, struct cdns_dsi, base);
+}
+
+static inline struct cdns_dsi_input *
+bridge_to_cdns_dsi_input(struct drm_bridge *bridge)
+{
+ return container_of(bridge, struct cdns_dsi_input, bridge);
+}
+
+static unsigned int mode_to_dpi_hfp(const struct drm_display_mode *mode,
+ bool mode_valid_check)
+{
+ if (mode_valid_check)
+ return mode->hsync_start - mode->hdisplay;
+
+ return mode->crtc_hsync_start - mode->crtc_hdisplay;
+}
+
+static unsigned int dpi_to_dsi_timing(unsigned int dpi_timing,
+ unsigned int dpi_bpp,
+ unsigned int dsi_pkt_overhead)
+{
+ unsigned int dsi_timing = DIV_ROUND_UP(dpi_timing * dpi_bpp, 8);
+ //unsigned int dsi_timing = dpi_timing;
+
+ if (dsi_timing < dsi_pkt_overhead)
+ dsi_timing = 0;
+ else
+ dsi_timing -= dsi_pkt_overhead;
+
+ return dsi_timing;
+}
+
+static int cdns_dsi_mode2cfg(struct cdns_dsi *dsi,
+ const struct drm_display_mode *mode,
+ struct cdns_dsi_cfg *dsi_cfg,
+ bool mode_valid_check)
+{
+ struct cdns_dsi_output *output = &dsi->output;
+ unsigned int tmp;
+ bool sync_pulse = false;
+ int bpp;
+
+ memset(dsi_cfg, 0, sizeof(*dsi_cfg));
+
+ if (output->dev->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)
+ sync_pulse = true;
+ bpp = mipi_dsi_pixel_format_to_bpp(output->dev->format);
+
+ if (mode_valid_check)
+ tmp = mode->htotal -
+ (sync_pulse ? mode->hsync_end : mode->hsync_start);
+ else
+ tmp = mode->crtc_htotal -
+ (sync_pulse ?
+ mode->crtc_hsync_end : mode->crtc_hsync_start);
+
+ dsi_cfg->hbp = dpi_to_dsi_timing(tmp, bpp, DSI_HBP_FRAME_OVERHEAD);
+
+ if (sync_pulse) {
+ if (mode_valid_check)
+ tmp = mode->hsync_end - mode->hsync_start;
+ else
+ tmp = mode->crtc_hsync_end - mode->crtc_hsync_start;
+
+ dsi_cfg->hsa = dpi_to_dsi_timing(tmp, bpp,
+ DSI_HSA_FRAME_OVERHEAD);
+ }
+
+ dsi_cfg->hact = dpi_to_dsi_timing(mode_valid_check ?
+ mode->hdisplay : mode->crtc_hdisplay,
+ bpp, 0);
+ dsi_cfg->hfp = dpi_to_dsi_timing(mode_to_dpi_hfp(mode, mode_valid_check),
+ bpp, DSI_HFP_FRAME_OVERHEAD);
+ //dpi to dsi transfer can not match , reconfig those parms
+ if (mode->hdisplay == 800) {
+ dsi_cfg->hsa = 16; //30-14
+ dsi_cfg->hbp = 73; //85-12
+ dsi_cfg->hfp = 146; //152-6
+ }
+
+ return 0;
+}
+
+static int cdns_dsi_adjust_phy_config(struct cdns_dsi *dsi,
+ struct cdns_dsi_cfg *dsi_cfg,
+ struct phy_configure_opts_mipi_dphy *phy_cfg,
+ const struct drm_display_mode *mode,
+ bool mode_valid_check)
+{
+ struct cdns_dsi_output *output = &dsi->output;
+ unsigned long long dlane_bps;
+ unsigned long adj_dsi_htotal;
+ unsigned long dsi_htotal;
+ unsigned long dpi_htotal;
+ unsigned long dpi_hz;
+ unsigned int dsi_hfp_ext;
+ unsigned int lanes = output->dev->lanes;
+
+ dsi_htotal = dsi_cfg->hbp + DSI_HBP_FRAME_OVERHEAD;
+
+ if (output->dev->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)
+ dsi_htotal += dsi_cfg->hsa + DSI_HSA_FRAME_OVERHEAD;
+
+ dsi_htotal += dsi_cfg->hact;
+ dsi_htotal += dsi_cfg->hfp + DSI_HFP_FRAME_OVERHEAD;
+
+ /*
+ * Make sure DSI htotal is aligned on a lane boundary when calculating
+ * the expected data rate. This is done by extending HFP in case of
+ * misalignment.
+ */
+ adj_dsi_htotal = dsi_htotal;
+ if (dsi_htotal % lanes)
+ adj_dsi_htotal += lanes - (dsi_htotal % lanes);
+
+ dpi_hz = (mode_valid_check ? mode->clock : mode->crtc_clock) * 1000;
+ dlane_bps = (unsigned long long)dpi_hz * adj_dsi_htotal;
+
+ /* data rate in bytes/sec is not an integer, refuse the mode. */
+ dpi_htotal = mode_valid_check ? mode->htotal : mode->crtc_htotal;
+
+ if (do_div(dlane_bps, lanes * dpi_htotal))
+ return -EINVAL;
+
+ /* data rate was in bytes/sec, convert to bits/sec. */
+ phy_cfg->hs_clk_rate = dlane_bps * 8;
+
+ dsi_hfp_ext = adj_dsi_htotal - dsi_htotal;
+ dsi_cfg->hfp += dsi_hfp_ext;
+ dsi_cfg->htotal = dsi_htotal + dsi_hfp_ext;
+
+ return 0;
+}
+
+static int cdns_dsi_check_conf(struct cdns_dsi *dsi,
+ const struct drm_display_mode *mode,
+ struct cdns_dsi_cfg *dsi_cfg,
+ bool mode_valid_check)
+{
+ struct cdns_dsi_output *output = &dsi->output;
+ struct phy_configure_opts_mipi_dphy *phy_cfg = &output->phy_opts.mipi_dphy;
+ unsigned long dsi_hss_hsa_hse_hbp;
+ unsigned int nlanes = output->dev->lanes;
+ int ret;
+
+ ret = cdns_dsi_mode2cfg(dsi, mode, dsi_cfg, mode_valid_check);
+ if (ret)
+ return ret;
+
+ phy_mipi_dphy_get_default_config(mode->crtc_clock * 1000,
+ mipi_dsi_pixel_format_to_bpp(output->dev->format),
+ nlanes, phy_cfg);
+
+ ret = cdns_dsi_adjust_phy_config(dsi, dsi_cfg, phy_cfg, mode, mode_valid_check);
+ if (ret)
+ return ret;
+
+ ret = phy_validate(dsi->dphy, PHY_MODE_MIPI_DPHY, 0, &output->phy_opts);
+ if (ret)
+ return ret;
+
+ dsi_hss_hsa_hse_hbp = dsi_cfg->hbp + DSI_HBP_FRAME_OVERHEAD;
+ if (output->dev->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)
+ dsi_hss_hsa_hse_hbp += dsi_cfg->hsa + DSI_HSA_FRAME_OVERHEAD;
+
+ /*
+ * Make sure DPI(HFP) > DSI(HSS+HSA+HSE+HBP) to guarantee that the FIFO
+ * is empty before we start a receiving a new line on the DPI
+ * interface.
+ */
+ if ((u64)phy_cfg->hs_clk_rate *
+ mode_to_dpi_hfp(mode, mode_valid_check) * nlanes <
+ (u64)dsi_hss_hsa_hse_hbp *
+ (mode_valid_check ? mode->clock : mode->crtc_clock) * 1000)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int cdns_dsi_bridge_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
+{
+ struct cdns_dsi_input *input = bridge_to_cdns_dsi_input(bridge);
+ struct cdns_dsi *dsi = input_to_dsi(input);
+ struct cdns_dsi_output *output = &dsi->output;
+
+ if (!drm_core_check_feature(bridge->dev, DRIVER_ATOMIC)) {
+ dev_err(dsi->base.dev,
+ "cdns-dsi driver is only compatible with DRM devices supporting atomic updates");
+ return -EOPNOTSUPP;
+ }
+
+ return drm_bridge_attach(bridge->encoder, output->bridge, bridge,
+ flags);
+}
+
+static enum drm_mode_status
+cdns_dsi_bridge_mode_valid(struct drm_bridge *bridge,
+ const struct drm_display_info *info,
+ const struct drm_display_mode *mode)
+{
+ struct cdns_dsi_input *input = bridge_to_cdns_dsi_input(bridge);
+ struct cdns_dsi *dsi = input_to_dsi(input);
+ struct cdns_dsi_output *output = &dsi->output;
+ struct cdns_dsi_cfg dsi_cfg;
+ int bpp, ret;
+
+ /*
+ * VFP_DSI should be less than VFP_DPI and VFP_DSI should be at
+ * least 1.
+ */
+ if (mode->vtotal - mode->vsync_end < 2)
+ return MODE_V_ILLEGAL;
+
+ /* VSA_DSI = VSA_DPI and must be at least 2. */
+ if (mode->vsync_end - mode->vsync_start < 2)
+ return MODE_V_ILLEGAL;
+
+ /* HACT must be 32-bits aligned. */
+ bpp = mipi_dsi_pixel_format_to_bpp(output->dev->format);
+ if ((mode->hdisplay * bpp) % 32)
+ return MODE_H_ILLEGAL;
+
+ ret = cdns_dsi_check_conf(dsi, mode, &dsi_cfg, true);
+ if (ret)
+ return MODE_BAD;
+
+ return MODE_OK;
+}
+
+static void cdns_dsi_bridge_disable(struct drm_bridge *bridge)
+{
+ struct cdns_dsi_input *input = bridge_to_cdns_dsi_input(bridge);
+ struct cdns_dsi *dsi = input_to_dsi(input);
+ u32 val;
+
+ dsi->link_initialized = false;
+ val = readl(dsi->regs + MCTL_MAIN_DATA_CTL);
+ val &= ~(IF_VID_SELECT_MASK | IF_VID_MODE | VID_EN | HOST_EOT_GEN |
+ DISP_EOT_GEN);
+ writel(val, dsi->regs + MCTL_MAIN_DATA_CTL);
+
+ val = readl(dsi->regs + MCTL_MAIN_EN) & ~IF_EN(input->id);
+ writel(val, dsi->regs + MCTL_MAIN_EN);
+ pm_runtime_put(dsi->base.dev);
+ phy_power_off(dsi->dphy);
+ phy_exit(dsi->dphy);
+}
+
+static void release_txbyte_rst(void)
+{
+ void __iomem *regs = ioremap(0x12250000, 0x10000);
+
+ u32 temp = readl(regs + SRST_ASSERT0);
+
+ temp &= ~(0x1 << 18);
+ temp |= (0x0 & 0x1) << 18;
+
+ writel(temp, regs + SRST_ASSERT0);
+
+ do {
+ temp = readl(regs + SRST_STATUS0) >> 18;
+ temp &= 0x1;
+ } while (temp != 0x1);
+ //udelay(1);
+}
+
+static void cdns_dsi_hs_init(struct cdns_dsi *dsi)
+{
+ struct cdns_dsi_output *output = &dsi->output;
+ u32 dpi_fifo_int = 0;
+
+ /*
+ * Power all internal DPHY blocks down and maintain their reset line
+ * asserted before changing the DPHY config.
+ */
+ writel(DPHY_CMN_PSO | DPHY_PLL_PSO | DPHY_ALL_D_PDN | DPHY_C_PDN |
+ DPHY_CMN_PDN | DPHY_PLL_PDN,
+ dsi->regs + MCTL_DPHY_CFG0);
+
+ phy_init(dsi->dphy);
+ phy_set_mode(dsi->dphy, PHY_MODE_MIPI_DPHY);
+ phy_configure(dsi->dphy, &output->phy_opts);
+ phy_power_on(dsi->dphy);
+ release_txbyte_rst();
+
+ writel(PLL_LOCKED, dsi->regs + MCTL_MAIN_STS_CLR);
+ writel(DPHY_CMN_PSO | DPHY_ALL_D_PDN | DPHY_C_PDN | DPHY_CMN_PDN,
+ dsi->regs + MCTL_DPHY_CFG0);
+ mdelay(100);
+ /* De-assert data and clock reset lines. */
+ writel(DPHY_CMN_PSO | DPHY_ALL_D_PDN | DPHY_C_PDN | DPHY_CMN_PDN |
+ DPHY_D_RSTB(output->dev->lanes) | DPHY_C_RSTB,
+ dsi->regs + MCTL_DPHY_CFG0);
+
+ dpi_fifo_int = readl(dsi->regs + DPI_IRQ_CLR);
+ if (dpi_fifo_int)
+ writel(1, dsi->regs + DPI_IRQ_CLR);
+}
+
+static void cdns_dsi_init_link(struct cdns_dsi *dsi)
+{
+ struct cdns_dsi_output *output = &dsi->output;
+ unsigned long sysclk_period, ulpout;
+ u32 val;
+ int i;
+
+ if (dsi->link_initialized)
+ return;
+
+ val = 0;
+ for (i = 1; i < output->dev->lanes; i++)
+ val |= DATA_LANE_EN(i);
+
+ if (!(output->dev->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS))
+ val |= CLK_CONTINUOUS;
+
+ writel(val, dsi->regs + MCTL_MAIN_PHY_CTL);
+
+ /* ULPOUT should be set to 1ms and is expressed in sysclk cycles.*/
+ sysclk_period = NSEC_PER_SEC / clk_get_rate(dsi->dsi_sys_clk);
+ ulpout = DIV_ROUND_UP(NSEC_PER_MSEC, sysclk_period);
+ writel(CLK_LANE_ULPOUT_TIME(ulpout) | DATA_LANE_ULPOUT_TIME(ulpout),
+ dsi->regs + MCTL_ULPOUT_TIME);
+
+ writel(LINK_EN, dsi->regs + MCTL_MAIN_DATA_CTL);
+
+ val = CLK_LANE_EN | PLL_START;
+ for (i = 0; i < output->dev->lanes; i++)
+ val |= DATA_LANE_START(i);
+
+ writel(val, dsi->regs + MCTL_MAIN_EN);
+
+ dsi->link_initialized = true;
+}
+
+static void cdns_dsi_bridge_enable(struct drm_bridge *bridge)
+{
+ struct cdns_dsi_input *input = bridge_to_cdns_dsi_input(bridge);
+ struct cdns_dsi *dsi = input_to_dsi(input);
+ struct cdns_dsi_output *output = &dsi->output;
+ struct drm_display_mode *mode;
+ struct phy_configure_opts_mipi_dphy *phy_cfg = &output->phy_opts.mipi_dphy;
+ unsigned long tx_byte_period;
+ struct cdns_dsi_cfg dsi_cfg;
+ u32 tmp, reg_wakeup;
+ int nlanes;
+
+ if (WARN_ON(pm_runtime_get_sync(dsi->base.dev) < 0))
+ return;
+
+ mode = &bridge->encoder->crtc->state->adjusted_mode;
+ nlanes = output->dev->lanes;
+
+ WARN_ON_ONCE(cdns_dsi_check_conf(dsi, mode, &dsi_cfg, false));
+
+ cdns_dsi_hs_init(dsi);
+ cdns_dsi_init_link(dsi);
+
+ writel(HBP_LEN(dsi_cfg.hbp) | HSA_LEN(dsi_cfg.hsa),
+ dsi->regs + VID_HSIZE1);
+ writel(HFP_LEN(dsi_cfg.hfp) | HACT_LEN(dsi_cfg.hact),
+ dsi->regs + VID_HSIZE2);
+
+ writel(VBP_LEN(mode->crtc_vtotal - mode->crtc_vsync_end - 1) |
+ VFP_LEN(mode->crtc_vsync_start - mode->crtc_vdisplay) |
+ VSA_LEN(mode->crtc_vsync_end - mode->crtc_vsync_start + 1),
+ dsi->regs + VID_VSIZE1);
+ writel(mode->crtc_vdisplay, dsi->regs + VID_VSIZE2);
+
+ tmp = dsi_cfg.htotal -
+ (dsi_cfg.hsa + DSI_BLANKING_FRAME_OVERHEAD +
+ DSI_HSA_FRAME_OVERHEAD);
+ writel(BLK_LINE_PULSE_PKT_LEN(tmp), dsi->regs + VID_BLKSIZE2);
+ if (output->dev->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)
+ writel(MAX_LINE_LIMIT(tmp - DSI_NULL_FRAME_OVERHEAD),
+ dsi->regs + VID_VCA_SETTING2);
+
+ tmp = dsi_cfg.htotal -
+ (DSI_HSS_VSS_VSE_FRAME_OVERHEAD + DSI_BLANKING_FRAME_OVERHEAD);
+ writel(BLK_LINE_EVENT_PKT_LEN(tmp), dsi->regs + VID_BLKSIZE1);
+ if (!(output->dev->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE))
+ writel(MAX_LINE_LIMIT(tmp - DSI_NULL_FRAME_OVERHEAD),
+ dsi->regs + VID_VCA_SETTING2);
+
+ tmp = DIV_ROUND_UP(dsi_cfg.htotal, nlanes) -
+ DIV_ROUND_UP(dsi_cfg.hsa, nlanes);
+
+ if (!(output->dev->mode_flags & MIPI_DSI_MODE_NO_EOT_PACKET))
+ tmp -= DIV_ROUND_UP(DSI_EOT_PKT_SIZE, nlanes);
+
+ tx_byte_period = DIV_ROUND_DOWN_ULL((u64)NSEC_PER_SEC * 8,
+ phy_cfg->hs_clk_rate);
+ reg_wakeup = (phy_cfg->hs_prepare + phy_cfg->hs_zero) / tx_byte_period;
+ writel(REG_WAKEUP_TIME(reg_wakeup) | REG_LINE_DURATION(tmp),
+ dsi->regs + VID_DPHY_TIME);
+
+ writel(0xafffb, dsi->regs + MCTL_DPHY_TIMEOUT1);
+ writel(0x3ffff, dsi->regs + MCTL_DPHY_TIMEOUT2);
+ writel(0x3ab05, dsi->regs + MCTL_ULPOUT_TIME);
+
+ if (output->dev->mode_flags & MIPI_DSI_MODE_VIDEO) {
+ switch (output->dev->format) {
+ case MIPI_DSI_FMT_RGB888:
+ tmp = VID_PIXEL_MODE_RGB888 |
+ VID_DATATYPE(MIPI_DSI_PACKED_PIXEL_STREAM_24);
+ break;
+
+ case MIPI_DSI_FMT_RGB666:
+ tmp = VID_PIXEL_MODE_RGB666 |
+ VID_DATATYPE(MIPI_DSI_PIXEL_STREAM_3BYTE_18);
+ break;
+
+ case MIPI_DSI_FMT_RGB666_PACKED:
+ tmp = VID_PIXEL_MODE_RGB666_PACKED |
+ VID_DATATYPE(MIPI_DSI_PACKED_PIXEL_STREAM_18);
+ break;
+
+ case MIPI_DSI_FMT_RGB565:
+ tmp = VID_PIXEL_MODE_RGB565 |
+ VID_DATATYPE(MIPI_DSI_PACKED_PIXEL_STREAM_16);
+ break;
+
+ default:
+ dev_err(dsi->base.dev, "Unsupported DSI format\n");
+ return;
+ }
+
+ if (output->dev->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)
+ tmp |= SYNC_PULSE_ACTIVE | SYNC_PULSE_HORIZONTAL;
+
+ tmp |= REG_BLKLINE_MODE(REG_BLK_MODE_BLANKING_PKT) |
+ REG_BLKEOL_MODE(REG_BLK_MODE_BLANKING_PKT) |
+ RECOVERY_MODE(RECOVERY_MODE_NEXT_HSYNC) |
+ VID_IGNORE_MISS_VSYNC;
+
+ writel(tmp, dsi->regs + VID_MAIN_CTL);
+ }
+
+ tmp = readl(dsi->regs + MCTL_MAIN_DATA_CTL);
+ tmp &= ~(IF_VID_SELECT_MASK | HOST_EOT_GEN | IF_VID_MODE);
+
+ if (!(output->dev->mode_flags & MIPI_DSI_MODE_NO_EOT_PACKET))
+ tmp |= HOST_EOT_GEN;
+
+ if (output->dev->mode_flags & MIPI_DSI_MODE_VIDEO)
+ tmp |= IF_VID_MODE | IF_VID_SELECT(input->id) | VID_EN;
+
+ writel(tmp, dsi->regs + MCTL_MAIN_DATA_CTL);
+
+ tmp = readl(dsi->regs + MCTL_MAIN_EN) | IF_EN(input->id);
+ writel(tmp, dsi->regs + MCTL_MAIN_EN);
+}
+
+static const struct drm_bridge_funcs cdns_dsi_bridge_funcs = {
+ .attach = cdns_dsi_bridge_attach,
+ .mode_valid = cdns_dsi_bridge_mode_valid,
+ .disable = cdns_dsi_bridge_disable,
+ .enable = cdns_dsi_bridge_enable,
+};
+
+static int cdns_dsi_attach(struct mipi_dsi_host *host,
+ struct mipi_dsi_device *dev)
+{
+ struct cdns_dsi *dsi = to_cdns_dsi(host);
+ struct cdns_dsi_output *output = &dsi->output;
+ struct cdns_dsi_input *input = &dsi->input;
+ struct drm_bridge *bridge;
+ struct drm_panel *panel;
+ struct device_node *np;
+ int ret;
+
+ /*
+ * We currently do not support connecting several DSI devices to the
+ * same host. In order to support that we'd need the DRM bridge
+ * framework to allow dynamic reconfiguration of the bridge chain.
+ */
+ if (output->dev)
+ return -EBUSY;
+
+ /* We do not support burst mode yet. */
+ if (dev->mode_flags & MIPI_DSI_MODE_VIDEO_BURST)
+ return -EOPNOTSUPP;
+
+ /*
+ * The host <-> device link might be described using an OF-graph
+ * representation, in this case we extract the device of_node from
+ * this representation, otherwise we use dsidev->dev.of_node which
+ * should have been filled by the core.
+ */
+ np = of_graph_get_remote_node(dsi->base.dev->of_node, DSI_OUTPUT_PORT,
+ dev->channel);
+ if (!np)
+ np = of_node_get(dev->dev.of_node);
+
+ panel = of_drm_find_panel(np);
+ if (!IS_ERR(panel)) {
+ bridge = drm_panel_bridge_add_typed(panel,
+ DRM_MODE_CONNECTOR_DSI);
+ } else {
+ bridge = of_drm_find_bridge(dev->dev.of_node);
+ if (!bridge)
+ bridge = ERR_PTR(-EINVAL);
+ }
+
+ of_node_put(np);
+
+ if (IS_ERR(bridge)) {
+ ret = PTR_ERR(bridge);
+ dev_err(host->dev, "failed to add DSI device %s (err = %d)",
+ dev->name, ret);
+ return ret;
+ }
+
+ output->dev = dev;
+ output->bridge = bridge;
+ output->panel = panel;
+
+ /*
+ * The DSI output has been properly configured, we can now safely
+ * register the input to the bridge framework so that it can take place
+ * in a display pipeline.
+ */
+ drm_bridge_add(&input->bridge);
+
+ return 0;
+}
+
+static int cdns_dsi_detach(struct mipi_dsi_host *host,
+ struct mipi_dsi_device *dev)
+{
+ struct cdns_dsi *dsi = to_cdns_dsi(host);
+ struct cdns_dsi_output *output = &dsi->output;
+ struct cdns_dsi_input *input = &dsi->input;
+
+ drm_bridge_remove(&input->bridge);
+ if (output->panel)
+ drm_panel_bridge_remove(output->bridge);
+
+ return 0;
+}
+
+static irqreturn_t cdns_dsi_interrupt(int irq, void *data)
+{
+ struct cdns_dsi *dsi = data;
+ irqreturn_t ret = IRQ_NONE;
+ u32 flag, ctl;
+
+ flag = readl(dsi->regs + DIRECT_CMD_STS_FLAG);
+ if (flag) {
+ ctl = readl(dsi->regs + DIRECT_CMD_STS_CTL);
+ ctl &= ~flag;
+ writel(ctl, dsi->regs + DIRECT_CMD_STS_CTL);
+ complete(&dsi->direct_cmd_comp);
+ ret = IRQ_HANDLED;
+ }
+
+ return ret;
+}
+
+static ssize_t cdns_dsi_transfer(struct mipi_dsi_host *host,
+ const struct mipi_dsi_msg *msg)
+{
+ struct cdns_dsi *dsi = to_cdns_dsi(host);
+ u32 cmd, val, wait = WRITE_COMPLETED, ctl = 0;
+ struct mipi_dsi_packet packet;
+ int ret, i, tx_len, rx_len;
+ u32 stat = 0;
+ int timeout = 100;
+ int stat_88;
+ int stat_188;
+ int stat_88_ack_val;
+
+ ret = pm_runtime_get_sync(host->dev);
+ if (ret < 0)
+ return ret;
+
+ cdns_dsi_init_link(dsi);
+
+ ret = mipi_dsi_create_packet(&packet, msg);
+ if (ret)
+ goto out;
+
+ tx_len = msg->tx_buf ? msg->tx_len : 0;
+ rx_len = msg->rx_buf ? msg->rx_len : 0;
+
+ /* For read operations, the maximum TX len is 2. */
+ if (rx_len && tx_len > 2) {
+ ret = -EOPNOTSUPP;
+ goto out;
+ }
+
+ /* TX len is limited by the CMD FIFO depth. */
+ if (tx_len > dsi->direct_cmd_fifo_depth) {
+ ret = -EOPNOTSUPP;
+ goto out;
+ }
+
+ /* RX len is limited by the RX FIFO depth. */
+ if (rx_len > dsi->rx_fifo_depth) {
+ ret = -EOPNOTSUPP;
+ goto out;
+ }
+
+ cmd = CMD_SIZE(tx_len) | CMD_VCHAN_ID(msg->channel) |
+ CMD_DATATYPE(msg->type);
+
+ if (msg->flags & MIPI_DSI_MSG_USE_LPM)
+ cmd |= CMD_LP_EN;
+
+ if (mipi_dsi_packet_format_is_long(msg->type))
+ cmd |= CMD_LONG;
+
+ if (rx_len) {
+ cmd |= READ_CMD;
+ wait = READ_COMPLETED_WITH_ERR | READ_COMPLETED;
+ ctl = READ_EN | BTA_EN;
+ } else if (msg->flags & MIPI_DSI_MSG_REQ_ACK) {
+ cmd |= BTA_REQ;
+ wait = ACK_WITH_ERR_RCVD | ACK_RCVD;
+ ctl = BTA_EN;
+ }
+
+ /* Clear status flags before sending the command. */
+
+ iowrite32(wait, dsi->regs + DIRECT_CMD_STS_CLR);
+ iowrite32(wait, dsi->regs + DIRECT_CMD_STS_CTL);
+ iowrite32(cmd, dsi->regs + DIRECT_CMD_MAIN_SETTINGS);
+
+ for (i = 0; i < tx_len; i += 4) {
+ const u8 *buf = msg->tx_buf;
+ int j;
+
+ val = 0;
+ for (j = 0; j < 4 && j + i < tx_len; j++)
+ val |= (u32)buf[i + j] << (8 * j);
+ iowrite32(val, dsi->regs + DIRECT_CMD_WRDATA);
+ }
+ iowrite32(0, dsi->regs + DIRECT_CMD_SEND);
+
+ do {
+ stat = readl(dsi->regs + DIRECT_CMD_STS);
+ if ((stat & 0x02) == 0x02)
+ break;
+ mdelay(10);
+ } while (--timeout);
+ if (!timeout)
+ DRM_DEBUG("timeout!\n");
+
+ stat_88 = readl(dsi->regs + DIRECT_CMD_STS);
+ stat_188 = readl(dsi->regs + MCTL_DPHY_ERR_FLAG);
+ stat_88_ack_val = stat_88 >> 16;
+ if (stat_188 || stat_88_ack_val)
+ dev_dbg(host->dev, "stat: [188h] %08x, [88h] %08x\r\n", stat_188, stat_88);
+
+out:
+ pm_runtime_put(host->dev);
+ return ret;
+}
+
+static const struct mipi_dsi_host_ops cdns_dsi_ops = {
+ .attach = cdns_dsi_attach,
+ .detach = cdns_dsi_detach,
+ .transfer = cdns_dsi_transfer,
+};
+
+static int __maybe_unused cdns_dsi_resume(struct device *dev)
+{
+ struct cdns_dsi *dsi = dev_get_drvdata(dev);
+
+ reset_control_deassert(dsi->dsi_p_rst);
+ clk_prepare_enable(dsi->dsi_p_clk);
+ clk_prepare_enable(dsi->dsi_sys_clk);
+
+ return 0;
+}
+
+static int __maybe_unused cdns_dsi_suspend(struct device *dev)
+{
+ struct cdns_dsi *dsi = dev_get_drvdata(dev);
+
+ clk_disable_unprepare(dsi->dsi_sys_clk);
+ clk_disable_unprepare(dsi->dsi_p_clk);
+ reset_control_assert(dsi->dsi_p_rst);
+ dsi->link_initialized = false;
+ return 0;
+}
+
+static UNIVERSAL_DEV_PM_OPS(cdns_dsi_pm_ops, cdns_dsi_suspend, cdns_dsi_resume,
+ NULL);
+
+static int cdns_dsi_drm_remove(struct platform_device *pdev)
+{
+ struct cdns_dsi *dsi = platform_get_drvdata(pdev);
+
+ mipi_dsi_host_unregister(&dsi->base);
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+static const struct of_device_id cdns_dsi_of_match[] = {
+ { .compatible = "cdns,dsi" },
+ { },
+};
+
+static int starfive_dsi_bind(struct device *dev, struct device *master, void *data)
+{
+ struct cdns_dsi *dsi;
+ struct cdns_dsi_input *input;
+ struct resource *res;
+ struct platform_device *pdev = to_platform_device(dev);
+ //struct drm_device *drm_dev = data;
+ //struct starfive_drm_private *private = drm_dev->dev_private;
+ int ret;
+ u32 val;
+
+ dsi = devm_kzalloc(&pdev->dev, sizeof(*dsi), GFP_KERNEL);
+ if (!dsi)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, dsi);
+ input = &dsi->input;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ dsi->regs = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(dsi->regs))
+ return PTR_ERR(dsi->regs);
+
+ dsi->dphy = devm_phy_get(&pdev->dev, "dphy");
+ if (IS_ERR(dsi->dphy))
+ return PTR_ERR(dsi->dphy);
+
+ val = readl(dsi->regs + ID_REG);
+
+ if (REV_VENDOR_ID(val) != 0xcad) {
+ dev_err(&pdev->dev, "invalid vendor id\n");
+ ret = -EINVAL;
+ goto err_disable_pclk;
+ }
+
+ val = readl(dsi->regs + IP_CONF);
+ dsi->direct_cmd_fifo_depth = 1 << (DIRCMD_FIFO_DEPTH(val) + 2);
+ dsi->rx_fifo_depth = RX_FIFO_DEPTH(val);
+ init_completion(&dsi->direct_cmd_comp);
+
+ writel(0, dsi->regs + MCTL_MAIN_DATA_CTL);
+ writel(0, dsi->regs + MCTL_MAIN_EN);
+ writel(0, dsi->regs + MCTL_MAIN_PHY_CTL);
+
+ input->id = CDNS_DPI_INPUT;
+ input->bridge.funcs = &cdns_dsi_bridge_funcs;
+ input->bridge.of_node = pdev->dev.of_node;
+
+ //drm_bridge_add(&input->bridge);
+
+ /* Mask all interrupts before registering the IRQ handler. */
+ writel(0, dsi->regs + MCTL_MAIN_STS_CTL);
+ writel(0, dsi->regs + MCTL_DPHY_ERR_CTL1);
+ writel(0, dsi->regs + CMD_MODE_STS_CTL);
+ writel(0, dsi->regs + DIRECT_CMD_STS_CTL);
+ writel(0, dsi->regs + DIRECT_CMD_RD_STS_CTL);
+ writel(0, dsi->regs + VID_MODE_STS_CTL);
+ writel(0, dsi->regs + TVG_STS_CTL);
+ writel(0, dsi->regs + DPI_IRQ_EN);
+
+ pm_runtime_enable(&pdev->dev);
+ dsi->base.dev = &pdev->dev;
+ dsi->base.ops = &cdns_dsi_ops;
+
+ ret = mipi_dsi_host_register(&dsi->base);
+ if (ret)
+ goto err_disable_runtime_pm;
+
+ init_seeed_panel();
+ return 0;
+
+err_disable_runtime_pm:
+ pm_runtime_disable(&pdev->dev);
+
+err_disable_pclk:
+ //clk_disable_unprepare(dsi->dsi_p_clk);
+
+ return ret;
+}
+
+static void starfive_dsi_unbind(struct device *dev, struct device *master, void *data)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct cdns_dsi *dsi = platform_get_drvdata(pdev);
+
+ exit_seeed_panel();
+ mipi_dsi_host_unregister(&dsi->base);
+ pm_runtime_disable(dev);
+}
+
+static const struct component_ops starfive_dsi_component_ops = {
+ .bind = starfive_dsi_bind,
+ .unbind = starfive_dsi_unbind,
+};
+
+static int starfive_dsi_probe(struct platform_device *pdev)
+{
+ return component_add(&pdev->dev, &starfive_dsi_component_ops);
+}
+
+static int starfive_dsi_remove(struct platform_device *pdev)
+{
+ component_del(&pdev->dev, &starfive_dsi_component_ops);
+ return 0;
+}
+
+struct platform_driver starfive_dsi_platform_driver = {
+ .probe = starfive_dsi_probe,
+ .remove = starfive_dsi_remove,
+ .driver = {
+ .name = "cdns-dsi",
+ .of_match_table = cdns_dsi_of_match,
+ .pm = &cdns_dsi_pm_ops,
+ },
+};
+
+MODULE_AUTHOR("Boris Brezillon <boris.brezillon@bootlin.com>");
+MODULE_DESCRIPTION("Cadence DSI driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:cdns-dsi");
+
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only\r
+/*\r
+ * Copyright (c) 2021 StarFive Technology Co., Ltd\r
+ * Author: StarFive <StarFive@starfivetech.com>\r
+ */\r
+\r
+#include <linux/clk.h>\r
+#include <linux/component.h>\r
+#include <linux/of_device.h>\r
+\r
+#include <drm/drm_atomic.h>\r
+#include <drm/drm_atomic_helper.h>\r
+#include <drm/drm_atomic_uapi.h>\r
+#include <drm/drm_fb_cma_helper.h>\r
+#include <drm/drm_print.h>\r
+#include <drm/drm_probe_helper.h>\r
+#include <drm/drm_vblank.h>\r
+#include <drm/drm_of.h>\r
+\r
+//#include "starfive_drm_drv.h"\r
+#include "starfive_drm_encoder.h"\r
+\r
+static void starfive_encoder_destroy(struct drm_encoder *encoder)\r
+{
+ drm_encoder_cleanup(encoder);
+}
+
+static const struct drm_encoder_funcs starfive_encoder_funcs = {\r
+ .destroy = starfive_encoder_destroy,\r
+};\r
+\r
+\r
+static int starfive_encoder_bind(struct device *dev, struct device *master, void *data)\r
+{\r
+ struct drm_device *drm_dev = data;\r
+ struct device_node *np = dev->of_node;\r
+ struct starfive_encoder *encoderp;\r
+ int ret;\r
+\r
+ struct drm_panel *tmp_panel;\r
+ struct drm_bridge *tmp_bridge;\r
+\r
+ u32 crtcs = 0;\r
+\r
+ encoderp = devm_kzalloc(dev, sizeof(*encoderp), GFP_KERNEL);\r
+ if (!encoderp)\r
+ return -ENOMEM;\r
+\r
+ encoderp->dev = dev;\r
+ encoderp->drm_dev = drm_dev;\r
+ dev_set_drvdata(dev, encoderp);\r
+\r
+ if (dev->of_node) {\r
+ crtcs = drm_of_find_possible_crtcs(drm_dev, dev->of_node);\r
+\r
+ if (of_property_read_u32(np, "encoder-type", &encoderp->encoder_type)) {\r
+ encoderp->encoder_type = DRM_MODE_ENCODER_TMDS;\r
+ }\r
+ }\r
+\r
+ /* If no CRTCs were found, fall back to our old behaviour */\r
+ if (crtcs == 0) {\r
+ dev_warn(dev, "Falling back to first CRTC\n");\r
+ crtcs = 1 << 0|1 << 1;\r
+ }\r
+\r
+ encoderp->encoder.possible_crtcs = 3;\r
+\r
+ ret = drm_encoder_init(drm_dev, &encoderp->encoder, &starfive_encoder_funcs,\r
+ encoderp->encoder_type, NULL);\r
+ if (ret)\r
+ goto err_encoder;\r
+\r
+\r
+ ret = drm_of_find_panel_or_bridge(dev->of_node,\r
+ 0, 0,\r
+ &tmp_panel,\r
+ &tmp_bridge);\r
+ if (ret) {\r
+ dev_err(dev,"endpoint returns %d\n", ret);\r
+ }\r
+\r
+ if (tmp_panel) {\r
+ dev_info(dev,"found panel on endpoint \n");\r
+ }\r
+ if (tmp_bridge) {\r
+ dev_info(dev,"found bridge on endpoint \n");\r
+ }\r
+\r
+ ret = drm_bridge_attach(&encoderp->encoder, tmp_bridge, NULL, 0);\r
+ if (ret)\r
+ goto err_bridge;\r
+\r
+ return 0;\r
+\r
+err_bridge:\r
+ drm_encoder_cleanup(&encoderp->encoder);\r
+err_encoder:\r
+ return ret;\r
+\r
+}\r
+\r
+static void starfive_encoder_unbind(struct device *dev, struct device *master, void *data)\r
+{\r
+ printk("===> %s , %d \n", __func__, __LINE__);\r
+}\r
+\r
+static const struct component_ops starfive_encoder_component_ops = {\r
+ .bind = starfive_encoder_bind,\r
+ .unbind = starfive_encoder_unbind,\r
+};\r
+\r
+static const struct of_device_id starfive_encoder_driver_dt_match[] = {\r
+ { .compatible = "starfive,display-encoder",\r
+ /*.data = &7100-crtc*/ },\r
+ {},\r
+};\r
+MODULE_DEVICE_TABLE(of, starfive_encoder_driver_dt_match);\r
+\r
+static int starfive_encoder_probe(struct platform_device *pdev)\r
+{\r
+ return component_add(&pdev->dev, &starfive_encoder_component_ops);\r
+}\r
+\r
+static int starfive_encoder_remove(struct platform_device *pdev)\r
+{\r
+ component_del(&pdev->dev, &starfive_encoder_component_ops);\r
+ return 0;\r
+}\r
+\r
+struct platform_driver starfive_encoder_driver = {\r
+ .probe = starfive_encoder_probe,\r
+ .remove = starfive_encoder_remove,\r
+ .driver = {\r
+ .name = "display-encoder",\r
+ .of_match_table = of_match_ptr(starfive_encoder_driver_dt_match),\r
+ },\r
+};\r
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0-only */\r
+/*\r
+ * Copyright (c) 2021 StarFive Technology Co., Ltd\r
+ * Author: StarFive <StarFive@starfivetech.com>\r
+ */\r
+\r
+#ifndef _STARFIVE_DRM_ENCODER_H\r
+#define _STARFIVE_DRM_ENCODER_H\r
+\r
+struct starfive_encoder {\r
+ struct drm_encoder encoder;\r
+ struct device *dev;\r
+ struct drm_device *drm_dev;\r
+ bool is_enabled;\r
+ int encoder_type;\r
+};\r
+#define to_starfive_encoder(x) container_of(x, struct starfive_encoder, encoder)\r
+\r
+#endif /* _STARFIVE_DRM_CRTC_H */\r
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021 StarFive Technology Co., Ltd.
+ *
+ * reference to seeed5inch.c
+ */
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_graph.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_device.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_panel.h>
+#include "vs_drv.h"
+#define RPI_DSI_DRIVER_NAME "cdns-dri-panel"
+
+/* I2C registers of the Atmel microcontroller. */
+enum REG_ADDR {
+ REG_ID = 0x80,
+ REG_PORTA, /* BIT(2) for horizontal flip, BIT(3) for vertical flip */
+ REG_PORTB,
+ REG_PORTC,
+ REG_PORTD,
+ REG_POWERON,
+ REG_PWM,
+ REG_DDRA,
+ REG_DDRB,
+ REG_DDRC,
+ REG_DDRD,
+ REG_TEST,
+ REG_WR_ADDRL,
+ REG_WR_ADDRH,
+ REG_READH,
+ REG_READL,
+ REG_WRITEH,
+ REG_WRITEL,
+ REG_ID2,
+};
+
+/* DSI D-PHY Layer Registers */
+#define D0W_DPHYCONTTX 0x0004
+#define CLW_DPHYCONTRX 0x0020
+#define D0W_DPHYCONTRX 0x0024
+#define D1W_DPHYCONTRX 0x0028
+#define COM_DPHYCONTRX 0x0038
+#define CLW_CNTRL 0x0040
+#define D0W_CNTRL 0x0044
+#define D1W_CNTRL 0x0048
+#define DFTMODE_CNTRL 0x0054
+
+/* DSI PPI Layer Registers */
+#define PPI_STARTPPI 0x0104
+#define PPI_BUSYPPI 0x0108
+#define PPI_LINEINITCNT 0x0110
+#define PPI_LPTXTIMECNT 0x0114
+#define PPI_CLS_ATMR 0x0140
+#define PPI_D0S_ATMR 0x0144
+#define PPI_D1S_ATMR 0x0148
+#define PPI_D0S_CLRSIPOCOUNT 0x0164
+#define PPI_D1S_CLRSIPOCOUNT 0x0168
+#define CLS_PRE 0x0180
+#define D0S_PRE 0x0184
+#define D1S_PRE 0x0188
+#define CLS_PREP 0x01A0
+#define D0S_PREP 0x01A4
+#define D1S_PREP 0x01A8
+#define CLS_ZERO 0x01C0
+#define D0S_ZERO 0x01C4
+#define D1S_ZERO 0x01C8
+#define PPI_CLRFLG 0x01E0
+#define PPI_CLRSIPO 0x01E4
+#define HSTIMEOUT 0x01F0
+#define HSTIMEOUTENABLE 0x01F4
+
+/* DSI Protocol Layer Registers */
+#define DSI_STARTDSI 0x0204
+#define DSI_BUSYDSI 0x0208
+#define DSI_LANEENABLE 0x0210
+#define DSI_LANEENABLE_CLOCK BIT(0)
+#define DSI_LANEENABLE_D0 BIT(1)
+#define DSI_LANEENABLE_D1 BIT(2)
+
+#define DSI_LANESTATUS0 0x0214
+#define DSI_LANESTATUS1 0x0218
+#define DSI_INTSTATUS 0x0220
+#define DSI_INTMASK 0x0224
+#define DSI_INTCLR 0x0228
+#define DSI_LPTXTO 0x0230
+#define DSI_MODE 0x0260
+#define DSI_PAYLOAD0 0x0268
+#define DSI_PAYLOAD1 0x026C
+#define DSI_SHORTPKTDAT 0x0270
+#define DSI_SHORTPKTREQ 0x0274
+#define DSI_BTASTA 0x0278
+#define DSI_BTACLR 0x027C
+
+/* DSI General Registers */
+#define DSIERRCNT 0x0300
+#define DSISIGMOD 0x0304
+
+/* DSI Application Layer Registers */
+#define APLCTRL 0x0400
+#define APLSTAT 0x0404
+#define APLERR 0x0408
+#define PWRMOD 0x040C
+#define RDPKTLN 0x0410
+#define PXLFMT 0x0414
+#define MEMWRCMD 0x0418
+
+/* LCDC/DPI Host Registers */
+#define LCDCTRL 0x0420
+#define HSR 0x0424
+#define HDISPR 0x0428
+#define VSR 0x042C
+#define VDISPR 0x0430
+#define VFUEN 0x0434
+
+/* DBI-B Host Registers */
+#define DBIBCTRL 0x0440
+
+/* SPI Master Registers */
+#define SPICMR 0x0450
+#define SPITCR 0x0454
+
+/* System Controller Registers */
+#define SYSSTAT 0x0460
+#define SYSCTRL 0x0464
+#define SYSPLL1 0x0468
+#define SYSPLL2 0x046C
+#define SYSPLL3 0x0470
+#define SYSPMCTRL 0x047C
+
+/* GPIO Registers */
+#define GPIOC 0x0480
+#define GPIOO 0x0484
+#define GPIOI 0x0488
+
+/* I2C Registers */
+#define I2CCLKCTRL 0x0490
+
+/* Chip/Rev Registers */
+#define IDREG 0x04A0
+
+/* Debug Registers */
+#define WCMDQUEUE 0x0500
+#define RCMDQUEUE 0x0504
+
+struct seeed_panel_dev {
+ struct i2c_client *client;
+ struct drm_panel base;
+ struct mipi_dsi_device *dsi;
+
+ struct device *dev;
+ int irq;
+
+};
+
+static int seeed_panel_i2c_write(struct i2c_client *client, u8 reg, u8 val)
+{
+ struct i2c_msg msg;
+ u8 buf[2];
+ int ret;
+
+ buf[0] = reg;
+ buf[1] = val;
+ msg.addr = client->addr;
+ msg.flags = 0;
+ msg.buf = buf;
+ msg.len = 2;
+
+ ret = i2c_transfer(client->adapter, &msg, 1);
+ if (ret >= 0)
+ return 0;
+
+ return ret;
+}
+
+static int seeed_panel_i2c_read(struct i2c_client *client, u8 reg, u8 *val)
+{
+ struct i2c_msg msg[2];
+ u8 buf[2];
+ int ret;
+
+ buf[0] = reg;
+ msg[0].addr = client->addr;
+ msg[0].flags = 0;
+ msg[0].buf = buf;
+ msg[0].len = 1;
+ msg[1].addr = client->addr;
+ msg[1].flags = I2C_M_RD;
+ msg[1].buf = val;
+ msg[1].len = 1;
+ ret = i2c_transfer(client->adapter, msg, 2);
+ if (ret >= 0)
+ return 0;
+
+ return ret;
+}
+
+enum dsi_rgb_pattern_t {
+ RGB_PAT_WHITE,
+ RGB_PAT_BLACK,
+ RGB_PAT_RED,
+ RGB_PAT_GREEN,
+ RGB_PAT_BLUE,
+ RGB_PAT_HORIZ_COLORBAR,
+ RGB_PAT_VERT_COLORBAR,
+ RGB_PAT_NUM
+};
+
+static struct seeed_panel_dev *panel_to_seeed(struct drm_panel *panel)
+{
+ return container_of(panel, struct seeed_panel_dev, base);
+}
+
+static const struct drm_display_mode seeed_panel_modes[] = {
+ {
+ .clock = 33000000 / 1000,
+ .hdisplay = 800,
+ .hsync_start = 800 + 50,
+ .hsync_end = 800 + 50 + 20,
+ .htotal = 800 + 50 + 20 + 10,
+ .vdisplay = 480,
+ .vsync_start = 480 + 135,
+ .vsync_end = 480 + 135 + 5,
+ .vtotal = 480 + 135 + 5 + 5,
+ },
+};
+
+static int seeed_panel_disable(struct drm_panel *panel)
+{
+ struct seeed_panel_dev *sp = panel_to_seeed(panel);
+
+ seeed_panel_i2c_write(sp->client, REG_PWM, 0);
+ seeed_panel_i2c_write(sp->client, REG_POWERON, 0);
+ udelay(1);
+
+ return 0;
+}
+
+static int seeed_panel_noop(struct drm_panel *panel)
+{
+ return 0;
+}
+
+static int seeed_dsi_write(struct drm_panel *panel, u16 reg, u32 val)
+{
+ struct seeed_panel_dev *sp = panel_to_seeed(panel);
+
+ u8 msg[] = {
+ reg,
+ reg >> 8,
+ val,
+ val >> 8,
+ val >> 16,
+ val >> 24,
+ };
+ mipi_dsi_generic_write(sp->dsi, msg, sizeof(msg));
+
+ return 0;
+}
+
+static int seeed_panel_enable(struct drm_panel *panel)
+{
+ struct seeed_panel_dev *sp = panel_to_seeed(panel);
+ int i;
+ u8 reg_value = 0;
+
+ seeed_panel_i2c_write(sp->client, REG_POWERON, 1);
+ /* Wait for nPWRDWN to go low to indicate poweron is done. */
+ for (i = 0; i < 100; i++) {
+ seeed_panel_i2c_read(sp->client, REG_PORTB, ®_value);
+ if (reg_value & 1)
+ break;
+ }
+
+ seeed_dsi_write(panel, DSI_LANEENABLE,
+ DSI_LANEENABLE_CLOCK |
+ DSI_LANEENABLE_D0);
+ seeed_dsi_write(panel, PPI_D0S_CLRSIPOCOUNT, 0x05);
+ seeed_dsi_write(panel, PPI_D1S_CLRSIPOCOUNT, 0x05);
+ seeed_dsi_write(panel, PPI_D0S_ATMR, 0x00);
+ seeed_dsi_write(panel, PPI_D1S_ATMR, 0x00);
+ seeed_dsi_write(panel, PPI_LPTXTIMECNT, 0x03);
+ seeed_dsi_write(panel, SPICMR, 0x00);
+ seeed_dsi_write(panel, LCDCTRL, 0x00100150);
+ seeed_dsi_write(panel, SYSCTRL, 0x040f);
+ msleep(100);
+
+ seeed_dsi_write(panel, PPI_STARTPPI, 0x01);
+ seeed_dsi_write(panel, DSI_STARTDSI, 0x01);
+ msleep(100);
+
+ /* Turn on the backlight. */
+ seeed_panel_i2c_write(sp->client, REG_PWM, 255);
+
+ /* Default to the same orientation as the closed source
+ * firmware used for the panel. Runtime rotation
+ * configuration will be supported using VC4's plane
+ * orientation bits.
+ */
+ seeed_panel_i2c_write(sp->client, REG_PORTA, BIT(2));
+
+ return 0;
+}
+
+static int seeed_panel_get_modes(struct drm_panel *panel,
+ struct drm_connector *connector)
+{
+ unsigned int i, num = 0;
+ static const u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24;
+
+ for (i = 0; i < ARRAY_SIZE(seeed_panel_modes); i++) {
+ const struct drm_display_mode *m = &seeed_panel_modes[i];
+ struct drm_display_mode *mode;
+
+ mode = drm_mode_duplicate(connector->dev, m);
+ if (!mode) {
+ dev_err(panel->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 (i == 0)
+ mode->type |= DRM_MODE_TYPE_PREFERRED;
+
+ drm_mode_set_name(mode);
+ drm_mode_probed_add(connector, mode);
+ num++;
+ }
+
+ connector->display_info.bpc = 8;
+ connector->display_info.width_mm = 154;
+ connector->display_info.height_mm = 86;
+ drm_display_info_set_bus_formats(&connector->display_info,
+ &bus_format, 1);
+
+ return num;
+}
+
+static const struct drm_panel_funcs seeed_panel_funcs = {
+ .disable = seeed_panel_disable,
+ .unprepare = seeed_panel_noop,
+ .prepare = seeed_panel_noop,
+ .enable = seeed_panel_enable,
+ .get_modes = seeed_panel_get_modes,
+};
+
+static int seeed_panel_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+ u8 reg_value = 0;
+ struct seeed_panel_dev *seeed_panel;
+ struct device_node *endpoint, *dsi_host_node;
+ struct mipi_dsi_host *host;
+ struct device *dev = &client->dev;
+
+ struct mipi_dsi_device_info info = {
+ .type = RPI_DSI_DRIVER_NAME,
+ .channel = 0, //0,
+ .node = NULL,
+ };
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_warn(&client->dev,
+ "I2C adapter doesn't support I2C_FUNC_SMBUS_BYTE\n");
+ return -EIO;
+ }
+
+ seeed_panel = devm_kzalloc(&client->dev, sizeof(struct seeed_panel_dev), GFP_KERNEL);
+ if (!seeed_panel)
+ return -ENOMEM;
+
+ seeed_panel->client = client;
+ i2c_set_clientdata(client, seeed_panel);
+
+ seeed_panel_i2c_read(client, REG_ID, ®_value);
+ switch (reg_value) {
+ case 0xde: /* ver 1 */
+ case 0xc3: /* ver 2 */
+ break;
+
+ default:
+ dev_err(&client->dev, "Unknown Atmel firmware revision: 0x%02x\n", reg_value);
+ return -ENODEV;
+ }
+
+ seeed_panel_i2c_write(client, REG_POWERON, 0);
+
+ endpoint = of_graph_get_next_endpoint(dev->of_node, NULL);
+ if (!endpoint)
+ return -ENODEV;
+
+ dsi_host_node = of_graph_get_remote_port_parent(endpoint);
+ if (!dsi_host_node)
+ goto error;
+
+ host = of_find_mipi_dsi_host_by_node(dsi_host_node);
+ of_node_put(dsi_host_node);
+ if (!host) {
+ of_node_put(endpoint);
+ return -EPROBE_DEFER;
+ }
+
+ drm_panel_init(&seeed_panel->base, dev, &seeed_panel_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+
+ /* This appears last, as it's what will unblock the DSI host
+ * driver's component bind function.
+ */
+ drm_panel_add(&seeed_panel->base);
+
+ info.node = of_node_get(of_graph_get_remote_port(endpoint));
+ if (!info.node)
+ goto error;
+
+ of_node_put(endpoint);
+
+ seeed_panel->dsi = mipi_dsi_device_register_full(host, &info);
+ if (IS_ERR(seeed_panel->dsi)) {
+ dev_err(dev, "DSI device registration failed: %ld\n",
+ PTR_ERR(seeed_panel->dsi));
+ return PTR_ERR(seeed_panel->dsi);
+ }
+
+ return 0;
+error:
+ of_node_put(endpoint);
+ return -ENODEV;
+
+}
+
+static int seeed_panel_remove(struct i2c_client *client)
+{
+ struct seeed_panel_dev *seeed_panel = i2c_get_clientdata(client);
+
+ mipi_dsi_detach(seeed_panel->dsi);
+
+ drm_panel_remove(&seeed_panel->base);
+
+ mipi_dsi_device_unregister(seeed_panel->dsi);
+ // kfree(seeed_panel->dsi);
+ return 0;
+}
+
+static const struct i2c_device_id seeed_panel_id[] = {
+ { "seeed_panel", 0 },
+ { }
+};
+
+static const struct of_device_id seeed_panel_dt_ids[] = {
+ { .compatible = "seeed_panel", },
+ { /* sentinel */ }
+};
+
+static struct i2c_driver seeed_panel_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "seeed_panel",
+ .of_match_table = seeed_panel_dt_ids,
+ },
+ .probe = seeed_panel_probe,
+ .remove = seeed_panel_remove,
+ .id_table = seeed_panel_id,
+};
+
+static int seeed_dsi_probe(struct mipi_dsi_device *dsi)
+{
+ int ret;
+
+ dsi->mode_flags = (MIPI_DSI_MODE_VIDEO |
+ MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
+ MIPI_DSI_MODE_LPM);
+ dsi->format = MIPI_DSI_FMT_RGB888;
+ dsi->lanes = 1;
+
+ ret = mipi_dsi_attach(dsi);
+ if (ret)
+ dev_err(&dsi->dev, "failed to attach dsi to host: %d\n", ret);
+
+ return ret;
+}
+
+static struct mipi_dsi_driver seeed_dsi_driver = {
+ .driver.name = RPI_DSI_DRIVER_NAME,
+ .probe = seeed_dsi_probe,
+};
+
+int init_seeed_panel(void)
+{
+ int err;
+
+ mipi_dsi_driver_register(&seeed_dsi_driver);
+ err = i2c_add_driver(&seeed_panel_driver);
+ return err;
+}
+EXPORT_SYMBOL(init_seeed_panel);
+
+void exit_seeed_panel(void)
+{
+ i2c_del_driver(&seeed_panel_driver);
+ mipi_dsi_driver_unregister(&seeed_dsi_driver);
+}
+EXPORT_SYMBOL(exit_seeed_panel);
+
+MODULE_DESCRIPTION("A driver for seeed_panel");
+MODULE_LICENSE("GPL");
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020 VeriSilicon Holdings Co., Ltd.
+ */
+
+#include <linux/clk.h>
+#include <linux/debugfs.h>
+#include <drm/drm_gem_atomic_helper.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_atomic.h>
+
+#include <drm/drm_crtc.h>
+#include <drm/vs_drm.h>
+
+#include "vs_crtc.h"
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 5, 0)
+#include <drm/drm_vblank.h>
+#endif
+
+void vs_crtc_destroy(struct drm_crtc *crtc)
+{
+ struct vs_crtc *vs_crtc = to_vs_crtc(crtc);
+
+ drm_crtc_cleanup(crtc);
+ kfree(vs_crtc);
+}
+
+static void vs_crtc_reset(struct drm_crtc *crtc)
+{
+ struct vs_crtc_state *state;
+
+ if (crtc->state) {
+ __drm_atomic_helper_crtc_destroy_state(crtc->state);
+
+ state = to_vs_crtc_state(crtc->state);
+ kfree(state);
+ crtc->state = NULL;
+ }
+
+ state = kzalloc(sizeof(*state), GFP_KERNEL);
+ if (state == NULL)
+ return;
+
+ __drm_atomic_helper_crtc_reset(crtc, &state->base);
+
+ state->sync_mode = VS_SINGLE_DC;
+ state->output_fmt = MEDIA_BUS_FMT_RBG888_1X24;
+ state->encoder_type = DRM_MODE_ENCODER_NONE;
+#ifdef CONFIG_VERISILICON_MMU
+ state->mmu_prefetch = VS_MMU_PREFETCH_DISABLE;
+#endif
+}
+
+static struct drm_crtc_state *
+vs_crtc_atomic_duplicate_state(struct drm_crtc *crtc)
+{
+ struct vs_crtc_state *ori_state;
+ struct vs_crtc_state *state;
+
+ if (WARN_ON(!crtc->state))
+ return NULL;
+
+ ori_state = to_vs_crtc_state(crtc->state);
+ state = kzalloc(sizeof(*state), GFP_KERNEL);
+ if (!state)
+ return NULL;
+
+ __drm_atomic_helper_crtc_duplicate_state(crtc, &state->base);
+
+ state->sync_mode = ori_state->sync_mode;
+ state->output_fmt = ori_state->output_fmt;
+ state->encoder_type = ori_state->encoder_type;
+ state->bg_color = ori_state->bg_color;
+ state->bpp = ori_state->bpp;
+ state->sync_enable = ori_state->sync_enable;
+ state->dither_enable = ori_state->dither_enable;
+ state->underflow = ori_state->underflow;
+#ifdef CONFIG_VERISILICON_MMU
+ state->mmu_prefetch = ori_state->mmu_prefetch;
+#endif
+
+ return &state->base;
+}
+
+static void vs_crtc_atomic_destroy_state(struct drm_crtc *crtc,
+ struct drm_crtc_state *state)
+{
+ __drm_atomic_helper_crtc_destroy_state(state);
+ kfree(to_vs_crtc_state(state));
+}
+
+static int vs_crtc_atomic_set_property(struct drm_crtc *crtc,
+ struct drm_crtc_state *state,
+ struct drm_property *property,
+ uint64_t val)
+{
+ struct vs_crtc *vs_crtc = to_vs_crtc(crtc);
+ struct vs_crtc_state *vs_crtc_state = to_vs_crtc_state(state);
+
+ if (property == vs_crtc->sync_mode)
+ vs_crtc_state->sync_mode = val;
+ else if (property == vs_crtc->mmu_prefetch)
+ vs_crtc_state->mmu_prefetch = val;
+ else if (property == vs_crtc->bg_color)
+ vs_crtc_state->bg_color = val;
+ else if (property == vs_crtc->panel_sync)
+ vs_crtc_state->sync_enable = val;
+ else if (property == vs_crtc->dither)
+ vs_crtc_state->dither_enable = val;
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+static int vs_crtc_atomic_get_property(struct drm_crtc *crtc,
+ const struct drm_crtc_state *state,
+ struct drm_property *property,
+ uint64_t *val)
+{
+ struct vs_crtc *vs_crtc = to_vs_crtc(crtc);
+ const struct vs_crtc_state *vs_crtc_state =
+ container_of(state, const struct vs_crtc_state, base);
+
+ if (property == vs_crtc->sync_mode)
+ *val = vs_crtc_state->sync_mode;
+ else if (property == vs_crtc->mmu_prefetch)
+ *val = vs_crtc_state->mmu_prefetch;
+ else if (property == vs_crtc->bg_color)
+ *val = vs_crtc_state->bg_color;
+ else if (property == vs_crtc->panel_sync)
+ *val = vs_crtc_state->sync_enable;
+ else if (property == vs_crtc->dither)
+ *val = vs_crtc_state->dither_enable;
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+#ifdef CONFIG_DEBUG_FS
+static int vs_crtc_debugfs_show(struct seq_file *s, void *data)
+{
+ struct drm_crtc *crtc = s->private;
+ struct vs_crtc_state *crtc_state = to_vs_crtc_state(crtc->state);
+ struct drm_display_mode *mode = &crtc->state->adjusted_mode;
+
+ seq_printf(s, "crtc[%u]: %s\n", crtc->base.id, crtc->name);
+ seq_printf(s, "\tactive = %d\n", crtc->state->active);
+ seq_printf(s, "\tsize = %dx%d\n", mode->hdisplay, mode->vdisplay);
+ seq_printf(s, "\tbpp = %u\n", crtc_state->bpp);
+ seq_printf(s, "\tunderflow = %d\n", crtc_state->underflow);
+
+ return 0;
+}
+
+static int vs_crtc_debugfs_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, vs_crtc_debugfs_show, inode->i_private);
+}
+
+static const struct file_operations vs_crtc_debugfs_fops = {
+ .open = vs_crtc_debugfs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int vs_crtc_debugfs_init(struct drm_crtc *crtc)
+{
+ debugfs_create_file("status", 0444, crtc->debugfs_entry,
+ crtc, &vs_crtc_debugfs_fops);
+
+ return 0;
+}
+#else
+static int vs_crtc_debugfs_init(struct drm_crtc *crtc)
+{
+ return 0;
+}
+#endif /* CONFIG_DEBUG_FS */
+
+static int vs_crtc_late_register(struct drm_crtc *crtc)
+{
+ return vs_crtc_debugfs_init(crtc);
+}
+
+static int vs_crtc_enable_vblank(struct drm_crtc *crtc)
+{
+ struct vs_crtc *vs_crtc = to_vs_crtc(crtc);
+
+ vs_crtc->funcs->enable_vblank(vs_crtc->dev, true);
+
+ return 0;
+}
+
+static void vs_crtc_disable_vblank(struct drm_crtc *crtc)
+{
+ struct vs_crtc *vs_crtc = to_vs_crtc(crtc);
+
+ vs_crtc->funcs->enable_vblank(vs_crtc->dev, false);
+}
+
+static const struct drm_crtc_funcs vs_crtc_funcs = {
+ .set_config = drm_atomic_helper_set_config,
+ .destroy = vs_crtc_destroy,
+ .page_flip = drm_atomic_helper_page_flip,
+ .reset = vs_crtc_reset,
+ .atomic_duplicate_state = vs_crtc_atomic_duplicate_state,
+ .atomic_destroy_state = vs_crtc_atomic_destroy_state,
+ .atomic_set_property = vs_crtc_atomic_set_property,
+ .atomic_get_property = vs_crtc_atomic_get_property,
+ //.gamma_set = drm_atomic_helper_legacy_gamma_set,
+ .late_register = vs_crtc_late_register,
+ .enable_vblank = vs_crtc_enable_vblank,
+ .disable_vblank = vs_crtc_disable_vblank,
+};
+
+static u8 cal_pixel_bits(u32 bus_format)
+{
+ u8 bpp;
+
+ switch (bus_format) {
+ case MEDIA_BUS_FMT_RGB565_1X16:
+ case MEDIA_BUS_FMT_UYVY8_1X16:
+ bpp = 16;
+ break;
+ case MEDIA_BUS_FMT_RGB666_1X18:
+ case MEDIA_BUS_FMT_RGB666_1X24_CPADHI:
+ bpp = 18;
+ break;
+ case MEDIA_BUS_FMT_UYVY10_1X20:
+ bpp = 20;
+ break;
+ case MEDIA_BUS_FMT_BGR888_1X24:
+ case MEDIA_BUS_FMT_UYYVYY8_0_5X24:
+ case MEDIA_BUS_FMT_YUV8_1X24:
+ bpp = 24;
+ break;
+ case MEDIA_BUS_FMT_RGB101010_1X30:
+ case MEDIA_BUS_FMT_UYYVYY10_0_5X30:
+ case MEDIA_BUS_FMT_YUV10_1X30:
+ bpp = 30;
+ break;
+ default:
+ bpp = 24;
+ break;
+ }
+
+ return bpp;
+}
+
+static bool vs_crtc_mode_fixup(struct drm_crtc *crtc,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ struct vs_crtc *vs_crtc = to_vs_crtc(crtc);
+
+ return vs_crtc->funcs->mode_fixup(vs_crtc->dev, mode, adjusted_mode);
+}
+
+static void vs_crtc_atomic_enable(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+{
+ struct vs_crtc *vs_crtc = to_vs_crtc(crtc);
+ struct vs_crtc_state *vs_crtc_state = to_vs_crtc_state(crtc->state);
+
+ vs_crtc_state->bpp = cal_pixel_bits(vs_crtc_state->output_fmt);
+
+ vs_crtc->funcs->enable(vs_crtc->dev, crtc);
+
+ drm_crtc_vblank_on(crtc);
+}
+
+static void vs_crtc_atomic_disable(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+{
+ struct vs_crtc *vs_crtc = to_vs_crtc(crtc);
+
+ drm_crtc_vblank_off(crtc);
+
+ vs_crtc->funcs->disable(vs_crtc->dev, crtc);
+
+ if (crtc->state->event && !crtc->state->active) {
+ spin_lock_irq(&crtc->dev->event_lock);
+ drm_crtc_send_vblank_event(crtc, crtc->state->event);
+ spin_unlock_irq(&crtc->dev->event_lock);
+
+ crtc->state->event = NULL;
+ }
+}
+
+static void vs_crtc_atomic_begin(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+{
+ 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 vs_crtc *vs_crtc = to_vs_crtc(crtc);
+ struct device *dev = vs_crtc->dev;
+ struct drm_property_blob *blob = crtc->state->gamma_lut;
+ struct drm_color_lut *lut;
+
+ if (crtc_state->color_mgmt_changed) {
+ if ((blob) && (blob->length)) {
+ lut = blob->data;
+ vs_crtc->funcs->set_gamma(dev, crtc, lut,
+ blob->length / sizeof(*lut));
+ vs_crtc->funcs->enable_gamma(dev, crtc, true);
+ } else {
+ vs_crtc->funcs->enable_gamma(dev, crtc, false);
+ }
+ }
+}
+
+static void vs_crtc_atomic_flush(struct drm_crtc *crtc,
+ struct drm_atomic_state *state)
+{
+ struct vs_crtc *vs_crtc = to_vs_crtc(crtc);
+ struct drm_pending_vblank_event *event = crtc->state->event;
+
+ vs_crtc->funcs->commit(vs_crtc->dev);
+
+ if (event) {
+ WARN_ON(drm_crtc_vblank_get(crtc) != 0);
+
+ spin_lock_irq(&crtc->dev->event_lock);
+ drm_crtc_arm_vblank_event(crtc, event);
+ spin_unlock_irq(&crtc->dev->event_lock);
+ crtc->state->event = NULL;
+ }
+}
+
+static const struct drm_crtc_helper_funcs vs_crtc_helper_funcs = {
+ .mode_fixup = vs_crtc_mode_fixup,
+ .atomic_enable = vs_crtc_atomic_enable,
+ .atomic_disable = vs_crtc_atomic_disable,
+ .atomic_begin = vs_crtc_atomic_begin,
+ .atomic_flush = vs_crtc_atomic_flush,
+};
+
+static const struct drm_prop_enum_list vs_sync_mode_enum_list[] = {
+ { VS_SINGLE_DC, "single dc mode" },
+ { VS_MULTI_DC_PRIMARY, "primary dc for multi dc mode" },
+ { VS_MULTI_DC_SECONDARY, "secondary dc for multi dc mode" },
+};
+
+#ifdef CONFIG_VERISILICON_MMU
+static const struct drm_prop_enum_list vs_mmu_prefetch_enum_list[] = {
+ { VS_MMU_PREFETCH_DISABLE, "disable mmu prefetch" },
+ { VS_MMU_PREFETCH_ENABLE, "enable mmu prefetch" },
+};
+#endif
+
+struct vs_crtc *vs_crtc_create(struct drm_device *drm_dev,
+ struct vs_dc_info *info)
+{
+ struct vs_crtc *crtc;
+ int ret;
+
+ if (!info)
+ return NULL;
+
+ crtc = kzalloc(sizeof(struct vs_crtc), GFP_KERNEL);
+ if (!crtc)
+ return NULL;
+
+ ret = drm_crtc_init_with_planes(drm_dev, &crtc->base,
+ NULL, NULL, &vs_crtc_funcs,
+ info->name ? info->name : NULL);
+ if (ret)
+ goto err_free_crtc;
+
+ drm_crtc_helper_add(&crtc->base, &vs_crtc_helper_funcs);
+
+ /* Set up the crtc properties */
+ if (info->pipe_sync) {
+ crtc->sync_mode = drm_property_create_enum(drm_dev, 0,
+ "SYNC_MODE",
+ vs_sync_mode_enum_list,
+ ARRAY_SIZE(vs_sync_mode_enum_list));
+
+ if (!crtc->sync_mode)
+ goto err_cleanup_crts;
+
+ drm_object_attach_property(&crtc->base.base,
+ crtc->sync_mode,
+ VS_SINGLE_DC);
+ }
+
+ if (info->gamma_size) {
+ ret = drm_mode_crtc_set_gamma_size(&crtc->base,
+ info->gamma_size);
+ if (ret)
+ goto err_cleanup_crts;
+
+ drm_crtc_enable_color_mgmt(&crtc->base, 0, false,
+ info->gamma_size);
+ }
+
+ if (info->background) {
+ crtc->bg_color = drm_property_create_range(drm_dev, 0,
+ "BG_COLOR", 0, 0xffffffff);
+
+ if (!crtc->bg_color)
+ goto err_cleanup_crts;
+
+ drm_object_attach_property(&crtc->base.base, crtc->bg_color, 0);
+ }
+
+ if (info->panel_sync) {
+ crtc->panel_sync = drm_property_create_bool(drm_dev, 0, "SYNC_ENABLED");
+
+ if (!crtc->panel_sync)
+ goto err_cleanup_crts;
+
+ drm_object_attach_property(&crtc->base.base, crtc->panel_sync, 0);
+ }
+
+ crtc->dither = drm_property_create_bool(drm_dev, 0, "DITHER_ENABLED");
+ if (!crtc->dither)
+ goto err_cleanup_crts;
+
+ drm_object_attach_property(&crtc->base.base, crtc->dither, 0);
+
+#ifdef CONFIG_VERISILICON_MMU
+ if (info->mmu_prefetch) {
+ crtc->mmu_prefetch = drm_property_create_enum(drm_dev, 0,
+ "MMU_PREFETCH",
+ vs_mmu_prefetch_enum_list,
+ ARRAY_SIZE(vs_mmu_prefetch_enum_list));
+ if (!crtc->mmu_prefetch)
+ goto err_cleanup_crts;
+
+ drm_object_attach_property(&crtc->base.base,
+ crtc->mmu_prefetch,
+ VS_MMU_PREFETCH_DISABLE);
+ }
+#endif
+
+ crtc->max_bpc = info->max_bpc;
+ crtc->color_formats = info->color_formats;
+ return crtc;
+
+err_cleanup_crts:
+ drm_crtc_cleanup(&crtc->base);
+
+err_free_crtc:
+ kfree(crtc);
+ return NULL;
+}
+
+void vs_crtc_handle_vblank(struct drm_crtc *crtc, bool underflow)
+{
+ struct vs_crtc_state *vs_crtc_state = to_vs_crtc_state(crtc->state);
+
+ drm_crtc_handle_vblank(crtc);
+
+ vs_crtc_state->underflow = underflow;
+}
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2020 VeriSilicon Holdings Co., Ltd.
+ */
+
+#ifndef __VS_CRTC_H__
+#define __VS_CRTC_H__
+
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+
+#include "vs_type.h"
+
+struct vs_crtc_funcs {
+ void (*enable)(struct device *dev, struct drm_crtc *crtc);
+ void (*disable)(struct device *dev, struct drm_crtc *crtc);
+ bool (*mode_fixup)(struct device *dev,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode);
+ void (*set_gamma)(struct device *dev, struct drm_crtc *crtc,
+ struct drm_color_lut *lut, unsigned int size);
+ void (*enable_gamma)(struct device *dev, struct drm_crtc *crtc,
+ bool enable);
+ void (*enable_vblank)(struct device *dev, bool enable);
+ void (*commit)(struct device *dev);
+};
+
+struct vs_crtc_state {
+ struct drm_crtc_state base;
+
+ u32 sync_mode;
+ u32 output_fmt;
+ u32 bg_color;
+ u8 encoder_type;
+ u8 mmu_prefetch;
+ u8 bpp;
+ bool sync_enable;
+ bool dither_enable;
+ bool underflow;
+};
+
+struct vs_crtc {
+ struct drm_crtc base;
+ struct device *dev;
+ struct drm_pending_vblank_event *event;
+ unsigned int max_bpc;
+ unsigned int color_formats; /* supported color format */
+
+ struct drm_property *sync_mode;
+ struct drm_property *mmu_prefetch;
+ struct drm_property *bg_color;
+ struct drm_property *panel_sync;
+ struct drm_property *dither;
+
+ const struct vs_crtc_funcs *funcs;
+};
+
+void vs_crtc_destroy(struct drm_crtc *crtc);
+
+struct vs_crtc *vs_crtc_create(struct drm_device *drm_dev,
+ struct vs_dc_info *info);
+void vs_crtc_handle_vblank(struct drm_crtc *crtc, bool underflow);
+
+static inline struct vs_crtc *to_vs_crtc(struct drm_crtc *crtc)
+{
+ return container_of(crtc, struct vs_crtc, base);
+}
+
+static inline struct vs_crtc_state *
+to_vs_crtc_state(struct drm_crtc_state *state)
+{
+ return container_of(state, struct vs_crtc_state, base);
+}
+#endif /* __VS_CRTC_H__ */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020 VeriSilicon Holdings Co., Ltd.
+ */
+
+#include <linux/component.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/media-bus-format.h>
+
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/vs_drm.h>
+
+#include "vs_type.h"
+#include "vs_dc_hw.h"
+#include "vs_dc.h"
+#include "vs_crtc.h"
+#include "vs_drv.h"
+#include <soc/starfive/vic7100.h>
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 5, 0)
+#include <drm/drm_fourcc.h>
+#include <drm/drm_vblank.h>
+#endif
+
+static inline void update_format(u32 format, u64 mod, struct dc_hw_fb *fb)
+{
+ u8 f = FORMAT_A8R8G8B8;
+
+ switch (format) {
+ case DRM_FORMAT_XRGB4444:
+ case DRM_FORMAT_RGBX4444:
+ case DRM_FORMAT_XBGR4444:
+ case DRM_FORMAT_BGRX4444:
+ f = FORMAT_X4R4G4B4;
+ break;
+ case DRM_FORMAT_ARGB4444:
+ case DRM_FORMAT_RGBA4444:
+ case DRM_FORMAT_ABGR4444:
+ case DRM_FORMAT_BGRA4444:
+ f = FORMAT_A4R4G4B4;
+ break;
+ case DRM_FORMAT_XRGB1555:
+ case DRM_FORMAT_RGBX5551:
+ case DRM_FORMAT_XBGR1555:
+ case DRM_FORMAT_BGRX5551:
+ f = FORMAT_X1R5G5B5;
+ break;
+ case DRM_FORMAT_ARGB1555:
+ case DRM_FORMAT_RGBA5551:
+ case DRM_FORMAT_ABGR1555:
+ case DRM_FORMAT_BGRA5551:
+ f = FORMAT_A1R5G5B5;
+ break;
+ case DRM_FORMAT_RGB565:
+ case DRM_FORMAT_BGR565:
+ f = FORMAT_R5G6B5;
+ break;
+ case DRM_FORMAT_XRGB8888:
+ case DRM_FORMAT_RGBX8888:
+ case DRM_FORMAT_XBGR8888:
+ case DRM_FORMAT_BGRX8888:
+ f = FORMAT_X8R8G8B8;
+ break;
+ case DRM_FORMAT_ARGB8888:
+ case DRM_FORMAT_RGBA8888:
+ case DRM_FORMAT_ABGR8888:
+ case DRM_FORMAT_BGRA8888:
+ f = FORMAT_A8R8G8B8;
+ break;
+ case DRM_FORMAT_YUYV:
+ case DRM_FORMAT_YVYU:
+ f = FORMAT_YUY2;
+ break;
+ case DRM_FORMAT_UYVY:
+ case DRM_FORMAT_VYUY:
+ f = FORMAT_UYVY;
+ break;
+ case DRM_FORMAT_YUV420:
+ case DRM_FORMAT_YVU420:
+ f = FORMAT_YV12;
+ break;
+ case DRM_FORMAT_NV21:
+ f = FORMAT_NV12;
+ break;
+ case DRM_FORMAT_NV16:
+ case DRM_FORMAT_NV61:
+ f = FORMAT_NV16;
+ break;
+ case DRM_FORMAT_P010:
+ f = FORMAT_P010;
+ break;
+ case DRM_FORMAT_ARGB2101010:
+ case DRM_FORMAT_RGBA1010102:
+ case DRM_FORMAT_ABGR2101010:
+ case DRM_FORMAT_BGRA1010102:
+ f = FORMAT_A2R10G10B10;
+ break;
+ case DRM_FORMAT_NV12:
+ if (fourcc_mod_vs_get_type(mod) ==
+ DRM_FORMAT_MOD_VS_TYPE_CUSTOM_10BIT)
+ f = FORMAT_NV12_10BIT;
+ else
+ f = FORMAT_NV12;
+ break;
+ case DRM_FORMAT_YUV444:
+ if (fourcc_mod_vs_get_type(mod) ==
+ DRM_FORMAT_MOD_VS_TYPE_CUSTOM_10BIT)
+ f = FORMAT_YUV444_10BIT;
+ else
+ f = FORMAT_YUV444;
+ break;
+ default:
+ break;
+ }
+
+ fb->format = f;
+}
+
+static inline void update_swizzle(u32 format, struct dc_hw_fb *fb)
+{
+ fb->swizzle = SWIZZLE_ARGB;
+ fb->uv_swizzle = 0;
+
+ switch (format) {
+ case DRM_FORMAT_RGBX4444:
+ case DRM_FORMAT_RGBA4444:
+ case DRM_FORMAT_RGBX5551:
+ case DRM_FORMAT_RGBA5551:
+ case DRM_FORMAT_RGBX8888:
+ case DRM_FORMAT_RGBA8888:
+ case DRM_FORMAT_RGBA1010102:
+ fb->swizzle = SWIZZLE_RGBA;
+ break;
+ case DRM_FORMAT_XBGR4444:
+ case DRM_FORMAT_ABGR4444:
+ case DRM_FORMAT_XBGR1555:
+ case DRM_FORMAT_ABGR1555:
+ case DRM_FORMAT_BGR565:
+ case DRM_FORMAT_XBGR8888:
+ case DRM_FORMAT_ABGR8888:
+ case DRM_FORMAT_ABGR2101010:
+ fb->swizzle = SWIZZLE_ABGR;
+ break;
+ case DRM_FORMAT_BGRX4444:
+ case DRM_FORMAT_BGRA4444:
+ case DRM_FORMAT_BGRX5551:
+ case DRM_FORMAT_BGRA5551:
+ case DRM_FORMAT_BGRX8888:
+ case DRM_FORMAT_BGRA8888:
+ case DRM_FORMAT_BGRA1010102:
+ fb->swizzle = SWIZZLE_BGRA;
+ break;
+ case DRM_FORMAT_YVYU:
+ case DRM_FORMAT_VYUY:
+ case DRM_FORMAT_NV21:
+ case DRM_FORMAT_NV61:
+ fb->uv_swizzle = 1;
+ break;
+ default:
+ break;
+ }
+}
+
+static inline void update_watermark(struct drm_property_blob *watermark,
+ struct dc_hw_fb *fb)
+{
+ struct drm_vs_watermark *data;
+ fb->water_mark = 0;
+
+ if (watermark) {
+ data = watermark->data;
+ fb->water_mark = data->watermark & 0xFFFFF;
+ }
+}
+
+static inline u8 to_vs_rotation(unsigned int rotation)
+{
+ u8 rot;
+
+ switch (rotation & DRM_MODE_REFLECT_MASK) {
+ case DRM_MODE_REFLECT_X:
+ rot = FLIP_X;
+ return rot;
+ case DRM_MODE_REFLECT_Y:
+ rot = FLIP_Y;
+ return rot;
+ case DRM_MODE_REFLECT_X | DRM_MODE_REFLECT_Y:
+ rot = FLIP_XY;
+ return rot;
+ default:
+ break;
+ }
+
+ switch (rotation & DRM_MODE_ROTATE_MASK) {
+ case DRM_MODE_ROTATE_0:
+ rot = ROT_0;
+ break;
+ case DRM_MODE_ROTATE_90:
+ rot = ROT_90;
+ break;
+ case DRM_MODE_ROTATE_180:
+ rot = ROT_180;
+ break;
+ case DRM_MODE_ROTATE_270:
+ rot = ROT_270;
+ break;
+ default:
+ rot = ROT_0;
+ break;
+ }
+
+ return rot;
+}
+
+static inline u8 to_vs_yuv_color_space(u32 color_space)
+{
+ u8 cs;
+
+ switch (color_space) {
+ case DRM_COLOR_YCBCR_BT601:
+ cs = COLOR_SPACE_601;
+ break;
+ case DRM_COLOR_YCBCR_BT709:
+ cs = COLOR_SPACE_709;
+ break;
+ case DRM_COLOR_YCBCR_BT2020:
+ cs = COLOR_SPACE_2020;
+ break;
+ default:
+ cs = COLOR_SPACE_601;
+ break;
+ }
+
+ return cs;
+}
+
+static inline u8 to_vs_tile_mode(u64 modifier)
+{
+ return (u8)(modifier & DRM_FORMAT_MOD_VS_NORM_MODE_MASK);
+}
+
+static inline u8 to_vs_display_id(struct vs_dc *dc, struct drm_crtc *crtc)
+{
+ u8 panel_num = dc->hw.info->panel_num;
+ u32 index = drm_crtc_index(crtc);
+ int i;
+
+ for (i = 0; i < panel_num; i++) {
+ if (index == dc->crtc[i]->base.index)
+ return i;
+ }
+
+ return 0;
+}
+
+static void dc_deinit(struct device *dev)
+{
+ struct vs_dc *dc = dev_get_drvdata(dev);
+
+ dc_hw_enable_interrupt(&dc->hw, 0);
+ dc_hw_deinit(&dc->hw);
+ //clk_disable_unprepare(dc->core_clk);
+ //clk_disable_unprepare(dc->pix_clk);
+ //clk_disable_unprepare(dc->axi_clk);
+}
+
+static int dc_init(struct device *dev)
+{
+ struct vs_dc *dc = dev_get_drvdata(dev);
+ int ret;
+
+ dc->first_frame = true;
+
+#if 0
+ ret = clk_prepare_enable(dc->core_clk);
+ if (ret < 0) {
+ dev_err(dev, "failed to prepare/enable core_clk\n");
+ return ret;
+ }
+
+ ret = clk_prepare_enable(dc->pix_clk);
+ if (ret < 0) {
+ dev_err(dev, "failed to prepare/enable pix_clk\n");
+ goto err_unprepare_core_clk;
+ }
+
+ ret = clk_prepare_enable(dc->axi_clk);
+ if (ret < 0) {
+ dev_err(dev, "failed to prepare/enable axi_clk\n");
+ goto err_unprepare_pix_clk;
+ }
+
+ dc->pix_clk_rate = clk_get_rate(dc->pix_clk);
+#endif
+
+ ret = dc_hw_init(&dc->hw);
+ if (ret) {
+ dev_err(dev, "failed to init DC HW\n");
+ goto err_unprepare_axi_clk;
+ }
+
+ return 0;
+
+err_unprepare_axi_clk:
+ //clk_disable_unprepare(dc->axi_clk);
+err_unprepare_core_clk:
+ //clk_disable_unprepare(dc->core_clk);
+err_unprepare_pix_clk:
+ //clk_disable_unprepare(dc->pix_clk);
+ return ret;
+}
+
+static void vs_dc_dump_enable(struct device *dev, dma_addr_t addr,
+ unsigned int pitch)
+{
+ struct vs_dc *dc = dev_get_drvdata(dev);
+
+ dc_hw_enable_dump(&dc->hw, addr, pitch);
+}
+
+static void vs_dc_dump_disable(struct device *dev)
+{
+ struct vs_dc *dc = dev_get_drvdata(dev);
+
+ dc_hw_disable_dump(&dc->hw);
+}
+
+static void vs_dc_enable(struct device *dev, struct drm_crtc *crtc)
+{
+ struct vs_dc *dc = dev_get_drvdata(dev);
+ struct vs_crtc_state *crtc_state = to_vs_crtc_state(crtc->state);
+ struct drm_display_mode *mode = &crtc->state->adjusted_mode;
+ struct dc_hw_display display;
+
+ display.bus_format = crtc_state->output_fmt;
+ display.h_active = mode->hdisplay;
+ display.h_total = mode->htotal;
+ display.h_sync_start = mode->hsync_start;
+ display.h_sync_end = mode->hsync_end;
+ if (mode->flags & DRM_MODE_FLAG_PHSYNC)
+ display.h_sync_polarity = true;
+ else
+ display.h_sync_polarity = false;
+
+ display.v_active = mode->vdisplay;
+ display.v_total = mode->vtotal;
+ display.v_sync_start = mode->vsync_start;
+ display.v_sync_end = mode->vsync_end;
+ if (mode->flags & DRM_MODE_FLAG_PVSYNC)
+ display.v_sync_polarity = true;
+ else
+ display.v_sync_polarity = false;
+
+ display.sync_mode = crtc_state->sync_mode;
+ display.bg_color = crtc_state->bg_color;
+
+ display.id = to_vs_display_id(dc, crtc);
+ display.sync_enable = crtc_state->sync_enable;
+ display.dither_enable = crtc_state->dither_enable;
+
+ display.enable = true;
+
+#if 1
+ ;//printk("====> %s, %d--pix_clk.\n", __func__, __LINE__);
+#else
+ if (dc->pix_clk_rate != mode->clock) {
+ clk_set_rate(dc->pix_clk, mode->clock * 1000);
+ dc->pix_clk_rate = mode->clock;
+ }
+#endif
+
+ if (crtc_state->encoder_type == DRM_MODE_ENCODER_DSI)
+ dc_hw_set_out(&dc->hw, OUT_DPI, display.id);
+ else
+ dc_hw_set_out(&dc->hw, OUT_DP, display.id);
+
+#ifdef CONFIG_VERISILICON_MMU
+ if (crtc_state->mmu_prefetch == VS_MMU_PREFETCH_ENABLE)
+ dc_hw_enable_mmu_prefetch(&dc->hw, true);
+ else
+ dc_hw_enable_mmu_prefetch(&dc->hw, false);
+#endif
+
+ dc_hw_setup_display(&dc->hw, &display);
+}
+
+static void vs_dc_disable(struct device *dev, struct drm_crtc *crtc)
+{
+ struct vs_dc *dc = dev_get_drvdata(dev);
+ struct dc_hw_display display;
+
+ display.id = to_vs_display_id(dc, crtc);
+ display.enable = false;
+
+ dc_hw_setup_display(&dc->hw, &display);
+}
+
+static bool vs_dc_mode_fixup(struct device *dev,
+ const struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ struct vs_dc *dc = dev_get_drvdata(dev);
+ long clk_rate;
+
+#if 1
+ ;//printk("====> %s, %d--pix_clk.\n", __func__, __LINE__);
+#else
+ if (dc->pix_clk) {
+ clk_rate = clk_round_rate(dc->pix_clk,
+ adjusted_mode->clock * 1000);
+ adjusted_mode->clock = DIV_ROUND_UP(clk_rate, 1000);
+ }
+#endif
+
+ return true;
+}
+
+static void vs_dc_set_gamma(struct device *dev, struct drm_crtc *crtc,
+ struct drm_color_lut *lut, unsigned int size)
+{
+ struct vs_dc *dc = dev_get_drvdata(dev);
+ u16 i, r, g, b;
+ u8 bits, id;
+
+ if (size != dc->hw.info->gamma_size) {
+ dev_err(dev, "gamma size does not match!\n");
+ return;
+ }
+
+ id = to_vs_display_id(dc, crtc);
+
+ bits = dc->hw.info->gamma_bits;
+ for (i = 0; i < size; i++) {
+ r = drm_color_lut_extract(lut[i].red, bits);
+ g = drm_color_lut_extract(lut[i].green, bits);
+ b = drm_color_lut_extract(lut[i].blue, bits);
+ dc_hw_update_gamma(&dc->hw, id, i, r, g, b);
+ }
+}
+
+static void vs_dc_enable_gamma(struct device *dev, struct drm_crtc *crtc,
+ bool enable)
+{
+ struct vs_dc *dc = dev_get_drvdata(dev);
+ u8 id;
+
+ id = to_vs_display_id(dc, crtc);
+ dc_hw_enable_gamma(&dc->hw, id, enable);
+}
+
+static void vs_dc_enable_vblank(struct device *dev, bool enable)
+{
+ struct vs_dc *dc = dev_get_drvdata(dev);
+
+ dc_hw_enable_interrupt(&dc->hw, enable);
+}
+
+static u32 calc_factor(u32 src, u32 dest)
+{
+ u32 factor = 1 << 16;
+
+ if ((src > 1) && (dest > 1))
+ factor = ((src - 1) << 16) / (dest - 1);
+
+ return factor;
+}
+
+static void update_scale(struct drm_plane_state *state, struct dc_hw_roi *roi,
+ struct dc_hw_scale *scale)
+{
+ int dst_w = drm_rect_width(&state->dst);
+ int dst_h = drm_rect_height(&state->dst);
+ int src_w, src_h, temp;
+ scale->enable = false;
+
+ if (roi->enable) {
+ src_w = roi->width;
+ src_h = roi->height;
+ } else {
+ src_w = drm_rect_width(&state->src) >> 16;
+ src_h = drm_rect_height(&state->src) >> 16;
+ }
+
+ if (drm_rotation_90_or_270(state->rotation)) {
+ temp = src_w;
+ src_w = src_h;
+ src_h = temp;
+ }
+
+ if (src_w != dst_w) {
+ scale->scale_factor_x = calc_factor(src_w, dst_w);
+ scale->enable = true;
+ } else {
+ scale->scale_factor_x = 1 << 16;
+ }
+ if (src_h != dst_h) {
+ scale->scale_factor_y = calc_factor(src_h, dst_h);
+ scale->enable = true;
+ } else {
+ scale->scale_factor_y = 1 << 16;
+ }
+}
+
+static void update_fb(struct vs_plane *plane, u8 display_id,
+ struct dc_hw_fb *fb, struct drm_plane_state *state)
+{
+ //struct drm_plane_state *state = plane->base.state;
+ struct vs_plane_state *plane_state = to_vs_plane_state(state);
+ struct drm_framebuffer *drm_fb = state->fb;
+ struct drm_rect *src = &state->src;
+
+ fb->display_id = display_id;
+ fb->y_address = plane->dma_addr[0];
+ fb->y_stride = drm_fb->pitches[0];
+ if (drm_fb->format->format == DRM_FORMAT_YVU420) {
+ fb->u_address = plane->dma_addr[2];
+ fb->v_address = plane->dma_addr[1];
+ fb->u_stride = drm_fb->pitches[2];
+ fb->v_stride = drm_fb->pitches[1];
+ } else {
+ fb->u_address = plane->dma_addr[1];
+ fb->v_address = plane->dma_addr[2];
+ fb->u_stride = drm_fb->pitches[1];
+ fb->v_stride = drm_fb->pitches[2];
+ }
+ fb->width = drm_rect_width(src) >> 16;
+ fb->height = drm_rect_height(src) >> 16;
+ fb->tile_mode = to_vs_tile_mode(drm_fb->modifier);
+ printk("update_fb tile_mode = %d\n",to_vs_tile_mode(drm_fb->modifier));
+ //fb->tile_mode = 0x04;
+ fb->rotation = to_vs_rotation(state->rotation);
+ fb->yuv_color_space = to_vs_yuv_color_space(state->color_encoding);
+ fb->zpos = state->zpos;
+ fb->enable = state->visible;
+ update_format(drm_fb->format->format, drm_fb->modifier, fb);
+ update_swizzle(drm_fb->format->format, fb);
+ update_watermark(plane_state->watermark, fb);
+
+ plane_state->status.tile_mode = fb->tile_mode;
+}
+
+#ifdef CONFIG_VERISILICON_DEC
+static u8 get_stream_base(u8 id)
+{
+ u8 stream_base = 0;
+
+ switch (id) {
+ case OVERLAY_PLANE_0:
+ stream_base = 3;
+ break;
+ case OVERLAY_PLANE_1:
+ stream_base = 6;
+ break;
+ case PRIMARY_PLANE_1:
+ stream_base = 16;
+ break;
+ case OVERLAY_PLANE_2:
+ stream_base = 19;
+ break;
+ case OVERLAY_PLANE_3:
+ stream_base = 22;
+ break;
+ default:
+ break;
+ }
+
+ return stream_base;
+}
+
+static void update_fbc(struct vs_dc *dc, struct vs_plane *plane, bool *enable,
+ struct drm_plane_state *state)
+{
+ struct dc_dec_fb dec_fb;
+ //struct drm_plane_state *state = plane->base.state;
+ struct drm_framebuffer *drm_fb = state->fb;
+ struct vs_dc_plane *dc_plane = &dc->planes[plane->id];
+ u8 i, stream_id;
+
+ if (!dc->hw.info->cap_dec) {
+ *enable = false;
+ return;
+ }
+
+ stream_id = get_stream_base(dc_plane->id);
+ memset(&dec_fb, 0, sizeof(struct dc_dec_fb));
+ dec_fb.fb = drm_fb;
+
+ if (fourcc_mod_vs_get_type(drm_fb->modifier) !=
+ DRM_FORMAT_MOD_VS_TYPE_COMPRESSED) {
+ *enable = false;
+ } else {
+ *enable = true;
+
+ for (i = 0; i < DEC_PLANE_MAX; i++) {
+ dec_fb.addr[i] = plane->dma_addr[i];
+ dec_fb.stride[i] = drm_fb->pitches[i];
+ }
+ }
+
+ dc_dec_config(&dc->dec400l, &dec_fb, stream_id);
+}
+
+static void disable_fbc(struct vs_dc *dc, struct vs_plane *plane)
+{
+ struct vs_dc_plane *dc_plane = &dc->planes[plane->id];
+ u8 stream_id;
+
+ if (!dc->hw.info->cap_dec)
+ return;
+
+ stream_id = get_stream_base(dc_plane->id);
+ dc_dec_config(&dc->dec400l, NULL, stream_id);
+}
+#endif
+
+static void update_degamma(struct vs_dc *dc, struct vs_plane *plane,
+ struct vs_plane_state *plane_state)
+{
+ dc_hw_update_degamma(&dc->hw, plane->id, plane_state->degamma);
+ plane_state->degamma_changed = false;
+}
+
+static void update_roi(struct vs_dc *dc, u8 id,
+ struct vs_plane_state *plane_state,
+ struct dc_hw_roi *roi,
+ struct drm_plane_state *state)
+{
+ struct drm_vs_roi *data;
+ //struct drm_rect *src = &plane_state->base.src;
+ struct drm_rect *src = &state->src;
+ u16 src_w = drm_rect_width(src) >> 16;
+ u16 src_h = drm_rect_height(src) >> 16;
+
+ if (plane_state->roi) {
+ data = plane_state->roi->data;
+
+ if (data->enable) {
+ roi->x = data->roi_x;
+ roi->y = data->roi_y;
+ roi->width = (data->roi_x + data->roi_w > src_w) ?
+ (src_w - data->roi_x) : data->roi_w;
+ roi->height = (data->roi_y + data->roi_h > src_h) ?
+ (src_h - data->roi_y) : data->roi_h;
+ roi->enable = true;
+ } else {
+ roi->enable = false;
+ }
+
+ dc_hw_update_roi(&dc->hw, id, roi);
+ } else {
+ roi->enable = false;
+ }
+}
+
+static void update_color_mgmt(struct vs_dc *dc, u8 id,
+ struct dc_hw_fb *fb,
+ struct vs_plane_state *plane_state)
+{
+ struct drm_vs_color_mgmt *data;
+ struct dc_hw_colorkey colorkey;
+
+ if (plane_state->color_mgmt) {
+ data = plane_state->color_mgmt->data;
+
+ fb->clear_enable = data->clear_enable;
+ fb->clear_value = data->clear_value;
+
+ if (data->colorkey > data->colorkey_high)
+ data->colorkey = data->colorkey_high;
+
+ colorkey.colorkey = data->colorkey;
+ colorkey.colorkey_high = data->colorkey_high;
+ colorkey.transparency = (data->transparency) ?
+ DC_TRANSPARENCY_KEY : DC_TRANSPARENCY_OPAQUE;
+ dc_hw_update_colorkey(&dc->hw, id, &colorkey);
+ }
+}
+
+static void update_plane(struct vs_dc *dc, struct vs_plane *plane, struct drm_plane *drm_plane,
+ struct drm_atomic_state *drm_state)
+{
+ struct dc_hw_fb fb = {0};
+ struct dc_hw_scale scale;
+ struct dc_hw_position pos;
+ struct dc_hw_blend blend;
+ struct dc_hw_roi roi;
+ //struct drm_plane_state *state = plane->base.state;
+ struct drm_plane_state *state = drm_atomic_get_new_plane_state(drm_state,
+ drm_plane);
+ struct vs_plane_state *plane_state = to_vs_plane_state(state);
+ struct drm_rect *dest = &state->dst;
+ bool dec_enable = false;
+ u8 display_id = 0;
+
+#ifdef CONFIG_VERISILICON_DEC
+ update_fbc(dc, plane, &dec_enable, state);
+#endif
+
+ display_id = to_vs_display_id(dc, state->crtc);
+ update_fb(plane, display_id, &fb, state);
+ fb.dec_enable = dec_enable;
+
+
+ update_roi(dc, plane->id, plane_state, &roi, state);
+
+ update_scale(state, &roi, &scale);
+
+ if (plane_state->degamma_changed)
+ update_degamma(dc, plane, plane_state);
+
+ pos.start_x = dest->x1;
+ pos.start_y = dest->y1;
+ pos.end_x = dest->x2;
+ pos.end_y = dest->y2;
+
+ blend.alpha = (u8)(state->alpha >> 8);
+ blend.blend_mode = (u8)(state->pixel_blend_mode);
+
+ update_color_mgmt(dc, plane->id, &fb, plane_state);
+
+ dc_hw_update_plane(&dc->hw, plane->id, &fb, &scale, &pos, &blend);
+}
+
+static void update_qos(struct vs_dc *dc, struct vs_plane *plane, struct drm_plane *drm_plane,
+ struct drm_atomic_state *drm_state)
+{
+ //struct drm_plane_state *state = plane->base.state;
+ struct drm_plane_state *state = drm_atomic_get_new_plane_state(drm_state,
+ drm_plane);
+ struct vs_plane_state *plane_state = to_vs_plane_state(state);
+ struct drm_vs_watermark *data;
+ struct dc_hw_qos qos;
+
+ if (plane_state->watermark){
+ data = plane_state->watermark->data;
+
+ if (data->qos_high) {
+ if (data->qos_low > data->qos_high)
+ data->qos_low = data->qos_high;
+
+ qos.low_value = data->qos_low & 0x0F;
+ qos.high_value = data->qos_high & 0x0F;
+ dc_hw_update_qos(&dc->hw, &qos);
+ }
+ }
+}
+
+static void update_cursor_size(struct drm_plane_state *state, struct dc_hw_cursor *cursor)
+{
+ u8 size_type;
+
+ switch (state->crtc_w) {
+ case 32:
+ size_type = CURSOR_SIZE_32X32;
+ break;
+ case 64:
+ size_type = CURSOR_SIZE_64X64;
+ break;
+ default:
+ size_type = CURSOR_SIZE_32X32;
+ break;
+ }
+
+ cursor->size = size_type;
+}
+
+static void update_cursor_plane(struct vs_dc *dc, struct vs_plane *plane, struct drm_plane *drm_plane,
+ struct drm_atomic_state *drm_state)
+{
+ //struct drm_plane_state *state = plane->base.state;
+ struct drm_plane_state *state = drm_atomic_get_new_plane_state(drm_state,
+ drm_plane);
+ struct drm_framebuffer *drm_fb = state->fb;
+ struct dc_hw_cursor cursor;
+
+ cursor.address = plane->dma_addr[0];
+ cursor.x = state->crtc_x;
+ cursor.y = state->crtc_y;
+ cursor.hot_x = drm_fb->hot_x;
+ cursor.hot_y = drm_fb->hot_y;
+ cursor.display_id = to_vs_display_id(dc, state->crtc);
+ update_cursor_size(state, &cursor);
+ cursor.enable = true;
+
+ dc_hw_update_cursor(&dc->hw, cursor.display_id, &cursor);
+}
+
+static void vs_dc_update_plane(struct device *dev, struct vs_plane *plane, struct drm_plane *drm_plane,
+ struct drm_atomic_state *drm_state)
+{
+ struct vs_dc *dc = dev_get_drvdata(dev);
+ enum drm_plane_type type = plane->base.type;
+
+ switch (type) {
+ case DRM_PLANE_TYPE_PRIMARY:
+ case DRM_PLANE_TYPE_OVERLAY:
+ update_plane(dc, plane, drm_plane, drm_state);
+ update_qos(dc, plane, drm_plane, drm_state);
+ break;
+ case DRM_PLANE_TYPE_CURSOR:
+ update_cursor_plane(dc, plane, drm_plane, drm_state);
+ break;
+ default:
+ break;
+ }
+}
+
+static void vs_dc_disable_plane(struct device *dev, struct vs_plane *plane,
+ struct drm_plane_state *old_state)
+{
+ struct vs_dc *dc = dev_get_drvdata(dev);
+ enum drm_plane_type type = plane->base.type;
+ struct dc_hw_fb fb = {0};
+ struct dc_hw_cursor cursor = {0};
+
+ switch (type) {
+ case DRM_PLANE_TYPE_PRIMARY:
+ case DRM_PLANE_TYPE_OVERLAY:
+ fb.enable = false;
+ dc_hw_update_plane(&dc->hw, plane->id, &fb, NULL, NULL, NULL);
+#ifdef CONFIG_VERISILICON_DEC
+ disable_fbc(dc, plane);
+#endif
+ break;
+ case DRM_PLANE_TYPE_CURSOR:
+ cursor.enable = false;
+ cursor.display_id = to_vs_display_id(dc, old_state->crtc);
+ dc_hw_update_cursor(&dc->hw, cursor.display_id, &cursor);
+ break;
+ default:
+ break;
+ }
+}
+
+static bool vs_dc_mod_supported(const struct vs_plane_info *plane_info,
+ u64 modifier)
+{
+ const u64 *mods;
+
+ if (plane_info->modifiers == NULL)
+ return false;
+
+ for(mods = plane_info->modifiers; *mods != DRM_FORMAT_MOD_INVALID; mods++) {
+ if (*mods == modifier)
+ return true;
+ }
+
+ return false;
+}
+
+static int vs_dc_check_plane(struct device *dev, 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 vs_dc *dc = dev_get_drvdata(dev);
+ struct drm_framebuffer *fb = new_plane_state->fb;
+ const struct vs_plane_info *plane_info;
+ struct drm_crtc *crtc = new_plane_state->crtc;
+ struct drm_crtc_state *crtc_state;
+ struct vs_plane *vs_plane = to_vs_plane(plane);
+
+ plane_info = &dc->hw.info->planes[vs_plane->id];
+ if (plane_info == NULL)
+ return -EINVAL;
+
+ if (fb->width < plane_info->min_width ||
+ fb->width > plane_info->max_width ||
+ fb->height < plane_info->min_height ||
+ fb->height > plane_info->max_height)
+ dev_err_once(dev, "buffer size may not support on plane%d.\n",
+ vs_plane->id);
+
+ if ((vs_plane->base.type != DRM_PLANE_TYPE_CURSOR) &&
+ (!vs_dc_mod_supported(plane_info, fb->modifier))) {
+ dev_err(dev, "unsupported modifier on plane%d.\n", vs_plane->id);
+ return -EINVAL;
+ }
+
+ crtc_state = drm_atomic_get_existing_crtc_state(state, crtc);
+ if (IS_ERR(crtc_state))
+ return -EINVAL;
+
+ return drm_atomic_helper_check_plane_state(new_plane_state, crtc_state,
+ plane_info->min_scale,
+ plane_info->max_scale,
+ true, true);
+}
+
+static irqreturn_t dc_isr(int irq, void *data)
+{
+ struct vs_dc *dc = data;
+ struct vs_dc_info *dc_info = dc->hw.info;
+ u32 i,ret;
+
+ ret = dc_hw_get_interrupt(&dc->hw);
+
+ for (i = 0; i < dc_info->panel_num; i ++) {
+ vs_crtc_handle_vblank(&dc->crtc[i]->base,dc_hw_check_underflow(&dc->hw));
+ }
+ starfive_flush_dcache(dc->hw.plane[0].fb.y_address, dc->hw.plane[0].fb.width*dc->hw.plane[0].fb.height*2);
+ starfive_flush_dcache(dc->hw.plane[1].fb.y_address, dc->hw.plane[1].fb.width*dc->hw.plane[1].fb.height*2);
+ starfive_flush_dcache(dc->hw.plane[2].fb.y_address, dc->hw.plane[2].fb.width*dc->hw.plane[2].fb.height*2);
+
+ return IRQ_HANDLED;
+}
+
+static void vs_dc_commit(struct device *dev)
+{
+ struct vs_dc *dc = dev_get_drvdata(dev);
+
+#ifdef CONFIG_VERISILICON_DEC
+ if (dc->hw.info->cap_dec)
+ dc_dec_commit(&dc->dec400l, &dc->hw);
+#endif
+
+ dc_hw_enable_shadow_register(&dc->hw, false);
+
+ dc_hw_commit(&dc->hw);
+
+ if (dc->first_frame)
+ dc->first_frame = false;
+
+ dc_hw_enable_shadow_register(&dc->hw, true);
+}
+
+static const struct vs_crtc_funcs dc_crtc_funcs = {
+ .enable = vs_dc_enable,
+ .disable = vs_dc_disable,
+ .mode_fixup = vs_dc_mode_fixup,
+ .set_gamma = vs_dc_set_gamma,
+ .enable_gamma = vs_dc_enable_gamma,
+ .enable_vblank = vs_dc_enable_vblank,
+ .commit = vs_dc_commit,
+};
+
+static const struct vs_plane_funcs dc_plane_funcs = {
+ .update = vs_dc_update_plane,
+ .disable = vs_dc_disable_plane,
+ .check = vs_dc_check_plane,
+};
+
+static const struct vs_dc_funcs dc_funcs = {
+ .dump_enable = vs_dc_dump_enable,
+ .dump_disable = vs_dc_dump_disable,
+};
+
+static int dc_bind(struct device *dev, struct device *master, void *data)
+{
+ struct drm_device *drm_dev = data;
+#ifdef CONFIG_VERISILICON_MMU
+ struct vs_drm_private *priv = drm_dev->dev_private;
+#endif
+ struct vs_dc *dc = dev_get_drvdata(dev);
+ struct device_node *port;
+ struct vs_crtc *crtc;
+ struct drm_crtc *drm_crtc;
+ struct vs_dc_info *dc_info;
+ struct vs_plane *plane;
+ struct drm_plane *drm_plane, *tmp;
+ struct vs_plane_info *plane_info;
+ int i, ret;
+ u32 ctrc_mask = 0;
+
+ if (!drm_dev || !dc) {
+ dev_err(dev, "devices are not created.\n");
+ return -ENODEV;
+ }
+
+ ret = dc_init(dev);
+ if (ret < 0) {
+ dev_err(dev, "Failed to initialize DC hardware.\n");
+ return ret;
+ }
+
+#ifdef CONFIG_VERISILICON_MMU
+ ret = dc_mmu_construct(priv->dma_dev, &priv->mmu);
+ if (ret) {
+ dev_err(dev, "failed to construct DC MMU\n");
+ goto err_clean_dc;
+ }
+
+ ret = dc_hw_mmu_init(&dc->hw, priv->mmu);
+ if (ret) {
+ dev_err(dev, "failed to init DC MMU\n");
+ goto err_clean_dc;
+ }
+#endif
+
+ ret = vs_drm_iommu_attach_device(drm_dev, dev);
+ if (ret < 0) {
+ dev_err(dev, "Failed to attached iommu device.\n");
+ goto err_clean_dc;
+ }
+
+ port = of_get_child_by_name(dev->of_node, "port");
+ if (!port) {
+ dev_err(dev, "no port node found\n");
+ goto err_detach_dev;
+ }
+ of_node_put(port);
+
+ dc_info = dc->hw.info;
+
+ for(i = 0; i < dc_info->panel_num; i++) {
+ crtc = vs_crtc_create(drm_dev, dc_info);
+ if (!crtc) {
+ dev_err(dev, "Failed to create CRTC.\n");
+ ret = -ENOMEM;
+ goto err_detach_dev;
+ }
+
+ crtc->base.port = port;
+ crtc->dev = dev;
+ crtc->funcs = &dc_crtc_funcs;
+ dc->crtc[i] = crtc;
+ ctrc_mask |= drm_crtc_mask(&crtc->base);
+ }
+
+ for (i = 0; i < dc_info->plane_num; i++) {
+ plane_info = (struct vs_plane_info *)&dc_info->planes[i];
+
+ if (!strcmp(plane_info->name, "Primary") ||!strcmp(plane_info->name, "Cursor"))
+ {
+ plane = vs_plane_create(drm_dev, plane_info, dc_info->layer_num,
+ drm_crtc_mask(&dc->crtc[0]->base));
+ }else if(!strcmp(plane_info->name, "Primary_1") ||
+ !strcmp(plane_info->name, "Cursor_1"))
+ {
+ plane = vs_plane_create(drm_dev, plane_info, dc_info->layer_num,
+ drm_crtc_mask(&dc->crtc[1]->base));
+ }
+ else{
+ plane = vs_plane_create(drm_dev, plane_info,
+ dc_info->layer_num, ctrc_mask);
+ }
+
+ if (!plane)
+ goto err_cleanup_planes;
+
+ plane->id = i;
+ dc->planes[i].id = plane_info->id;
+
+ plane->funcs = &dc_plane_funcs;
+
+ if (plane_info->type == DRM_PLANE_TYPE_PRIMARY) {
+ if (!strcmp(plane_info->name, "Primary"))
+ dc->crtc[0]->base.primary = &plane->base;
+ else
+ dc->crtc[1]->base.primary = &plane->base;
+ drm_dev->mode_config.min_width = plane_info->min_width;
+ drm_dev->mode_config.min_height =
+ plane_info->min_height;
+ drm_dev->mode_config.max_width = plane_info->max_width;
+ drm_dev->mode_config.max_height =
+ plane_info->max_height;
+ }
+
+ if (plane_info->type == DRM_PLANE_TYPE_CURSOR) {
+ if (!strcmp(plane_info->name, "Cursor"))
+ dc->crtc[0]->base.cursor = &plane->base;
+ else
+ dc->crtc[1]->base.cursor = &plane->base;
+ drm_dev->mode_config.cursor_width =
+ plane_info->max_width;
+ drm_dev->mode_config.cursor_height =
+ plane_info->max_height;
+ }
+ }
+
+ dc->funcs = &dc_funcs;
+
+ vs_drm_update_pitch_alignment(drm_dev, dc_info->pitch_alignment);
+
+ return 0;
+
+err_cleanup_planes:
+ list_for_each_entry_safe(drm_plane, tmp,
+ &drm_dev->mode_config.plane_list, head)
+ if (drm_plane->possible_crtcs & ctrc_mask)
+ vs_plane_destory(drm_plane);
+
+ drm_for_each_crtc(drm_crtc, drm_dev)
+ vs_crtc_destroy(drm_crtc);
+err_detach_dev:
+ vs_drm_iommu_detach_device(drm_dev, dev);
+err_clean_dc:
+ dc_deinit(dev);
+ return ret;
+}
+
+static void dc_unbind(struct device *dev, struct device *master, void *data)
+{
+ struct drm_device *drm_dev = data;
+
+ dc_deinit(dev);
+
+ vs_drm_iommu_detach_device(drm_dev, dev);
+}
+
+const struct component_ops dc_component_ops = {
+ .bind = dc_bind,
+ .unbind = dc_unbind,
+};
+
+static const struct of_device_id dc_driver_dt_match[] = {
+ { .compatible = "verisilicon,dc8200", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, dc_driver_dt_match);
+
+static int dc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct vs_dc *dc;
+ int irq, ret;
+
+ dc = devm_kzalloc(dev, sizeof(*dc), GFP_KERNEL);
+ if (!dc)
+ return -ENOMEM;
+
+
+ dc->hw.hi_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(dc->hw.hi_base))
+ return PTR_ERR(dc->hw.hi_base);
+
+
+ dc->hw.reg_base = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(dc->hw.reg_base))
+ return PTR_ERR(dc->hw.reg_base);
+
+#ifdef CONFIG_VERISILICON_MMU
+ dc->hw.mmu_base = devm_platform_ioremap_resource(pdev, 2);
+ if (IS_ERR(dc->hw.mmu_base))
+ return PTR_ERR(dc->hw.mmu_base);
+#endif
+
+ irq = platform_get_irq(pdev, 0);
+ ret = devm_request_irq(dev, irq, dc_isr, 0, dev_name(dev), dc);
+ if (ret < 0) {
+ dev_err(dev, "Failed to install irq:%u.\n", irq);
+ return ret;
+ }
+
+#if 0
+ dc->core_clk = devm_clk_get_optional(dev, "core_clk");
+ if (IS_ERR(dc->core_clk)) {
+ dev_err(dev, "failed to get core_clk source\n");
+ return PTR_ERR(dc->core_clk);
+ }
+
+ dc->pix_clk = devm_clk_get_optional(dev, "pix_clk");
+ if (IS_ERR(dc->core_clk)) {
+ dev_err(dev, "failed to get pix_clk source\n");
+ return PTR_ERR(dc->core_clk);
+ }
+
+ dc->axi_clk = devm_clk_get_optional(dev, "axi_clk");
+ if (IS_ERR(dc->core_clk)) {
+ dev_err(dev, "failed to get axi_clk source\n");
+ return PTR_ERR(dc->core_clk);
+ }
+#endif
+
+ dev_set_drvdata(dev, dc);
+
+ return component_add(dev, &dc_component_ops);
+}
+
+static int dc_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+
+ component_del(dev, &dc_component_ops);
+
+ dev_set_drvdata(dev, NULL);
+
+ return 0;
+}
+
+struct platform_driver dc_platform_driver = {
+ .probe = dc_probe,
+ .remove = dc_remove,
+ .driver = {
+ .name = "vs-dc",
+ .of_match_table = of_match_ptr(dc_driver_dt_match),
+ },
+};
+
+MODULE_DESCRIPTION("VeriSilicon DC Driver");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2020 VeriSilicon Holdings Co., Ltd.
+ */
+
+#ifndef __VS_DC_H__
+#define __VS_DC_H__
+
+#include <linux/version.h>
+#include <linux/mm_types.h>
+
+#include <drm/drm_modes.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 5, 0)
+#include <drm/drmP.h>
+#endif
+
+#include "vs_plane.h"
+#include "vs_crtc.h"
+#include "vs_dc_hw.h"
+#include "vs_dc_dec.h"
+#ifdef CONFIG_VERISILICON_MMU
+#include "vs_dc_mmu.h"
+#endif
+
+struct vs_dc_funcs {
+ void (*dump_enable)(struct device *dev, dma_addr_t addr,
+ unsigned int pitch);
+ void (*dump_disable)(struct device *dev);
+};
+
+struct vs_dc_plane {
+ enum dc_hw_plane_id id;
+};
+
+struct vs_dc {
+ struct vs_crtc *crtc[DC_DISPLAY_NUM];
+ struct dc_hw hw;
+#ifdef CONFIG_VERISILICON_DEC
+ struct dc_dec400l dec400l;
+#endif
+
+ struct clk *core_clk;
+ struct clk *pix_clk;
+ struct clk *axi_clk;
+ unsigned int pix_clk_rate; /* in KHz */
+
+ bool first_frame;
+
+ struct vs_dc_plane planes[PLANE_NUM];
+
+ const struct vs_dc_funcs *funcs;
+};
+
+extern struct platform_driver dc_platform_driver;
+#endif /* __VS_DC_H__ */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020 VeriSilicon Holdings Co., Ltd.
+ */
+
+#include <linux/types.h>
+#include <linux/errno.h>
+
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+
+#include "vs_dc_dec.h"
+
+#define fourcc_mod_vs_get_tile_mode(val) (u8)((val) & \
+ DRM_FORMAT_MOD_VS_DEC_TILE_MODE_MASK)
+
+static inline bool _is_stream_changed(struct dc_dec400l *dec400l, u8 stream_id)
+{
+ return dec400l->stream[stream_id].dirty;
+}
+
+static inline bool _is_stream_valid(struct dc_dec400l *dec400l, u8 stream_id)
+{
+ return !!(dec400l->stream[stream_id].main_base_addr);
+}
+
+static void _enable_stream(struct dc_dec400l *dec400l, struct dc_hw *hw,
+ u8 stream_id)
+{
+ if (!(dec400l->stream_status & (1 << stream_id))) {
+ if (!(dec400l->stream_status))
+ /* the first enabled steram */
+ dc_hw_dec_init(hw);
+
+ dec400l->stream_status |= 1 << stream_id;
+ }
+}
+static void _disable_stream(struct dc_dec400l *dec400l, struct dc_hw *hw,
+ u8 stream_id)
+{
+ if ((dec400l->stream_status & (1 << stream_id)))
+ dec400l->stream_status &= ~(1 << stream_id);
+}
+
+static u16 get_dec_tile_size(u8 tile_mode, u8 cpp)
+{
+ u16 multi = 0;
+
+ switch (tile_mode) {
+ case DRM_FORMAT_MOD_VS_DEC_RASTER_16X1:
+ multi = 16;
+ break;
+ case DRM_FORMAT_MOD_VS_DEC_TILE_8X4:
+ case DRM_FORMAT_MOD_VS_DEC_TILE_4X8:
+ case DRM_FORMAT_MOD_VS_DEC_RASTER_32X1:
+ case DRM_FORMAT_MOD_VS_DEC_TILE_8X4_S:
+ multi = 32;
+ break;
+ case DRM_FORMAT_MOD_VS_DEC_TILE_8X8_XMAJOR:
+ case DRM_FORMAT_MOD_VS_DEC_TILE_8X8_YMAJOR:
+ case DRM_FORMAT_MOD_VS_DEC_TILE_16X4:
+ case DRM_FORMAT_MOD_VS_DEC_RASTER_16X4:
+ case DRM_FORMAT_MOD_VS_DEC_RASTER_64X1:
+ case DRM_FORMAT_MOD_VS_DEC_RASTER_32X2:
+ case DRM_FORMAT_MOD_VS_DEC_TILE_16X4_S:
+ case DRM_FORMAT_MOD_VS_DEC_TILE_16X4_LSB:
+ multi = 64;
+ break;
+ case DRM_FORMAT_MOD_VS_DEC_TILE_32X4:
+ case DRM_FORMAT_MOD_VS_DEC_RASTER_128X1:
+ case DRM_FORMAT_MOD_VS_DEC_TILE_16X8:
+ case DRM_FORMAT_MOD_VS_DEC_TILE_8X16:
+ case DRM_FORMAT_MOD_VS_DEC_RASTER_32X4:
+ case DRM_FORMAT_MOD_VS_DEC_RASTER_64X2:
+ case DRM_FORMAT_MOD_VS_DEC_TILE_32X4_S:
+ case DRM_FORMAT_MOD_VS_DEC_TILE_32X4_LSB:
+ multi = 128;
+ break;
+ case DRM_FORMAT_MOD_VS_DEC_TILE_64X4:
+ case DRM_FORMAT_MOD_VS_DEC_RASTER_256X1:
+ case DRM_FORMAT_MOD_VS_DEC_RASTER_64X4:
+ case DRM_FORMAT_MOD_VS_DEC_RASTER_128X2:
+ case DRM_FORMAT_MOD_VS_DEC_TILE_16X16:
+ case DRM_FORMAT_MOD_VS_DEC_TILE_32X8:
+ multi = 256;
+ break;
+ case DRM_FORMAT_MOD_VS_DEC_RASTER_256X2:
+ case DRM_FORMAT_MOD_VS_DEC_RASTER_128X4:
+ case DRM_FORMAT_MOD_VS_DEC_RASTER_512X1:
+ case DRM_FORMAT_MOD_VS_DEC_TILE_128X4:
+ case DRM_FORMAT_MOD_VS_DEC_TILE_32X16:
+ multi = 512;
+ break;
+ case DRM_FORMAT_MOD_VS_DEC_TILE_256X4:
+ case DRM_FORMAT_MOD_VS_DEC_TILE_64X16:
+ case DRM_FORMAT_MOD_VS_DEC_TILE_128X8:
+ multi = 1024;
+ break;
+ case DRM_FORMAT_MOD_VS_DEC_TILE_512X4:
+ multi = 2048;
+ break;
+ default:
+ break;
+ }
+
+ return multi * cpp;
+}
+
+static void update_fb_format(struct dc_dec_fb *dec_fb)
+{
+ struct drm_framebuffer *drm_fb = dec_fb->fb;
+ u8 tile_mod = fourcc_mod_vs_get_tile_mode(drm_fb->modifier);
+ u8 norm_mod = DRM_FORMAT_MOD_VS_LINEAR;
+
+ switch (tile_mod) {
+ case DRM_FORMAT_MOD_VS_DEC_RASTER_32X1:
+ norm_mod = DRM_FORMAT_MOD_VS_TILE_32X1;
+ break;
+ case DRM_FORMAT_MOD_VS_DEC_RASTER_64X1:
+ norm_mod = DRM_FORMAT_MOD_VS_TILE_64X1;
+ break;
+ case DRM_FORMAT_MOD_VS_DEC_RASTER_128X1:
+ norm_mod = DRM_FORMAT_MOD_VS_TILE_128X1;
+ break;
+ case DRM_FORMAT_MOD_VS_DEC_RASTER_256X1:
+ norm_mod = DRM_FORMAT_MOD_VS_TILE_256X1;
+ break;
+ case DRM_FORMAT_MOD_VS_DEC_TILE_8X4:
+ norm_mod = DRM_FORMAT_MOD_VS_SUPER_TILED_XMAJOR_8X4;
+ break;
+ case DRM_FORMAT_MOD_VS_DEC_TILE_4X8:
+ norm_mod = DRM_FORMAT_MOD_VS_SUPER_TILED_YMAJOR_4X8;
+ break;
+ case DRM_FORMAT_MOD_VS_DEC_TILE_8X8_XMAJOR:
+ if (drm_fb->format->format == DRM_FORMAT_YUYV ||
+ drm_fb->format->format == DRM_FORMAT_UYVY ||
+ drm_fb->format->format == DRM_FORMAT_P010)
+ norm_mod = DRM_FORMAT_MOD_VS_TILE_8X8;
+ else
+ norm_mod = DRM_FORMAT_MOD_VS_SUPER_TILED_XMAJOR;
+ break;
+ case DRM_FORMAT_MOD_VS_DEC_TILE_16X8:
+ if (drm_fb->format->format == DRM_FORMAT_NV12 ||
+ drm_fb->format->format == DRM_FORMAT_P010)
+ norm_mod = DRM_FORMAT_MOD_VS_TILE_8X8;
+ break;
+ case DRM_FORMAT_MOD_VS_DEC_TILE_32X8:
+ if (drm_fb->format->format == DRM_FORMAT_NV12)
+ norm_mod = DRM_FORMAT_MOD_VS_TILE_8X8;
+ break;
+ default:
+ break;
+ }
+
+ drm_fb->modifier = fourcc_mod_vs_norm_code(norm_mod);
+}
+
+static void _stream_config(struct dc_dec_fb *dec_fb,
+ struct dc_dec_stream *stream, u8 index)
+{
+ struct drm_framebuffer *drm_fb = dec_fb->fb;
+ const struct drm_format_info *info = drm_fb->format;
+ u32 plane_height = drm_format_info_plane_height(info,
+ drm_fb->height, index);
+ u16 tile_size;
+
+ stream->main_base_addr = dec_fb->addr[index];
+ stream->tile_mode = fourcc_mod_vs_get_tile_mode(drm_fb->modifier);
+ if (drm_fb->modifier & DRM_FORMAT_MOD_VS_DEC_ALIGN_32)
+ stream->align_mode = DEC_ALIGN_32;
+ else
+ stream->align_mode = DEC_ALIGN_64;
+
+ switch (drm_fb->format->format) {
+ case DRM_FORMAT_ARGB8888:
+ stream->format = DEC_FORMAT_ARGB8;
+ stream->depth = DEC_DEPTH_8;
+ break;
+ case DRM_FORMAT_XRGB8888:
+ stream->format = DEC_FORMAT_XRGB8;
+ stream->depth = DEC_DEPTH_8;
+ break;
+ case DRM_FORMAT_RGB565:
+ stream->format = DEC_FORMAT_R5G6B5;
+ stream->depth = DEC_DEPTH_8;
+ break;
+ case DRM_FORMAT_ARGB1555:
+ stream->format = DEC_FORMAT_A1RGB5;
+ stream->depth = DEC_DEPTH_8;
+ break;
+ case DRM_FORMAT_XRGB1555:
+ stream->format = DEC_FORMAT_X1RGB5;
+ stream->depth = DEC_DEPTH_8;
+ break;
+ case DRM_FORMAT_ARGB4444:
+ stream->format = DEC_FORMAT_ARGB4;
+ stream->depth = DEC_DEPTH_8;
+ break;
+ case DRM_FORMAT_XRGB4444:
+ stream->format = DEC_FORMAT_XRGB4;
+ stream->depth = DEC_DEPTH_8;
+ break;
+ case DRM_FORMAT_ARGB2101010:
+ stream->format = DEC_FORMAT_A2R10G10B10;
+ stream->depth = DEC_DEPTH_10;
+ break;
+ case DRM_FORMAT_YUYV:
+ stream->format = DEC_FORMAT_YUY2;
+ stream->depth = DEC_DEPTH_8;
+ break;
+ case DRM_FORMAT_UYVY:
+ stream->format = DEC_FORMAT_UYVY;
+ stream->depth = DEC_DEPTH_8;
+ break;
+ case DRM_FORMAT_YVU420:
+ stream->format = DEC_FORMAT_YUV_ONLY;
+ stream->depth = DEC_DEPTH_8;
+ break;
+ case DRM_FORMAT_NV12:
+ WARN_ON(stream->tile_mode != DRM_FORMAT_MOD_VS_DEC_RASTER_256X1 &&
+ stream->tile_mode != DRM_FORMAT_MOD_VS_DEC_RASTER_128X1 &&
+ stream->tile_mode != DRM_FORMAT_MOD_VS_DEC_TILE_32X8 &&
+ stream->tile_mode != DRM_FORMAT_MOD_VS_DEC_TILE_16X8);
+ if (index) {
+ stream->format = DEC_FORMAT_UV_MIX;
+ switch (stream->tile_mode) {
+ case DRM_FORMAT_MOD_VS_DEC_RASTER_256X1:
+ stream->tile_mode =
+ DRM_FORMAT_MOD_VS_DEC_RASTER_128X1;
+ break;
+ case DRM_FORMAT_MOD_VS_DEC_RASTER_128X1:
+ stream->tile_mode =
+ DRM_FORMAT_MOD_VS_DEC_RASTER_64X1;
+ break;
+ case DRM_FORMAT_MOD_VS_DEC_TILE_32X8:
+ stream->tile_mode =
+ DRM_FORMAT_MOD_VS_DEC_TILE_32X4;
+ break;
+ case DRM_FORMAT_MOD_VS_DEC_TILE_16X8:
+ stream->tile_mode =
+ DRM_FORMAT_MOD_VS_DEC_TILE_16X4;
+ break;
+ default:
+ break;
+ }
+ } else {
+ stream->format = DEC_FORMAT_YUV_ONLY;
+ }
+
+ stream->depth = DEC_DEPTH_8;
+ break;
+ case DRM_FORMAT_P010:
+ WARN_ON(stream->tile_mode != DRM_FORMAT_MOD_VS_DEC_RASTER_128X1 &&
+ stream->tile_mode != DRM_FORMAT_MOD_VS_DEC_RASTER_64X1 &&
+ stream->tile_mode != DRM_FORMAT_MOD_VS_DEC_TILE_16X8 &&
+ stream->tile_mode != DRM_FORMAT_MOD_VS_DEC_TILE_8X8_XMAJOR);
+ if (index) {
+ stream->format = DEC_FORMAT_UV_MIX;
+ switch (stream->tile_mode) {
+ case DRM_FORMAT_MOD_VS_DEC_RASTER_128X1:
+ stream->tile_mode =
+ DRM_FORMAT_MOD_VS_DEC_RASTER_64X1;
+ break;
+ case DRM_FORMAT_MOD_VS_DEC_RASTER_64X1:
+ stream->tile_mode =
+ DRM_FORMAT_MOD_VS_DEC_RASTER_32X1;
+ break;
+ case DRM_FORMAT_MOD_VS_DEC_TILE_16X8:
+ stream->tile_mode =
+ DRM_FORMAT_MOD_VS_DEC_TILE_16X4;
+ break;
+ case DRM_FORMAT_MOD_VS_DEC_TILE_8X8_XMAJOR:
+ stream->tile_mode =
+ DRM_FORMAT_MOD_VS_DEC_TILE_8X4;
+ break;
+ default:
+ break;
+ }
+ } else {
+ stream->format = DEC_FORMAT_YUV_ONLY;
+ }
+
+ stream->depth = DEC_DEPTH_10;
+ break;
+ case DRM_FORMAT_NV16:
+ WARN_ON(stream->tile_mode != DRM_FORMAT_MOD_VS_DEC_RASTER_256X1 &&
+ stream->tile_mode != DRM_FORMAT_MOD_VS_DEC_RASTER_128X1);
+ if (index) {
+ stream->format = DEC_FORMAT_UV_MIX;
+ switch (stream->tile_mode) {
+ case DRM_FORMAT_MOD_VS_DEC_RASTER_256X1:
+ stream->tile_mode =
+ DRM_FORMAT_MOD_VS_DEC_RASTER_128X1;
+ break;
+ case DRM_FORMAT_MOD_VS_DEC_RASTER_128X1:
+ stream->tile_mode =
+ DRM_FORMAT_MOD_VS_DEC_RASTER_64X1;
+ break;
+ default:
+ break;
+ }
+ } else {
+ stream->format = DEC_FORMAT_YUV_ONLY;
+ }
+
+ stream->depth = DEC_DEPTH_8;
+ break;
+ }
+
+ tile_size = get_dec_tile_size(stream->tile_mode, info->cpp[index]);
+ stream->aligned_stride = ALIGN(dec_fb->stride[index], tile_size);
+ stream->ts_base_addr = stream->main_base_addr +
+ stream->aligned_stride * plane_height;
+}
+
+static int _dec400l_config(struct dc_dec400l *dec400l,
+ struct dc_dec_fb *dec_fb, u8 stream_base)
+{
+ struct dc_dec_stream stream;
+ u8 i, stream_id, num_planes = 0;
+
+ if (dec_fb) {
+ const struct drm_format_info *info = dec_fb->fb->format;
+
+ num_planes = info->num_planes;
+ }
+
+ for (i = 0; i < STREAM_COUNT; i ++) {
+ stream_id = stream_base + i;
+
+ memset(&dec400l->stream[stream_id], 0, sizeof(struct dc_dec_stream));
+
+ if (i < num_planes) {
+ memset(&stream, 0, sizeof(struct dc_dec_stream));
+ _stream_config(dec_fb, &stream, i);
+ memcpy(&dec400l->stream[stream_id], &stream,
+ sizeof(struct dc_dec_stream));
+ }
+ dec400l->stream[stream_id].dirty = true;
+ }
+
+ if (dec_fb)
+ update_fb_format(dec_fb);
+
+ return 0;
+}
+
+int dc_dec_config(struct dc_dec400l *dec400l, struct dc_dec_fb *dec_fb,
+ u8 stream_base)
+{
+ if (dec_fb && !dec_fb->fb)
+ return -EINVAL;
+
+ if (dec_fb && (fourcc_mod_vs_get_type(dec_fb->fb->modifier) !=
+ DRM_FORMAT_MOD_VS_TYPE_COMPRESSED))
+ _dec400l_config(dec400l, NULL, stream_base);
+ else
+ _dec400l_config(dec400l, dec_fb, stream_base);
+
+ return 0;
+}
+
+int dc_dec_commit(struct dc_dec400l *dec400l, struct dc_hw *hw)
+{
+ u8 i;
+
+ for (i = 0; i < STREAM_TOTAL; i++) {
+ if (!_is_stream_changed(dec400l, i))
+ continue;
+
+ if (_is_stream_valid(dec400l, i)) {
+ _enable_stream(dec400l, hw, i);
+ dc_hw_dec_stream_set(hw, dec400l->stream[i].main_base_addr,
+ dec400l->stream[i].ts_base_addr, dec400l->stream[i].tile_mode,
+ dec400l->stream[i].align_mode, dec400l->stream[i].format,
+ dec400l->stream[i].depth, i);
+ } else {
+ dc_hw_dec_stream_disable(hw, i);
+ _disable_stream(dec400l, hw, i);
+ }
+
+ dec400l->stream[i].dirty = false;
+ }
+
+ return 0;
+}
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2020 VeriSilicon Holdings Co., Ltd.
+ */
+
+#ifndef _VS_DC_DEC_H_
+#define _VS_DC_DEC_H_
+
+#include <drm/drm_fourcc.h>
+
+#include "vs_dc_hw.h"
+
+#define fourcc_mod_vs_get_type(val) \
+ (((val) & DRM_FORMAT_MOD_VS_TYPE_MASK) >> 54)
+
+/* DEC_READ_CONFIG */
+#define COMPRESSION_EN BIT(0)
+#define COMPRESSION_FORMAT_MASK GENMASK(7, 3)
+#define COMPRESSION_ALIGN_MODE_MASK GENMASK(17, 16)
+#define TILE_MODE_MASK GENMASK(30, 25)
+
+/* DEC_READ_EX_CONFIG */
+#define BIT_DEPTH_MASK GENMASK(18, 16)
+
+/* DEC_CONTROL */
+#define FLUSH_ENABLE BIT(0)
+#define COMPRESSION_DISABLE BIT(1)
+
+/* DEC_CONTROL_EX */
+#define WRITE_MISS_POLICY1 1
+#define WRITE_MISS_POLICY_MASK BIT(19)
+#define READ_MISS_POLICY0 0
+#define READ_MISS_POLICY_MASK BIT(29)
+
+/* DEC_CONTROL_EX2 */
+#define TILE_STATUS_READ_ID 16
+#define TILE_STATUS_READ_ID_MASK GENMASK(6, 0)
+#define TILE_STATUS_READ_ID_H 0
+#define TILE_STATUS_READ_ID_H_MASK GENMASK(23, 22)
+#define DISABLE_HW_DEC_FLUSH BIT(28)
+
+#define STREAM_COUNT 3
+#define STREAM_TOTAL 32
+
+#define DEC_PLANE_MAX 3
+
+enum dc_dec_align_mode {
+ DEC_ALIGN_32 = 0x02,
+ DEC_ALIGN_64,
+};
+
+enum dc_dec_format {
+ DEC_FORMAT_ARGB8,
+ DEC_FORMAT_XRGB8,
+ DEC_FORMAT_AYUV,
+ DEC_FORMAT_UYVY,
+ DEC_FORMAT_YUY2,
+ DEC_FORMAT_YUV_ONLY,
+ DEC_FORMAT_UV_MIX,
+ DEC_FORMAT_ARGB4,
+ DEC_FORMAT_XRGB4,
+ DEC_FORMAT_A1RGB5,
+ DEC_FORMAT_X1RGB5,
+ DEC_FORMAT_R5G6B5,
+ DEC_FORMAT_A2R10G10B10 = 0x0F,
+ DEC_FORMAT_BAYER,
+ DEC_FORMAT_COEFFICIENT = 0x12,
+ DEC_FORMAT_ARGB16,
+ DEC_FORMAT_X2RGB10 = 0x15,
+};
+
+enum dc_dec_depth {
+ DEC_DEPTH_8,
+ DEC_DEPTH_10,
+ DEC_DEPTH_12,
+ DEC_DEPTH_14,
+ DEC_DEPTH_16,
+};
+
+struct dc_dec_stream {
+ u32 main_base_addr;
+ u32 aligned_stride;
+ u32 ts_base_addr;
+ u8 tile_mode;
+ u8 align_mode;
+ u8 format;
+ u8 depth;
+ bool dirty;
+};
+
+struct dc_dec_fb {
+ struct drm_framebuffer *fb;
+ u32 addr[DEC_PLANE_MAX];
+ u32 stride[DEC_PLANE_MAX];
+};
+
+struct dc_dec400l {
+ struct dc_dec_stream stream[STREAM_TOTAL];
+ u32 stream_status;
+};
+
+int dc_dec_config(struct dc_dec400l *dec400l, struct dc_dec_fb *dec_fb,
+ u8 stream_base);
+int dc_dec_commit(struct dc_dec400l *dec400l, struct dc_hw *hw);
+
+#endif /* _VS_DC_DEC_H_ */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020 VeriSilicon Holdings Co., Ltd.
+ */
+
+#include <linux/io.h>
+#include <linux/bits.h>
+#include <linux/media-bus-format.h>
+
+#include <drm/vs_drm.h>
+
+#include "vs_type.h"
+#include "vs_dc_hw.h"
+#include "vs_dc_dec.h"
+
+static const u32 horKernel[] = {
+ 0x00000000, 0x20000000, 0x00002000, 0x00000000,
+ 0x00000000, 0x00000000, 0x23fd1c03, 0x00000000,
+ 0x00000000, 0x00000000, 0x181f0000, 0x000027e1,
+ 0x00000000, 0x00000000, 0x00000000, 0x2b981468,
+ 0x00000000, 0x00000000, 0x00000000, 0x10f00000,
+ 0x00002f10, 0x00000000, 0x00000000, 0x00000000,
+ 0x32390dc7, 0x00000000, 0x00000000, 0x00000000,
+ 0x0af50000, 0x0000350b, 0x00000000, 0x00000000,
+ 0x00000000, 0x3781087f, 0x00000000, 0x00000000,
+ 0x00000000, 0x06660000, 0x0000399a, 0x00000000,
+ 0x00000000, 0x00000000, 0x3b5904a7, 0x00000000,
+ 0x00000000, 0x00000000, 0x033c0000, 0x00003cc4,
+ 0x00000000, 0x00000000, 0x00000000, 0x3de1021f,
+ 0x00000000, 0x00000000, 0x00000000, 0x01470000,
+ 0x00003eb9, 0x00000000, 0x00000000, 0x00000000,
+ 0x3f5300ad, 0x00000000, 0x00000000, 0x00000000,
+ 0x00480000, 0x00003fb8, 0x00000000, 0x00000000,
+ 0x00000000, 0x3fef0011, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00004000, 0x00000000,
+ 0x00000000, 0x00000000, 0x20002000, 0x00000000,
+ 0x00000000, 0x00000000, 0x1c030000, 0x000023fd,
+ 0x00000000, 0x00000000, 0x00000000, 0x27e1181f,
+ 0x00000000, 0x00000000, 0x00000000, 0x14680000,
+ 0x00002b98, 0x00000000, 0x00000000, 0x00000000,
+ 0x2f1010f0, 0x00000000, 0x00000000, 0x00000000,
+ 0x0dc70000, 0x00003239, 0x00000000, 0x00000000,
+ 0x00000000, 0x350b0af5, 0x00000000, 0x00000000,
+ 0x00000000, 0x087f0000, 0x00003781, 0x00000000,
+ 0x00000000, 0x00000000, 0x399a0666, 0x00000000,
+ 0x00000000, 0x00000000, 0x04a70000, 0x00003b59,
+ 0x00000000, 0x00000000, 0x00000000, 0x3cc4033c,
+ 0x00000000, 0x00000000, 0x00000000, 0x021f0000,
+};
+#define H_COEF_SIZE (sizeof(horKernel) / sizeof(u32))
+
+static const u32 verKernel[] = {
+ 0x00000000, 0x20000000, 0x00002000, 0x00000000,
+ 0x00000000, 0x00000000, 0x23fd1c03, 0x00000000,
+ 0x00000000, 0x00000000, 0x181f0000, 0x000027e1,
+ 0x00000000, 0x00000000, 0x00000000, 0x2b981468,
+ 0x00000000, 0x00000000, 0x00000000, 0x10f00000,
+ 0x00002f10, 0x00000000, 0x00000000, 0x00000000,
+ 0x32390dc7, 0x00000000, 0x00000000, 0x00000000,
+ 0x0af50000, 0x0000350b, 0x00000000, 0x00000000,
+ 0x00000000, 0x3781087f, 0x00000000, 0x00000000,
+ 0x00000000, 0x06660000, 0x0000399a, 0x00000000,
+ 0x00000000, 0x00000000, 0x3b5904a7, 0x00000000,
+ 0x00000000, 0x00000000, 0x033c0000, 0x00003cc4,
+ 0x00000000, 0x00000000, 0x00000000, 0x3de1021f,
+ 0x00000000, 0x00000000, 0x00000000, 0x01470000,
+ 0x00003eb9, 0x00000000, 0x00000000, 0x00000000,
+ 0x3f5300ad, 0x00000000, 0x00000000, 0x00000000,
+ 0x00480000, 0x00003fb8, 0x00000000, 0x00000000,
+ 0x00000000, 0x3fef0011, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00004000, 0x00000000,
+ 0xcdcd0000, 0xfdfdfdfd, 0xabababab, 0xabababab,
+ 0x00000000, 0x00000000, 0x5ff5f456, 0x000f5f58,
+ 0x02cc6c78, 0x02cc0c28, 0xfeeefeee, 0xfeeefeee,
+ 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee,
+ 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee,
+ 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee,
+ 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee,
+ 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee,
+ 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee,
+ 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee,
+ 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee,
+ 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee,
+ 0xfeeefeee, 0xfeeefeee, 0xfeeefeee, 0xfeeefeee,
+};
+#define V_COEF_SIZE (sizeof(verKernel) / sizeof(u32))
+
+/*
+ * RGB 709->2020 conversion parameters
+ */
+static u16 RGB2RGB[RGB_TO_RGB_TABLE_SIZE] = {
+ 10279, 5395, 709,
+ 1132, 15065, 187,
+ 269, 1442, 14674
+};
+
+/*
+ * YUV601 to RGB conversion parameters
+ * YUV2RGB[0] - [8] : C0 - C8;
+ * YUV2RGB[9] - [11]: D0 - D2;
+ * YUV2RGB[12] - [13]: Y clamp min & max calue;
+ * YUV2RGB[14] - [15]: UV clamp min & max calue;
+ */
+static s32 YUV601_2RGB[YUV_TO_RGB_TABLE_SIZE] = {
+ 1196, 0, 1640, 1196,
+ -404, -836, 1196, 2076,
+ 0, -916224, 558336, -1202944,
+ 64, 940, 64, 960
+};
+
+/*
+ * YUV709 to RGB conversion parameters
+ * YUV2RGB[0] - [8] : C0 - C8;
+ * YUV2RGB[9] - [11]: D0 - D2;
+ * YUV2RGB[12] - [13]: Y clamp min & max calue;
+ * YUV2RGB[14] - [15]: UV clamp min & max calue;
+ */
+static s32 YUV709_2RGB[YUV_TO_RGB_TABLE_SIZE] = {
+ 1196, 0, 1844, 1196,
+ -220, -548, 1196, 2172,
+ 0, -1020672, 316672, -1188608,
+ 64, 940, 64, 960
+};
+
+/*
+ * YUV2020 to RGB conversion parameters
+ * YUV2RGB[0] - [8] : C0 - C8;
+ * YUV2RGB[9] - [11]: D0 - D2;
+ * YUV2RGB[12] - [13]: Y clamp min & max calue;
+ * YUV2RGB[14] - [15]: UV clamp min & max calue;
+ */
+static s32 YUV2020_2RGB[YUV_TO_RGB_TABLE_SIZE] = {
+ 1196, 0, 1724, 1196,
+ -192, -668, 1196, 2200,
+ 0, -959232, 363776, -1202944,
+ 64, 940, 64, 960
+};
+
+/*
+ * RGB to YUV2020 conversion parameters
+ * RGB2YUV[0] - [8] : C0 - C8;
+ * RGB2YUV[9] - [11]: D0 - D2;
+ */
+static s16 RGB2YUV[RGB_TO_YUV_TABLE_SIZE] = {
+ 230, 594, 52,
+ -125, -323, 448,
+ 448, -412, -36,
+ 64, 512, 512
+};
+
+/*
+ * Degamma table for 709 color space data.
+ */
+static u16 DEGAMMA_709[DEGAMMA_SIZE] = {
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0002, 0x0004, 0x0005,
+ 0x0007, 0x000a, 0x000d, 0x0011, 0x0015, 0x0019, 0x001e, 0x0024,
+ 0x002a, 0x0030, 0x0038, 0x003f, 0x0048, 0x0051, 0x005a, 0x0064,
+ 0x006f, 0x007b, 0x0087, 0x0094, 0x00a1, 0x00af, 0x00be, 0x00ce,
+ 0x00de, 0x00ef, 0x0101, 0x0114, 0x0127, 0x013b, 0x0150, 0x0166,
+ 0x017c, 0x0193, 0x01ac, 0x01c4, 0x01de, 0x01f9, 0x0214, 0x0230,
+ 0x024d, 0x026b, 0x028a, 0x02aa, 0x02ca, 0x02ec, 0x030e, 0x0331,
+ 0x0355, 0x037a, 0x03a0, 0x03c7, 0x03ef, 0x0418, 0x0441, 0x046c,
+ 0x0498, 0x04c4, 0x04f2, 0x0520, 0x0550, 0x0581, 0x05b2, 0x05e5,
+ 0x0618, 0x064d, 0x0682, 0x06b9, 0x06f0, 0x0729, 0x0763, 0x079d,
+ 0x07d9, 0x0816, 0x0854, 0x0893, 0x08d3, 0x0914, 0x0956, 0x0999,
+ 0x09dd, 0x0a23, 0x0a69, 0x0ab1, 0x0afa, 0x0b44, 0x0b8f, 0x0bdb,
+ 0x0c28, 0x0c76, 0x0cc6, 0x0d17, 0x0d69, 0x0dbb, 0x0e10, 0x0e65,
+ 0x0ebb, 0x0f13, 0x0f6c, 0x0fc6, 0x1021, 0x107d, 0x10db, 0x113a,
+ 0x119a, 0x11fb, 0x125d, 0x12c1, 0x1325, 0x138c, 0x13f3, 0x145b,
+ 0x14c5, 0x1530, 0x159c, 0x160a, 0x1678, 0x16e8, 0x175a, 0x17cc,
+ 0x1840, 0x18b5, 0x192b, 0x19a3, 0x1a1c, 0x1a96, 0x1b11, 0x1b8e,
+ 0x1c0c, 0x1c8c, 0x1d0c, 0x1d8e, 0x1e12, 0x1e96, 0x1f1c, 0x1fa3,
+ 0x202c, 0x20b6, 0x2141, 0x21ce, 0x225c, 0x22eb, 0x237c, 0x240e,
+ 0x24a1, 0x2536, 0x25cc, 0x2664, 0x26fc, 0x2797, 0x2832, 0x28cf,
+ 0x296e, 0x2a0e, 0x2aaf, 0x2b51, 0x2bf5, 0x2c9b, 0x2d41, 0x2dea,
+ 0x2e93, 0x2f3e, 0x2feb, 0x3099, 0x3148, 0x31f9, 0x32ab, 0x335f,
+ 0x3414, 0x34ca, 0x3582, 0x363c, 0x36f7, 0x37b3, 0x3871, 0x3930,
+ 0x39f1, 0x3ab3, 0x3b77, 0x3c3c, 0x3d02, 0x3dcb, 0x3e94, 0x3f5f,
+ 0x402c, 0x40fa, 0x41ca, 0x429b, 0x436d, 0x4442, 0x4517, 0x45ee,
+ 0x46c7, 0x47a1, 0x487d, 0x495a, 0x4a39, 0x4b19, 0x4bfb, 0x4cde,
+ 0x4dc3, 0x4eaa, 0x4f92, 0x507c, 0x5167, 0x5253, 0x5342, 0x5431,
+ 0x5523, 0x5616, 0x570a, 0x5800, 0x58f8, 0x59f1, 0x5aec, 0x5be9,
+ 0x5ce7, 0x5de6, 0x5ee7, 0x5fea, 0x60ef, 0x61f5, 0x62fc, 0x6406,
+ 0x6510, 0x661d, 0x672b, 0x683b, 0x694c, 0x6a5f, 0x6b73, 0x6c8a,
+ 0x6da2, 0x6ebb, 0x6fd6, 0x70f3, 0x7211, 0x7331, 0x7453, 0x7576,
+ 0x769b, 0x77c2, 0x78ea, 0x7a14, 0x7b40, 0x7c6d, 0x7d9c, 0x7ecd,
+ 0x3f65, 0x3f8c, 0x3fb2, 0x3fd8
+};
+
+/*
+ * Degamma table for 2020 color space data.
+ */
+static u16 DEGAMMA_2020[DEGAMMA_SIZE] = {
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001, 0x0001,
+ 0x0001, 0x0002, 0x0002, 0x0002, 0x0002, 0x0002, 0x0003, 0x0003,
+ 0x0003, 0x0003, 0x0004, 0x0004, 0x0004, 0x0005, 0x0005, 0x0006,
+ 0x0006, 0x0006, 0x0007, 0x0007, 0x0008, 0x0008, 0x0009, 0x000a,
+ 0x000a, 0x000b, 0x000c, 0x000c, 0x000d, 0x000e, 0x000f, 0x000f,
+ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0016, 0x0017, 0x0018,
+ 0x0019, 0x001b, 0x001c, 0x001e, 0x001f, 0x0021, 0x0022, 0x0024,
+ 0x0026, 0x0028, 0x002a, 0x002c, 0x002e, 0x0030, 0x0033, 0x0035,
+ 0x0038, 0x003a, 0x003d, 0x0040, 0x0043, 0x0046, 0x0049, 0x004d,
+ 0x0050, 0x0054, 0x0057, 0x005b, 0x005f, 0x0064, 0x0068, 0x006d,
+ 0x0071, 0x0076, 0x007c, 0x0081, 0x0086, 0x008c, 0x0092, 0x0098,
+ 0x009f, 0x00a5, 0x00ac, 0x00b4, 0x00bb, 0x00c3, 0x00cb, 0x00d3,
+ 0x00dc, 0x00e5, 0x00ee, 0x00f8, 0x0102, 0x010c, 0x0117, 0x0123,
+ 0x012e, 0x013a, 0x0147, 0x0154, 0x0161, 0x016f, 0x017e, 0x018d,
+ 0x019c, 0x01ac, 0x01bd, 0x01ce, 0x01e0, 0x01f3, 0x0206, 0x021a,
+ 0x022f, 0x0244, 0x025a, 0x0272, 0x0289, 0x02a2, 0x02bc, 0x02d6,
+ 0x02f2, 0x030f, 0x032c, 0x034b, 0x036b, 0x038b, 0x03ae, 0x03d1,
+ 0x03f5, 0x041b, 0x0443, 0x046b, 0x0495, 0x04c1, 0x04ee, 0x051d,
+ 0x054e, 0x0580, 0x05b4, 0x05ea, 0x0622, 0x065c, 0x0698, 0x06d6,
+ 0x0717, 0x075a, 0x079f, 0x07e7, 0x0831, 0x087e, 0x08cd, 0x0920,
+ 0x0976, 0x09ce, 0x0a2a, 0x0a89, 0x0aec, 0x0b52, 0x0bbc, 0x0c2a,
+ 0x0c9b, 0x0d11, 0x0d8b, 0x0e0a, 0x0e8d, 0x0f15, 0x0fa1, 0x1033,
+ 0x10ca, 0x1167, 0x120a, 0x12b2, 0x1360, 0x1415, 0x14d1, 0x1593,
+ 0x165d, 0x172e, 0x1806, 0x18e7, 0x19d0, 0x1ac1, 0x1bbb, 0x1cbf,
+ 0x1dcc, 0x1ee3, 0x2005, 0x2131, 0x2268, 0x23ab, 0x24fa, 0x2656,
+ 0x27be, 0x2934, 0x2ab8, 0x2c4a, 0x2dec, 0x2f9d, 0x315f, 0x3332,
+ 0x3516, 0x370d, 0x3916, 0x3b34, 0x3d66, 0x3fad, 0x420b, 0x4480,
+ 0x470d, 0x49b3, 0x4c73, 0x4f4e, 0x5246, 0x555a, 0x588e, 0x5be1,
+ 0x5f55, 0x62eb, 0x66a6, 0x6a86, 0x6e8c, 0x72bb, 0x7714, 0x7b99,
+ 0x3dcb, 0x3e60, 0x3ef5, 0x3f8c
+};
+
+/* one is for primary plane and the other is for all overlay planes */
+static const struct dc_hw_plane_reg dc_plane_reg[] = {
+ {
+ .y_address = DC_FRAMEBUFFER_ADDRESS,
+ .u_address = DC_FRAMEBUFFER_U_ADDRESS,
+ .v_address = DC_FRAMEBUFFER_V_ADDRESS,
+ .y_stride = DC_FRAMEBUFFER_STRIDE,
+ .u_stride = DC_FRAMEBUFFER_U_STRIDE,
+ .v_stride = DC_FRAMEBUFFER_V_STRIDE,
+ .size = DC_FRAMEBUFFER_SIZE,
+ .top_left = DC_FRAMEBUFFER_TOP_LEFT,
+ .bottom_right = DC_FRAMEBUFFER_BOTTOM_RIGHT,
+ .scale_factor_x = DC_FRAMEBUFFER_SCALE_FACTOR_X,
+ .scale_factor_y = DC_FRAMEBUFFER_SCALE_FACTOR_Y,
+ .h_filter_coef_index = DC_FRAMEBUFFER_H_FILTER_COEF_INDEX,
+ .h_filter_coef_data = DC_FRAMEBUFFER_H_FILTER_COEF_DATA,
+ .v_filter_coef_index = DC_FRAMEBUFFER_V_FILTER_COEF_INDEX,
+ .v_filter_coef_data = DC_FRAMEBUFFER_V_FILTER_COEF_DATA,
+ .init_offset = DC_FRAMEBUFFER_INIT_OFFSET,
+ .color_key = DC_FRAMEBUFFER_COLOR_KEY,
+ .color_key_high = DC_FRAMEBUFFER_COLOR_KEY_HIGH,
+ .clear_value = DC_FRAMEBUFFER_CLEAR_VALUE,
+ .color_table_index = DC_FRAMEBUFFER_COLOR_TABLE_INDEX,
+ .color_table_data = DC_FRAMEBUFFER_COLOR_TABLE_DATA,
+ .scale_config = DC_FRAMEBUFFER_SCALE_CONFIG,
+ .water_mark = DC_FRAMEBUFFER_WATER_MARK,
+ .degamma_index = DC_FRAMEBUFFER_DEGAMMA_INDEX,
+ .degamma_data = DC_FRAMEBUFFER_DEGAMMA_DATA,
+ .degamma_ex_data = DC_FRAMEBUFFER_DEGAMMA_EX_DATA,
+ .src_global_color = DC_FRAMEBUFFER_SRC_GLOBAL_COLOR,
+ .dst_global_color = DC_FRAMEBUFFER_DST_GLOBAL_COLOR,
+ .blend_config = DC_FRAMEBUFFER_BLEND_CONFIG,
+ .roi_origin = DC_FRAMEBUFFER_ROI_ORIGIN,
+ .roi_size = DC_FRAMEBUFFER_ROI_SIZE,
+ .YUVToRGBCoef0 = DC_FRAMEBUFFER_YUVTORGB_COEF0,
+ .YUVToRGBCoef1 = DC_FRAMEBUFFER_YUVTORGB_COEF1,
+ .YUVToRGBCoef2 = DC_FRAMEBUFFER_YUVTORGB_COEF2,
+ .YUVToRGBCoef3 = DC_FRAMEBUFFER_YUVTORGB_COEF3,
+ .YUVToRGBCoef4 = DC_FRAMEBUFFER_YUVTORGB_COEF4,
+ .YUVToRGBCoefD0 = DC_FRAMEBUFFER_YUVTORGB_COEFD0,
+ .YUVToRGBCoefD1 = DC_FRAMEBUFFER_YUVTORGB_COEFD1,
+ .YUVToRGBCoefD2 = DC_FRAMEBUFFER_YUVTORGB_COEFD2,
+ .YClampBound = DC_FRAMEBUFFER_Y_CLAMP_BOUND,
+ .UVClampBound = DC_FRAMEBUFFER_UV_CLAMP_BOUND,
+ .RGBToRGBCoef0 = DC_FRAMEBUFFER_RGBTORGB_COEF0,
+ .RGBToRGBCoef1 = DC_FRAMEBUFFER_RGBTORGB_COEF1,
+ .RGBToRGBCoef2 = DC_FRAMEBUFFER_RGBTORGB_COEF2,
+ .RGBToRGBCoef3 = DC_FRAMEBUFFER_RGBTORGB_COEF3,
+ .RGBToRGBCoef4 = DC_FRAMEBUFFER_RGBTORGB_COEF4,
+ },
+ {
+ .y_address = DC_OVERLAY_ADDRESS,
+ .u_address = DC_OVERLAY_U_ADDRESS,
+ .v_address = DC_OVERLAY_V_ADDRESS,
+ .y_stride = DC_OVERLAY_STRIDE,
+ .u_stride = DC_OVERLAY_U_STRIDE,
+ .v_stride = DC_OVERLAY_V_STRIDE,
+ .size = DC_OVERLAY_SIZE,
+ .top_left = DC_OVERLAY_TOP_LEFT,
+ .bottom_right = DC_OVERLAY_BOTTOM_RIGHT,
+ .scale_factor_x = DC_OVERLAY_SCALE_FACTOR_X,
+ .scale_factor_y = DC_OVERLAY_SCALE_FACTOR_Y,
+ .h_filter_coef_index = DC_OVERLAY_H_FILTER_COEF_INDEX,
+ .h_filter_coef_data = DC_OVERLAY_H_FILTER_COEF_DATA,
+ .v_filter_coef_index = DC_OVERLAY_V_FILTER_COEF_INDEX,
+ .v_filter_coef_data = DC_OVERLAY_V_FILTER_COEF_DATA,
+ .init_offset = DC_OVERLAY_INIT_OFFSET,
+ .color_key = DC_OVERLAY_COLOR_KEY,
+ .color_key_high = DC_OVERLAY_COLOR_KEY_HIGH,
+ .clear_value = DC_OVERLAY_CLEAR_VALUE,
+ .color_table_index = DC_OVERLAY_COLOR_TABLE_INDEX,
+ .color_table_data = DC_OVERLAY_COLOR_TABLE_DATA,
+ .scale_config = DC_OVERLAY_SCALE_CONFIG,
+ .water_mark = DC_OVERLAY_WATER_MARK,
+ .degamma_index = DC_OVERLAY_DEGAMMA_INDEX,
+ .degamma_data = DC_OVERLAY_DEGAMMA_DATA,
+ .degamma_ex_data = DC_OVERLAY_DEGAMMA_EX_DATA,
+ .src_global_color = DC_OVERLAY_SRC_GLOBAL_COLOR,
+ .dst_global_color = DC_OVERLAY_DST_GLOBAL_COLOR,
+ .blend_config = DC_OVERLAY_BLEND_CONFIG,
+ .roi_origin = DC_OVERLAY_ROI_ORIGIN,
+ .roi_size = DC_OVERLAY_ROI_SIZE,
+ .YUVToRGBCoef0 = DC_OVERLAY_YUVTORGB_COEF0,
+ .YUVToRGBCoef1 = DC_OVERLAY_YUVTORGB_COEF1,
+ .YUVToRGBCoef2 = DC_OVERLAY_YUVTORGB_COEF2,
+ .YUVToRGBCoef3 = DC_OVERLAY_YUVTORGB_COEF3,
+ .YUVToRGBCoef4 = DC_OVERLAY_YUVTORGB_COEF4,
+ .YUVToRGBCoefD0 = DC_OVERLAY_YUVTORGB_COEFD0,
+ .YUVToRGBCoefD1 = DC_OVERLAY_YUVTORGB_COEFD1,
+ .YUVToRGBCoefD2 = DC_OVERLAY_YUVTORGB_COEFD2,
+ .YClampBound = DC_OVERLAY_Y_CLAMP_BOUND,
+ .UVClampBound = DC_OVERLAY_UV_CLAMP_BOUND,
+ .RGBToRGBCoef0 = DC_OVERLAY_RGBTORGB_COEF0,
+ .RGBToRGBCoef1 = DC_OVERLAY_RGBTORGB_COEF1,
+ .RGBToRGBCoef2 = DC_OVERLAY_RGBTORGB_COEF2,
+ .RGBToRGBCoef3 = DC_OVERLAY_RGBTORGB_COEF3,
+ .RGBToRGBCoef4 = DC_OVERLAY_RGBTORGB_COEF4,
+ },
+};
+
+#ifdef CONFIG_VERISILICON_MMU
+static const u32 mmu_reg_base = SE_MMU_REG_BASE;
+
+static const struct dc_hw_mmu_reg dc_mmu_reg = {
+ .mmu_config = SE_MMU_REG_CONFIG,
+ .mmu_control = SE_MMU_REG_CONTROL,
+ .table_array_size = SE_MMU_REG_TABLE_ARRAY_SIZE,
+ .safe_non_secure = SE_MMU_REG_SAFE_NON_SECUR,
+ .safe_secure = SE_MMU_REG_SAFE_SECURE,
+ .safe_ex = SE_MMU_REG_SAFE_EXT_ADDRESS,
+ .context_pd_entry = SE_MMU_REG_CONTEXT_PD,
+};
+#endif
+
+static const u32 primary_overlay_format0[] = {
+ DRM_FORMAT_XRGB4444,
+ DRM_FORMAT_XBGR4444,
+ DRM_FORMAT_RGBX4444,
+ DRM_FORMAT_BGRX4444,
+ DRM_FORMAT_ARGB4444,
+ DRM_FORMAT_ABGR4444,
+ DRM_FORMAT_RGBA4444,
+ DRM_FORMAT_BGRA4444,
+ DRM_FORMAT_XRGB1555,
+ DRM_FORMAT_XBGR1555,
+ DRM_FORMAT_RGBX5551,
+ DRM_FORMAT_BGRX5551,
+ DRM_FORMAT_ARGB1555,
+ DRM_FORMAT_ABGR1555,
+ DRM_FORMAT_RGBA5551,
+ DRM_FORMAT_BGRA5551,
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_BGR565,
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_XBGR8888,
+ DRM_FORMAT_RGBX8888,
+ DRM_FORMAT_BGRX8888,
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_ABGR8888,
+ DRM_FORMAT_RGBA8888,
+ DRM_FORMAT_BGRA8888,
+ DRM_FORMAT_ARGB2101010,
+ DRM_FORMAT_ABGR2101010,
+ DRM_FORMAT_RGBA1010102,
+ DRM_FORMAT_BGRA1010102,
+ DRM_FORMAT_YUYV,
+ DRM_FORMAT_YVYU,
+ DRM_FORMAT_UYVY,
+ DRM_FORMAT_VYUY,
+ DRM_FORMAT_YVU420,
+ DRM_FORMAT_YUV420,
+ DRM_FORMAT_NV12,
+ DRM_FORMAT_NV21,
+ DRM_FORMAT_NV16,
+ DRM_FORMAT_NV61,
+ DRM_FORMAT_P010,
+};
+
+static const u32 primary_overlay_format1[] = {
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_XBGR8888,
+ DRM_FORMAT_RGBX8888,
+ DRM_FORMAT_BGRX8888,
+ DRM_FORMAT_ARGB8888,
+ DRM_FORMAT_ABGR8888,
+ DRM_FORMAT_RGBA8888,
+ DRM_FORMAT_BGRA8888,
+ DRM_FORMAT_ARGB2101010,
+ DRM_FORMAT_ABGR2101010,
+ DRM_FORMAT_RGBA1010102,
+ DRM_FORMAT_BGRA1010102,
+ DRM_FORMAT_NV12,
+ DRM_FORMAT_NV21,
+ DRM_FORMAT_YUV444,
+};
+
+static const u32 cursor_formats[] = {
+ DRM_FORMAT_ARGB8888
+};
+
+static const u64 format_modifier0[] = {
+ DRM_FORMAT_MOD_LINEAR,
+ fourcc_mod_vs_norm_code(DRM_FORMAT_MOD_VS_LINEAR),
+ fourcc_mod_vs_norm_code(DRM_FORMAT_MOD_VS_SUPER_TILED_XMAJOR),
+ fourcc_mod_vs_norm_code(DRM_FORMAT_MOD_VS_SUPER_TILED_YMAJOR),
+ fourcc_mod_vs_norm_code(DRM_FORMAT_MOD_VS_TILE_8X8),
+ fourcc_mod_vs_norm_code(DRM_FORMAT_MOD_VS_TILE_8X4),
+ fourcc_mod_vs_norm_code(DRM_FORMAT_MOD_VS_SUPER_TILED_XMAJOR_8X4),
+ fourcc_mod_vs_norm_code(DRM_FORMAT_MOD_VS_SUPER_TILED_YMAJOR_4X8),
+#ifdef CONFIG_VERISILICON_DEC
+ fourcc_mod_vs_dec_code(DRM_FORMAT_MOD_VS_DEC_TILE_8X8_XMAJOR,
+ DRM_FORMAT_MOD_VS_DEC_ALIGN_32),
+ fourcc_mod_vs_dec_code(DRM_FORMAT_MOD_VS_DEC_TILE_8X4,
+ DRM_FORMAT_MOD_VS_DEC_ALIGN_32),
+ fourcc_mod_vs_dec_code(DRM_FORMAT_MOD_VS_DEC_TILE_4X8,
+ DRM_FORMAT_MOD_VS_DEC_ALIGN_32),
+ fourcc_mod_vs_dec_code(DRM_FORMAT_MOD_VS_DEC_RASTER_256X1,
+ DRM_FORMAT_MOD_VS_DEC_ALIGN_32),
+ fourcc_mod_vs_dec_code(DRM_FORMAT_MOD_VS_DEC_RASTER_128X1,
+ DRM_FORMAT_MOD_VS_DEC_ALIGN_32),
+ fourcc_mod_vs_dec_code(DRM_FORMAT_MOD_VS_DEC_RASTER_64X1,
+ DRM_FORMAT_MOD_VS_DEC_ALIGN_32),
+ fourcc_mod_vs_dec_code(DRM_FORMAT_MOD_VS_DEC_TILE_16X8,
+ DRM_FORMAT_MOD_VS_DEC_ALIGN_32),
+ fourcc_mod_vs_dec_code(DRM_FORMAT_MOD_VS_DEC_RASTER_32X1,
+ DRM_FORMAT_MOD_VS_DEC_ALIGN_32),
+ fourcc_mod_vs_dec_code(DRM_FORMAT_MOD_VS_DEC_TILE_32X8,
+ DRM_FORMAT_MOD_VS_DEC_ALIGN_32),
+#endif
+ DRM_FORMAT_MOD_INVALID
+};
+
+static const u64 format_modifier1[] = {
+ DRM_FORMAT_MOD_LINEAR,
+ fourcc_mod_vs_norm_code(DRM_FORMAT_MOD_VS_LINEAR),
+ fourcc_mod_vs_norm_code(DRM_FORMAT_MOD_VS_SUPER_TILED_XMAJOR),
+ fourcc_mod_vs_norm_code(DRM_FORMAT_MOD_VS_SUPER_TILED_YMAJOR),
+ fourcc_mod_vs_norm_code(DRM_FORMAT_MOD_VS_TILE_8X8),
+ fourcc_mod_vs_norm_code(DRM_FORMAT_MOD_VS_TILE_8X4),
+ fourcc_mod_vs_norm_code(DRM_FORMAT_MOD_VS_SUPER_TILED_XMAJOR_8X4),
+ fourcc_mod_vs_norm_code(DRM_FORMAT_MOD_VS_SUPER_TILED_YMAJOR_4X8),
+ fourcc_mod_vs_norm_code(DRM_FORMAT_MOD_VS_TILE_MODE4X4),
+ fourcc_mod_vs_custom_code(DRM_FORMAT_MOD_VS_TILE_MODE4X4),
+ DRM_FORMAT_MOD_INVALID
+};
+
+static const u64 secondary_format_modifiers[] = {
+ DRM_FORMAT_MOD_LINEAR,
+#ifdef CONFIG_VERISILICON_DEC
+ fourcc_mod_vs_dec_code(DRM_FORMAT_MOD_VS_DEC_RASTER_256X1,
+ DRM_FORMAT_MOD_VS_DEC_ALIGN_32),
+ fourcc_mod_vs_dec_code(DRM_FORMAT_MOD_VS_DEC_RASTER_128X1,
+ DRM_FORMAT_MOD_VS_DEC_ALIGN_32),
+ fourcc_mod_vs_dec_code(DRM_FORMAT_MOD_VS_DEC_RASTER_64X1,
+ DRM_FORMAT_MOD_VS_DEC_ALIGN_32),
+ fourcc_mod_vs_dec_code(DRM_FORMAT_MOD_VS_DEC_RASTER_32X1,
+ DRM_FORMAT_MOD_VS_DEC_ALIGN_32),
+#endif
+ DRM_FORMAT_MOD_INVALID
+};
+
+#define FRAC_16_16(mult, div) (((mult) << 16) / (div))
+
+static const struct vs_plane_info dc_hw_planes[][PLANE_NUM] = {
+ {
+ /* DC_REV_0 */
+ {
+ .name = "Primary",
+ .id = PRIMARY_PLANE_0,
+ .type = DRM_PLANE_TYPE_PRIMARY,
+ .num_formats = ARRAY_SIZE(primary_overlay_format0),
+ .formats = primary_overlay_format0,
+ .num_modifiers = ARRAY_SIZE(format_modifier0),
+ .modifiers = format_modifier0,
+ .min_width = 0,
+ .min_height = 0,
+ .max_width = 4096,
+ .max_height = 4096,
+ .rotation = DRM_MODE_ROTATE_0 |
+ DRM_MODE_ROTATE_90 |
+ DRM_MODE_ROTATE_180 |
+ DRM_MODE_ROTATE_270 |
+ DRM_MODE_REFLECT_X |
+ DRM_MODE_REFLECT_Y,
+ .blend_mode = BIT(DRM_MODE_BLEND_PIXEL_NONE) |
+ BIT(DRM_MODE_BLEND_PREMULTI) |
+ BIT(DRM_MODE_BLEND_COVERAGE),
+ .color_encoding = BIT(DRM_COLOR_YCBCR_BT709) |
+ BIT(DRM_COLOR_YCBCR_BT2020),
+ .degamma_size = DEGAMMA_SIZE,
+ .min_scale = FRAC_16_16(1, 3),
+ .max_scale = FRAC_16_16(10, 1),
+ .zpos = 0,
+ .watermark = true,
+ .color_mgmt = true,
+ .roi = true,
+ },
+ {
+ .name = "Overlay",
+ .id = OVERLAY_PLANE_0,
+ .type = DRM_PLANE_TYPE_OVERLAY,
+ .num_formats = ARRAY_SIZE(primary_overlay_format0),
+ .formats = primary_overlay_format0,
+ .num_modifiers = ARRAY_SIZE(format_modifier0),
+ .modifiers = format_modifier0,
+ .min_width = 0,
+ .min_height = 0,
+ .max_width = 4096,
+ .max_height = 4096,
+ .rotation = DRM_MODE_ROTATE_0 |
+ DRM_MODE_ROTATE_90 |
+ DRM_MODE_ROTATE_180 |
+ DRM_MODE_ROTATE_270 |
+ DRM_MODE_REFLECT_X |
+ DRM_MODE_REFLECT_Y,
+ .blend_mode = BIT(DRM_MODE_BLEND_PIXEL_NONE) |
+ BIT(DRM_MODE_BLEND_PREMULTI) |
+ BIT(DRM_MODE_BLEND_COVERAGE),
+ .color_encoding = BIT(DRM_COLOR_YCBCR_BT709) |
+ BIT(DRM_COLOR_YCBCR_BT2020),
+ .degamma_size = DEGAMMA_SIZE,
+ .min_scale = FRAC_16_16(1, 3),
+ .max_scale = FRAC_16_16(10, 1),
+ .zpos = 1,
+ .watermark = true,
+ .color_mgmt = true,
+ .roi = true,
+ },
+ {
+ .name = "Overlay_1",
+ .id = OVERLAY_PLANE_1,
+ .type = DRM_PLANE_TYPE_OVERLAY,
+ .num_formats = ARRAY_SIZE(primary_overlay_format0),
+ .formats = primary_overlay_format0,
+ .num_modifiers = ARRAY_SIZE(secondary_format_modifiers),
+ .modifiers = secondary_format_modifiers,
+ .min_width = 0,
+ .min_height = 0,
+ .max_width = 4096,
+ .max_height = 4096,
+ .rotation = 0,
+ .blend_mode = BIT(DRM_MODE_BLEND_PIXEL_NONE) |
+ BIT(DRM_MODE_BLEND_PREMULTI) |
+ BIT(DRM_MODE_BLEND_COVERAGE),
+ .color_encoding = BIT(DRM_COLOR_YCBCR_BT709) |
+ BIT(DRM_COLOR_YCBCR_BT2020),
+ .degamma_size = DEGAMMA_SIZE,
+ .min_scale = DRM_PLANE_HELPER_NO_SCALING,
+ .max_scale = DRM_PLANE_HELPER_NO_SCALING,
+ .zpos = 2,
+ .watermark = true,
+ .color_mgmt = true,
+ .roi = true,
+ },
+ {
+ .name = "Primary_1",
+ .id = PRIMARY_PLANE_1,
+ .type = DRM_PLANE_TYPE_PRIMARY,
+ .num_formats = ARRAY_SIZE(primary_overlay_format0),
+ .formats = primary_overlay_format0,
+ .num_modifiers = ARRAY_SIZE(format_modifier0),
+ .modifiers = format_modifier0,
+ .min_width = 0,
+ .min_height = 0,
+ .max_width = 4096,
+ .max_height = 4096,
+ .rotation = DRM_MODE_ROTATE_0 |
+ DRM_MODE_ROTATE_90 |
+ DRM_MODE_ROTATE_180 |
+ DRM_MODE_ROTATE_270 |
+ DRM_MODE_REFLECT_X |
+ DRM_MODE_REFLECT_Y,
+ .blend_mode = BIT(DRM_MODE_BLEND_PIXEL_NONE) |
+ BIT(DRM_MODE_BLEND_PREMULTI) |
+ BIT(DRM_MODE_BLEND_COVERAGE),
+ .color_encoding = BIT(DRM_COLOR_YCBCR_BT709) |
+ BIT(DRM_COLOR_YCBCR_BT2020),
+ .degamma_size = DEGAMMA_SIZE,
+ .min_scale = FRAC_16_16(1, 3),
+ .max_scale = FRAC_16_16(10, 1),
+ .zpos = 3,
+ .watermark = true,
+ .color_mgmt = true,
+ .roi = true,
+ },
+ {
+ .name = "Overlay_2",
+ .id = OVERLAY_PLANE_2,
+ .type = DRM_PLANE_TYPE_OVERLAY,
+ .num_formats = ARRAY_SIZE(primary_overlay_format0),
+ .formats = primary_overlay_format0,
+ .num_modifiers = ARRAY_SIZE(format_modifier0),
+ .modifiers = format_modifier0,
+ .min_width = 0,
+ .min_height = 0,
+ .max_width = 4096,
+ .max_height = 4096,
+ .rotation = DRM_MODE_ROTATE_0 |
+ DRM_MODE_ROTATE_90 |
+ DRM_MODE_ROTATE_180 |
+ DRM_MODE_ROTATE_270 |
+ DRM_MODE_REFLECT_X |
+ DRM_MODE_REFLECT_Y,
+ .blend_mode = BIT(DRM_MODE_BLEND_PIXEL_NONE) |
+ BIT(DRM_MODE_BLEND_PREMULTI) |
+ BIT(DRM_MODE_BLEND_COVERAGE),
+ .color_encoding = BIT(DRM_COLOR_YCBCR_BT709) |
+ BIT(DRM_COLOR_YCBCR_BT2020),
+ .degamma_size = DEGAMMA_SIZE,
+ .min_scale = FRAC_16_16(1, 3),
+ .max_scale = FRAC_16_16(10, 1),
+ .zpos = 4,
+ .watermark = true,
+ .color_mgmt = true,
+ .roi = true,
+ },
+ {
+ .name = "Overlay_3",
+ .id = OVERLAY_PLANE_3,
+ .type = DRM_PLANE_TYPE_OVERLAY,
+ .num_formats = ARRAY_SIZE(primary_overlay_format0),
+ .formats = primary_overlay_format0,
+ .num_modifiers = ARRAY_SIZE(secondary_format_modifiers),
+ .modifiers = secondary_format_modifiers,
+ .min_width = 0,
+ .min_height = 0,
+ .max_width = 4096,
+ .max_height = 4096,
+ .rotation = 0,
+ .blend_mode = BIT(DRM_MODE_BLEND_PIXEL_NONE) |
+ BIT(DRM_MODE_BLEND_PREMULTI) |
+ BIT(DRM_MODE_BLEND_COVERAGE),
+ .color_encoding = BIT(DRM_COLOR_YCBCR_BT709) |
+ BIT(DRM_COLOR_YCBCR_BT2020),
+ .degamma_size = DEGAMMA_SIZE,
+ .min_scale = DRM_PLANE_HELPER_NO_SCALING,
+ .max_scale = DRM_PLANE_HELPER_NO_SCALING,
+ .zpos = 5,
+ .watermark = true,
+ .color_mgmt = true,
+ .roi = true,
+ },
+ {
+ .name = "Cursor",
+ .id = CURSOR_PLANE_0,
+ .type = DRM_PLANE_TYPE_CURSOR,
+ .num_formats = ARRAY_SIZE(cursor_formats),
+ .formats = cursor_formats,
+ .num_modifiers = 0,
+ .modifiers = NULL,
+ .min_width = 32,
+ .min_height = 32,
+ .max_width = 64,
+ .max_height = 64,
+ .rotation = 0,
+ .degamma_size = 0,
+ .min_scale = DRM_PLANE_HELPER_NO_SCALING,
+ .max_scale = DRM_PLANE_HELPER_NO_SCALING,
+ .zpos = 255,
+ .watermark = false,
+ .color_mgmt = false,
+ .roi = false,
+ },
+ {
+ .name = "Cursor_1",
+ .id = CURSOR_PLANE_1,
+ .type = DRM_PLANE_TYPE_CURSOR,
+ .num_formats = ARRAY_SIZE(cursor_formats),
+ .formats = cursor_formats,
+ .num_modifiers = 0,
+ .modifiers = NULL,
+ .min_width = 32,
+ .min_height = 32,
+ .max_width = 64,
+ .max_height = 64,
+ .rotation = 0,
+ .degamma_size = 0,
+ .min_scale = DRM_PLANE_HELPER_NO_SCALING,
+ .max_scale = DRM_PLANE_HELPER_NO_SCALING,
+ .zpos = 255,
+ .watermark = false,
+ .color_mgmt = false,
+ .roi = false,
+ },
+ },
+ {
+ /* DC_REV_1 */
+ {
+ .name = "Primary",
+ .id = PRIMARY_PLANE_0,
+ .type = DRM_PLANE_TYPE_PRIMARY,
+ .num_formats = ARRAY_SIZE(primary_overlay_format0),
+ .formats = primary_overlay_format0,
+ .num_modifiers = ARRAY_SIZE(format_modifier0),
+ .modifiers = format_modifier0,
+ .min_width = 0,
+ .min_height = 0,
+ .max_width = 4096,
+ .max_height = 4096,
+ .rotation = DRM_MODE_ROTATE_0 |
+ DRM_MODE_ROTATE_90 |
+ DRM_MODE_ROTATE_180 |
+ DRM_MODE_ROTATE_270 |
+ DRM_MODE_REFLECT_X |
+ DRM_MODE_REFLECT_Y,
+ .blend_mode = BIT(DRM_MODE_BLEND_PIXEL_NONE) |
+ BIT(DRM_MODE_BLEND_PREMULTI) |
+ BIT(DRM_MODE_BLEND_COVERAGE),
+ .color_encoding = BIT(DRM_COLOR_YCBCR_BT709) |
+ BIT(DRM_COLOR_YCBCR_BT2020),
+ .degamma_size = DEGAMMA_SIZE,
+ .min_scale = FRAC_16_16(1, 3),
+ .max_scale = FRAC_16_16(10, 1),
+ .zpos = 0,
+ .watermark = true,
+ .color_mgmt = true,
+ .roi = true,
+ },
+ {
+ .name = "Overlay",
+ .id = OVERLAY_PLANE_0,
+ .type = DRM_PLANE_TYPE_OVERLAY,
+ .num_formats = ARRAY_SIZE(primary_overlay_format0),
+ .formats = primary_overlay_format0,
+ .num_modifiers = ARRAY_SIZE(format_modifier0),
+ .modifiers = format_modifier0,
+ .min_width = 0,
+ .min_height = 0,
+ .max_width = 4096,
+ .max_height = 4096,
+ .rotation = DRM_MODE_ROTATE_0 |
+ DRM_MODE_ROTATE_90 |
+ DRM_MODE_ROTATE_180 |
+ DRM_MODE_ROTATE_270 |
+ DRM_MODE_REFLECT_X |
+ DRM_MODE_REFLECT_Y,
+ .blend_mode = BIT(DRM_MODE_BLEND_PIXEL_NONE) |
+ BIT(DRM_MODE_BLEND_PREMULTI) |
+ BIT(DRM_MODE_BLEND_COVERAGE),
+ .color_encoding = BIT(DRM_COLOR_YCBCR_BT709) |
+ BIT(DRM_COLOR_YCBCR_BT2020),
+ .degamma_size = DEGAMMA_SIZE,
+ .min_scale = FRAC_16_16(1, 3),
+ .max_scale = FRAC_16_16(10, 1),
+ .zpos = 1,
+ .watermark = true,
+ .color_mgmt = true,
+ .roi = true,
+ },
+ {
+ .name = "Primary_1",
+ .id = PRIMARY_PLANE_1,
+ .type = DRM_PLANE_TYPE_PRIMARY,
+ .num_formats = ARRAY_SIZE(primary_overlay_format0),
+ .formats = primary_overlay_format0,
+ .num_modifiers = ARRAY_SIZE(format_modifier0),
+ .modifiers = format_modifier0,
+ .min_width = 0,
+ .min_height = 0,
+ .max_width = 4096,
+ .max_height = 4096,
+ .rotation = DRM_MODE_ROTATE_0 |
+ DRM_MODE_ROTATE_90 |
+ DRM_MODE_ROTATE_180 |
+ DRM_MODE_ROTATE_270 |
+ DRM_MODE_REFLECT_X |
+ DRM_MODE_REFLECT_Y,
+ .blend_mode = BIT(DRM_MODE_BLEND_PIXEL_NONE) |
+ BIT(DRM_MODE_BLEND_PREMULTI) |
+ BIT(DRM_MODE_BLEND_COVERAGE),
+ .color_encoding = BIT(DRM_COLOR_YCBCR_BT709) |
+ BIT(DRM_COLOR_YCBCR_BT2020),
+ .degamma_size = DEGAMMA_SIZE,
+ .min_scale = FRAC_16_16(1, 3),
+ .max_scale = FRAC_16_16(10, 1),
+ .zpos = 2,
+ .watermark = true,
+ .color_mgmt = true,
+ .roi = true,
+ },
+ {
+ .name = "Overlay_2",
+ .id = OVERLAY_PLANE_2,
+ .type = DRM_PLANE_TYPE_OVERLAY,
+ .num_formats = ARRAY_SIZE(primary_overlay_format0),
+ .formats = primary_overlay_format0,
+ .num_modifiers = ARRAY_SIZE(format_modifier0),
+ .modifiers = format_modifier0,
+ .min_width = 0,
+ .min_height = 0,
+ .max_width = 4096,
+ .max_height = 4096,
+ .rotation = DRM_MODE_ROTATE_0 |
+ DRM_MODE_ROTATE_90 |
+ DRM_MODE_ROTATE_180 |
+ DRM_MODE_ROTATE_270 |
+ DRM_MODE_REFLECT_X |
+ DRM_MODE_REFLECT_Y,
+ .blend_mode = BIT(DRM_MODE_BLEND_PIXEL_NONE) |
+ BIT(DRM_MODE_BLEND_PREMULTI) |
+ BIT(DRM_MODE_BLEND_COVERAGE),
+ .color_encoding = BIT(DRM_COLOR_YCBCR_BT709) |
+ BIT(DRM_COLOR_YCBCR_BT2020),
+ .degamma_size = DEGAMMA_SIZE,
+ .min_scale = FRAC_16_16(1, 3),
+ .max_scale = FRAC_16_16(10, 1),
+ .zpos = 3,
+ .watermark = true,
+ .color_mgmt = true,
+ .roi = true,
+ },
+ {
+ .name = "Cursor",
+ .id = CURSOR_PLANE_0,
+ .type = DRM_PLANE_TYPE_CURSOR,
+ .num_formats = ARRAY_SIZE(cursor_formats),
+ .formats = cursor_formats,
+ .num_modifiers = 0,
+ .modifiers = NULL,
+ .min_width = 32,
+ .min_height = 32,
+ .max_width = 64,
+ .max_height = 64,
+ .rotation = 0,
+ .degamma_size = 0,
+ .min_scale = DRM_PLANE_HELPER_NO_SCALING,
+ .max_scale = DRM_PLANE_HELPER_NO_SCALING,
+ .zpos = 255,
+ .watermark = false,
+ .color_mgmt = false,
+ .roi = false,
+ },
+ {
+ .name = "Cursor_1",
+ .id = CURSOR_PLANE_1,
+ .type = DRM_PLANE_TYPE_CURSOR,
+ .num_formats = ARRAY_SIZE(cursor_formats),
+ .formats = cursor_formats,
+ .num_modifiers = 0,
+ .modifiers = NULL,
+ .min_width = 32,
+ .min_height = 32,
+ .max_width = 64,
+ .max_height = 64,
+ .rotation = 0,
+ .degamma_size = 0,
+ .min_scale = DRM_PLANE_HELPER_NO_SCALING,
+ .max_scale = DRM_PLANE_HELPER_NO_SCALING,
+ .zpos = 255,
+ .watermark = false,
+ .color_mgmt = false,
+ .roi = false,
+ },
+ },
+ {
+ /* DC_REV_2 */
+ {
+ .name = "Primary",
+ .id = PRIMARY_PLANE_0,
+ .type = DRM_PLANE_TYPE_PRIMARY,
+ .num_formats = ARRAY_SIZE(primary_overlay_format1),
+ .formats = primary_overlay_format1,
+ .num_modifiers = ARRAY_SIZE(format_modifier1),
+ .modifiers = format_modifier1,
+ .min_width = 0,
+ .min_height = 0,
+ .max_width = 4096,
+ .max_height = 4096,
+ .rotation = DRM_MODE_ROTATE_0 |
+ DRM_MODE_ROTATE_90 |
+ DRM_MODE_ROTATE_180 |
+ DRM_MODE_ROTATE_270 |
+ DRM_MODE_REFLECT_X |
+ DRM_MODE_REFLECT_Y,
+ .blend_mode = BIT(DRM_MODE_BLEND_PIXEL_NONE) |
+ BIT(DRM_MODE_BLEND_PREMULTI) |
+ BIT(DRM_MODE_BLEND_COVERAGE),
+ .color_encoding = BIT(DRM_COLOR_YCBCR_BT709) |
+ BIT(DRM_COLOR_YCBCR_BT2020),
+ .degamma_size = DEGAMMA_SIZE,
+ .min_scale = FRAC_16_16(1, 3),
+ .max_scale = FRAC_16_16(10, 1),
+ .zpos = 0,
+ .watermark = true,
+ .color_mgmt = true,
+ .roi = true,
+ },
+ {
+ .name = "Overlay",
+ .id = OVERLAY_PLANE_0,
+ .type = DRM_PLANE_TYPE_OVERLAY,
+ .num_formats = ARRAY_SIZE(primary_overlay_format1),
+ .formats = primary_overlay_format1,
+ .num_modifiers = ARRAY_SIZE(format_modifier1),
+ .modifiers = format_modifier1,
+ .min_width = 0,
+ .min_height = 0,
+ .max_width = 4096,
+ .max_height = 4096,
+ .rotation = DRM_MODE_ROTATE_0 |
+ DRM_MODE_ROTATE_90 |
+ DRM_MODE_ROTATE_180 |
+ DRM_MODE_ROTATE_270 |
+ DRM_MODE_REFLECT_X |
+ DRM_MODE_REFLECT_Y,
+ .blend_mode = BIT(DRM_MODE_BLEND_PIXEL_NONE) |
+ BIT(DRM_MODE_BLEND_PREMULTI) |
+ BIT(DRM_MODE_BLEND_COVERAGE),
+ .color_encoding = BIT(DRM_COLOR_YCBCR_BT709) |
+ BIT(DRM_COLOR_YCBCR_BT2020),
+ .degamma_size = DEGAMMA_SIZE,
+ .min_scale = FRAC_16_16(1, 3),
+ .max_scale = FRAC_16_16(10, 1),
+ .zpos = 1,
+ .watermark = true,
+ .color_mgmt = true,
+ .roi = true,
+ },
+ {
+ .name = "Overlay_1",
+ .id = OVERLAY_PLANE_1,
+ .type = DRM_PLANE_TYPE_OVERLAY,
+ .num_formats = ARRAY_SIZE(primary_overlay_format1),
+ .formats = primary_overlay_format1,
+ .num_modifiers = ARRAY_SIZE(secondary_format_modifiers),
+ .modifiers = secondary_format_modifiers,
+ .min_width = 0,
+ .min_height = 0,
+ .max_width = 4096,
+ .max_height = 4096,
+ .rotation = 0,
+ .blend_mode = BIT(DRM_MODE_BLEND_PIXEL_NONE) |
+ BIT(DRM_MODE_BLEND_PREMULTI) |
+ BIT(DRM_MODE_BLEND_COVERAGE),
+ .color_encoding = BIT(DRM_COLOR_YCBCR_BT709) |
+ BIT(DRM_COLOR_YCBCR_BT2020),
+ .degamma_size = DEGAMMA_SIZE,
+ .min_scale = DRM_PLANE_HELPER_NO_SCALING,
+ .max_scale = DRM_PLANE_HELPER_NO_SCALING,
+ .zpos = 2,
+ .watermark = true,
+ .color_mgmt = true,
+ .roi = true,
+ },
+ {
+ .name = "Primary_1",
+ .id = PRIMARY_PLANE_1,
+ .type = DRM_PLANE_TYPE_PRIMARY,
+ .num_formats = ARRAY_SIZE(primary_overlay_format1),
+ .formats = primary_overlay_format1,
+ .num_modifiers = ARRAY_SIZE(format_modifier1),
+ .modifiers = format_modifier1,
+ .min_width = 0,
+ .min_height = 0,
+ .max_width = 4096,
+ .max_height = 4096,
+ .rotation = DRM_MODE_ROTATE_0 |
+ DRM_MODE_ROTATE_90 |
+ DRM_MODE_ROTATE_180 |
+ DRM_MODE_ROTATE_270 |
+ DRM_MODE_REFLECT_X |
+ DRM_MODE_REFLECT_Y,
+ .blend_mode = BIT(DRM_MODE_BLEND_PIXEL_NONE) |
+ BIT(DRM_MODE_BLEND_PREMULTI) |
+ BIT(DRM_MODE_BLEND_COVERAGE),
+ .color_encoding = BIT(DRM_COLOR_YCBCR_BT709) |
+ BIT(DRM_COLOR_YCBCR_BT2020),
+ .degamma_size = DEGAMMA_SIZE,
+ .min_scale = FRAC_16_16(1, 3),
+ .max_scale = FRAC_16_16(10, 1),
+ .zpos = 3,
+ .watermark = true,
+ .color_mgmt = true,
+ .roi = true,
+ },
+ {
+ .name = "Overlay_2",
+ .id = OVERLAY_PLANE_2,
+ .type = DRM_PLANE_TYPE_OVERLAY,
+ .num_formats = ARRAY_SIZE(primary_overlay_format1),
+ .formats = primary_overlay_format1,
+ .num_modifiers = ARRAY_SIZE(format_modifier1),
+ .modifiers = format_modifier1,
+ .min_width = 0,
+ .min_height = 0,
+ .max_width = 4096,
+ .max_height = 4096,
+ .rotation = DRM_MODE_ROTATE_0 |
+ DRM_MODE_ROTATE_90 |
+ DRM_MODE_ROTATE_180 |
+ DRM_MODE_ROTATE_270 |
+ DRM_MODE_REFLECT_X |
+ DRM_MODE_REFLECT_Y,
+ .blend_mode = BIT(DRM_MODE_BLEND_PIXEL_NONE) |
+ BIT(DRM_MODE_BLEND_PREMULTI) |
+ BIT(DRM_MODE_BLEND_COVERAGE),
+ .color_encoding = BIT(DRM_COLOR_YCBCR_BT709) |
+ BIT(DRM_COLOR_YCBCR_BT2020),
+ .degamma_size = DEGAMMA_SIZE,
+ .min_scale = FRAC_16_16(1, 3),
+ .max_scale = FRAC_16_16(10, 1),
+ .zpos = 4,
+ .watermark = true,
+ .color_mgmt = true,
+ .roi = true,
+ },
+ {
+ .name = "Overlay_3",
+ .id = OVERLAY_PLANE_3,
+ .type = DRM_PLANE_TYPE_OVERLAY,
+ .num_formats = ARRAY_SIZE(primary_overlay_format1),
+ .formats = primary_overlay_format1,
+ .num_modifiers = ARRAY_SIZE(secondary_format_modifiers),
+ .modifiers = secondary_format_modifiers,
+ .min_width = 0,
+ .min_height = 0,
+ .max_width = 4096,
+ .max_height = 4096,
+ .rotation = 0,
+ .blend_mode = BIT(DRM_MODE_BLEND_PIXEL_NONE) |
+ BIT(DRM_MODE_BLEND_PREMULTI) |
+ BIT(DRM_MODE_BLEND_COVERAGE),
+ .color_encoding = BIT(DRM_COLOR_YCBCR_BT709) |
+ BIT(DRM_COLOR_YCBCR_BT2020),
+ .degamma_size = DEGAMMA_SIZE,
+ .min_scale = DRM_PLANE_HELPER_NO_SCALING,
+ .max_scale = DRM_PLANE_HELPER_NO_SCALING,
+ .zpos = 5,
+ .watermark = true,
+ .color_mgmt = true,
+ .roi = true,
+ },
+ {
+ .name = "Cursor",
+ .id = CURSOR_PLANE_0,
+ .type = DRM_PLANE_TYPE_CURSOR,
+ .num_formats = ARRAY_SIZE(cursor_formats),
+ .formats = cursor_formats,
+ .num_modifiers = 0,
+ .modifiers = NULL,
+ .min_width = 32,
+ .min_height = 32,
+ .max_width = 64,
+ .max_height = 64,
+ .rotation = 0,
+ .degamma_size = 0,
+ .min_scale = DRM_PLANE_HELPER_NO_SCALING,
+ .max_scale = DRM_PLANE_HELPER_NO_SCALING,
+ .zpos = 255,
+ .watermark = false,
+ .color_mgmt = false,
+ .roi = false,
+ },
+ {
+ .name = "Cursor_1",
+ .id = CURSOR_PLANE_1,
+ .type = DRM_PLANE_TYPE_CURSOR,
+ .num_formats = ARRAY_SIZE(cursor_formats),
+ .formats = cursor_formats,
+ .num_modifiers = 0,
+ .modifiers = NULL,
+ .min_width = 32,
+ .min_height = 32,
+ .max_width = 64,
+ .max_height = 64,
+ .rotation = 0,
+ .degamma_size = 0,
+ .min_scale = DRM_PLANE_HELPER_NO_SCALING,
+ .max_scale = DRM_PLANE_HELPER_NO_SCALING,
+ .zpos = 255,
+ .watermark = false,
+ .color_mgmt = false,
+ .roi = false,
+ },
+ },
+};
+
+static const struct vs_dc_info dc_info[] = {
+ {
+ /* DC_REV_0 */
+ .name = "DC8200",
+ .panel_num = 2,
+ .plane_num = 8,
+ .planes = dc_hw_planes[DC_REV_0],
+ .layer_num = 6,
+ .max_bpc = 10,
+ .color_formats = DRM_COLOR_FORMAT_RGB444 |
+ DRM_COLOR_FORMAT_YCRCB444 |
+ DRM_COLOR_FORMAT_YCRCB422 |
+ DRM_COLOR_FORMAT_YCRCB420,
+ .gamma_size = GAMMA_EX_SIZE,
+ .gamma_bits = 12,
+ .pitch_alignment = 128,
+ .pipe_sync = false,
+ .mmu_prefetch = false,
+ .background = true,
+ .panel_sync = true,
+ .cap_dec = true,
+ },
+ {
+ /* DC_REV_1 */
+ .name = "DC8200",
+ .panel_num = 2,
+ .plane_num = 6,
+ .planes = dc_hw_planes[DC_REV_1],
+ .layer_num = 4,
+ .max_bpc = 10,
+ .color_formats = DRM_COLOR_FORMAT_RGB444 |
+ DRM_COLOR_FORMAT_YCRCB444 |
+ DRM_COLOR_FORMAT_YCRCB422 |
+ DRM_COLOR_FORMAT_YCRCB420,
+ .gamma_size = GAMMA_EX_SIZE,
+ .gamma_bits = 12,
+ .pitch_alignment = 128,
+ .pipe_sync = false,
+ .mmu_prefetch = false,
+ .background = true,
+ .panel_sync = true,
+ .cap_dec = true,
+ },
+ {
+ /* DC_REV_2 */
+ .name = "DC8200",
+ .panel_num = 2,
+ .plane_num = 8,
+ .planes = dc_hw_planes[DC_REV_2],
+ .layer_num = 6,
+ .max_bpc = 10,
+ .color_formats = DRM_COLOR_FORMAT_RGB444 |
+ DRM_COLOR_FORMAT_YCRCB444 |
+ DRM_COLOR_FORMAT_YCRCB422 |
+ DRM_COLOR_FORMAT_YCRCB420,
+ .gamma_size = GAMMA_EX_SIZE,
+ .gamma_bits = 12,
+ .pitch_alignment = 128,
+ .pipe_sync = false,
+ .mmu_prefetch = false,
+ .background = true,
+ .panel_sync = true,
+ .cap_dec = false,
+ },
+};
+
+static const struct dc_hw_funcs hw_func;
+
+static inline u32 hi_read(struct dc_hw *hw, u32 reg)
+{
+ return readl(hw->hi_base + reg);
+}
+
+static inline void hi_write(struct dc_hw *hw, u32 reg, u32 value)
+{
+ writel(value, hw->hi_base + reg);
+}
+
+static inline void dc_write(struct dc_hw *hw, u32 reg, u32 value)
+{
+ writel(value, hw->reg_base + reg - DC_REG_BASE);
+}
+
+static inline u32 dc_read(struct dc_hw *hw, u32 reg)
+{
+ u32 value = readl(hw->reg_base + reg - DC_REG_BASE);
+
+ return value;
+}
+
+static inline void dc_set_clear(struct dc_hw *hw, u32 reg, u32 set, u32 clear)
+{
+ u32 value = dc_read(hw, reg);
+
+ value &= ~clear;
+ value |= set;
+ dc_write(hw, reg, value);
+}
+
+static void load_default_filter(struct dc_hw *hw,
+ const struct dc_hw_plane_reg *reg, u32 offset)
+{
+ u8 i;
+
+ dc_write(hw, reg->scale_config + offset, 0x33);
+ dc_write(hw, reg->init_offset + offset, 0x80008000);
+ dc_write(hw, reg->h_filter_coef_index + offset, 0x00);
+ for (i = 0; i < H_COEF_SIZE; i++)
+ dc_write(hw, reg->h_filter_coef_data + offset, horKernel[i]);
+
+ dc_write(hw, reg->v_filter_coef_index + offset, 0x00);
+ for (i = 0; i < V_COEF_SIZE; i++)
+ dc_write(hw, reg->v_filter_coef_data + offset, verKernel[i]);
+}
+
+static void load_rgb_to_rgb(struct dc_hw *hw, const struct dc_hw_plane_reg *reg,
+ u32 offset, u16 *table)
+{
+ dc_write(hw, reg->RGBToRGBCoef0 + offset, table[0] | (table[1] << 16));
+ dc_write(hw, reg->RGBToRGBCoef1 + offset, table[2] | (table[3] << 16));
+ dc_write(hw, reg->RGBToRGBCoef2 + offset, table[4] | (table[5] << 16));
+ dc_write(hw, reg->RGBToRGBCoef3 + offset, table[6] | (table[7] << 16));
+ dc_write(hw, reg->RGBToRGBCoef4 + offset, table[8]);
+}
+
+static void load_yuv_to_rgb(struct dc_hw *hw, const struct dc_hw_plane_reg *reg,
+ u32 offset, s32 *table)
+{
+ dc_write(hw, reg->YUVToRGBCoef0 + offset,
+ (0xFFFF & table[0]) | (table[1] << 16));
+ dc_write(hw, reg->YUVToRGBCoef1 + offset,
+ (0xFFFF & table[2]) | (table[3] << 16));
+ dc_write(hw, reg->YUVToRGBCoef2 + offset,
+ (0xFFFF & table[4]) | (table[5] << 16));
+ dc_write(hw, reg->YUVToRGBCoef3 + offset,
+ (0xFFFF & table[6]) | (table[7] << 16));
+ dc_write(hw, reg->YUVToRGBCoef4 + offset, table[8]);
+ dc_write(hw, reg->YUVToRGBCoefD0 + offset, table[9]);
+ dc_write(hw, reg->YUVToRGBCoefD1 + offset, table[10]);
+ dc_write(hw, reg->YUVToRGBCoefD2 + offset, table[11]);
+ dc_write(hw, reg->YClampBound + offset, table[12] | (table[13] << 16));
+ dc_write(hw, reg->UVClampBound + offset, table[14] | (table[15] << 16));
+}
+
+static void load_rgb_to_yuv(struct dc_hw *hw, u32 offset, s16 *table)
+{
+ dc_write(hw, DC_DISPLAY_RGBTOYUV_COEF0 + offset,
+ table[0] | (table[1] << 16));
+ dc_write(hw, DC_DISPLAY_RGBTOYUV_COEF1 + offset,
+ table[2] | (table[3] << 16));
+ dc_write(hw, DC_DISPLAY_RGBTOYUV_COEF2 + offset,
+ table[4] | (table[5] << 16));
+ dc_write(hw, DC_DISPLAY_RGBTOYUV_COEF3 + offset,
+ table[6] | (table[7] << 16));
+ dc_write(hw, DC_DISPLAY_RGBTOYUV_COEF4 + offset, table[8]);
+ dc_write(hw, DC_DISPLAY_RGBTOYUV_COEFD0 + offset, table[9]);
+ dc_write(hw, DC_DISPLAY_RGBTOYUV_COEFD1 + offset, table[10]);
+ dc_write(hw, DC_DISPLAY_RGBTOYUV_COEFD2 + offset, table[11]);
+}
+
+static bool is_rgb(enum dc_hw_color_format format)
+{
+ switch (format) {
+ case FORMAT_X4R4G4B4:
+ case FORMAT_A4R4G4B4:
+ case FORMAT_X1R5G5B5:
+ case FORMAT_A1R5G5B5:
+ case FORMAT_R5G6B5:
+ case FORMAT_X8R8G8B8:
+ case FORMAT_A8R8G8B8:
+ case FORMAT_A2R10G10B10:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static void load_degamma_table(struct dc_hw *hw,
+ const struct dc_hw_plane_reg *reg,
+ u32 offset, u16 *table)
+{
+ u16 i;
+ u32 value;
+
+ dc_write(hw, reg->degamma_index + offset, 0);
+
+ for (i = 0; i < DEGAMMA_SIZE; i++) {
+ value = table[i] | (table[i] << 16);
+ dc_write(hw, reg->degamma_data + offset, value);
+ dc_write(hw, reg->degamma_ex_data + offset, table[i]);
+ }
+}
+
+static u32 get_addr_offset(u32 id)
+{
+ u32 offset = 0;
+
+ switch(id) {
+ case PRIMARY_PLANE_1:
+ case OVERLAY_PLANE_1:
+ offset = 0x04;
+ break;
+ case OVERLAY_PLANE_2:
+ offset = 0x08;
+ break;
+ case OVERLAY_PLANE_3:
+ offset = 0x0C;
+ break;
+ default:
+ break;
+ }
+
+ return offset;
+}
+
+int dc_hw_init(struct dc_hw *hw)
+{
+ u8 i, id, panel_num, layer_num;
+ u32 offset;
+ u32 revision = hi_read(hw, DC_HW_REVISION);
+ u32 cid = hi_read(hw, DC_HW_CHIP_CID);
+ const struct dc_hw_plane_reg *reg;
+
+ switch (revision) {
+ case 0x5720:
+ hw->rev = DC_REV_0;
+ break;
+ case 0x5721:
+ switch (cid) {
+ case 0x30B:
+ hw->rev = DC_REV_1;
+ break;
+ case 0x310:
+ hw->rev = DC_REV_2;
+ break;
+ default:
+ hw->rev = DC_REV_0;
+ break;
+ }
+ break;
+ default:
+ return -ENXIO;
+ }
+
+ hw->info = (struct vs_dc_info *)&dc_info[hw->rev];
+ hw->func = (struct dc_hw_funcs *)&hw_func;
+
+ layer_num = hw->info->layer_num;
+ for (i = 0; i < layer_num; i++) {
+ id = hw->info->planes[i].id;
+ offset = get_addr_offset(id);
+ if (id == PRIMARY_PLANE_0 || id == PRIMARY_PLANE_1)
+ reg = &dc_plane_reg[0];
+ else
+ reg = &dc_plane_reg[1];
+
+ load_default_filter(hw, reg, offset);
+ load_rgb_to_rgb(hw, reg, offset, RGB2RGB);
+
+ }
+
+ panel_num = hw->info->panel_num;
+ for (i = 0; i < panel_num; i++) {
+ offset = i << 2;
+
+ load_rgb_to_yuv(hw, offset, RGB2YUV);
+ dc_write(hw, DC_DISPLAY_PANEL_CONFIG + offset, 0x111);
+
+ offset = i ? DC_CURSOR_OFFSET : 0;
+ dc_write(hw, DC_CURSOR_BACKGROUND + offset, 0x00FFFFFF);
+ dc_write(hw, DC_CURSOR_FOREGROUND + offset, 0x00AAAAAA);
+
+ }
+
+ return 0;
+}
+
+void dc_hw_deinit(struct dc_hw *hw)
+{
+ /* Nothing to do */
+}
+
+void dc_hw_update_plane(struct dc_hw *hw, u8 id,
+ struct dc_hw_fb *fb, struct dc_hw_scale *scale,
+ struct dc_hw_position *pos, struct dc_hw_blend *blend)
+{
+ struct dc_hw_plane *plane = &hw->plane[id];
+
+ if (plane) {
+ if (fb) {
+ if (fb->enable == false)
+ plane->fb.enable = false;
+ else
+ memcpy(&plane->fb, fb,
+ sizeof(*fb) - sizeof(fb->dirty));
+ plane->fb.dirty = true;
+ }
+ if (scale) {
+ memcpy(&plane->scale, scale,
+ sizeof(*scale) - sizeof(scale->dirty));
+ plane->scale.dirty = true;
+ }
+ if (pos) {
+ memcpy(&plane->pos, pos,
+ sizeof(*pos) - sizeof(pos->dirty));
+ plane->pos.dirty = true;
+ }
+ if (blend) {
+ memcpy(&plane->blend, blend,
+ sizeof(*blend) - sizeof(blend->dirty));
+ plane->blend.dirty = true;
+ }
+ }
+}
+
+void dc_hw_update_degamma(struct dc_hw *hw, u8 id, u32 mode)
+{
+ struct dc_hw_plane *plane = &hw->plane[id];
+
+ if (plane) {
+ if (hw->info->planes[id].degamma_size) {
+ plane->degamma.mode = mode;
+ plane->degamma.dirty = true;
+ } else {
+ plane->degamma.dirty = false;
+ }
+ }
+}
+
+void dc_hw_update_roi(struct dc_hw *hw, u8 id, struct dc_hw_roi *roi)
+{
+ struct dc_hw_plane *plane = &hw->plane[id];
+
+ if (plane) {
+ memcpy(&plane->roi, roi, sizeof(*roi) - sizeof(roi->dirty));
+ plane->roi.dirty = true;
+ }
+}
+
+void dc_hw_update_colorkey(struct dc_hw *hw, u8 id,
+ struct dc_hw_colorkey *colorkey)
+{
+ struct dc_hw_plane *plane = &hw->plane[id];
+
+ if (plane) {
+ memcpy(&plane->colorkey, colorkey,
+ sizeof(*colorkey) - sizeof(colorkey->dirty));
+ plane->colorkey.dirty = true;
+ }
+}
+
+void dc_hw_update_qos(struct dc_hw *hw, struct dc_hw_qos *qos)
+{
+ memcpy(&hw->qos, qos, sizeof(*qos) - sizeof(qos->dirty));
+ hw->qos.dirty = true;
+}
+
+void dc_hw_update_cursor(struct dc_hw *hw, u8 id, struct dc_hw_cursor *cursor)
+{
+ memcpy(&hw->cursor[id], cursor, sizeof(*cursor) - sizeof(cursor->dirty));
+ hw->cursor[id].dirty = true;
+}
+
+void dc_hw_update_gamma(struct dc_hw *hw, u8 id, u16 index,
+ u16 r, u16 g, u16 b)
+{
+ if (index >= hw->info->gamma_size)
+ return;
+
+ hw->gamma[id].gamma[index][0] = r;
+ hw->gamma[id].gamma[index][1] = g;
+ hw->gamma[id].gamma[index][2] = b;
+ hw->gamma[id].dirty = true;
+}
+
+void dc_hw_enable_gamma(struct dc_hw *hw, u8 id, bool enable)
+{
+ hw->gamma[id].enable = enable;
+ hw->gamma[id].dirty = true;
+}
+
+void dc_hw_enable_dump(struct dc_hw *hw, u32 addr, u32 pitch)
+{
+ dc_write(hw, 0x14F0, addr);
+ dc_write(hw, 0x14E8, addr);
+ dc_write(hw, 0x1500, pitch);
+ dc_write(hw, 0x14F8, 0x30000);
+}
+
+void dc_hw_disable_dump(struct dc_hw *hw)
+{
+ dc_write(hw, 0x14F8, 0x00);
+}
+
+void dc_hw_setup_display(struct dc_hw *hw, struct dc_hw_display *display)
+{
+ u8 id = display->id;
+
+ memcpy(&hw->display[id], display, sizeof(*display));
+
+ hw->func->display(hw, display);
+}
+
+void dc_hw_enable_interrupt(struct dc_hw *hw, bool enable)
+{
+ if (enable)
+ hi_write(hw, AQ_INTR_ENBL, 0xFFFFFFFF);
+ else
+ hi_write(hw, AQ_INTR_ENBL, 0);
+}
+
+u32 dc_hw_get_interrupt(struct dc_hw *hw)
+{
+ return hi_read(hw, AQ_INTR_ACKNOWLEDGE);
+}
+
+bool dc_hw_check_underflow(struct dc_hw *hw)
+{
+ return dc_read(hw, DC_FRAMEBUFFER_CONFIG) & BIT(5);
+}
+
+void dc_hw_enable_shadow_register(struct dc_hw *hw, bool enable)
+{
+ u32 i, offset;
+ u8 id, layer_num = hw->info->layer_num;
+ u8 panel_num = hw->info->panel_num;
+
+ for (i = 0; i< layer_num; i++) {
+ id = hw->info->planes[i].id;
+ offset = get_addr_offset(id);
+ if (enable) {
+ if (id == PRIMARY_PLANE_0 || id == PRIMARY_PLANE_1)
+ dc_set_clear(hw, DC_FRAMEBUFFER_CONFIG_EX + offset, BIT(12), 0);
+ else
+ dc_set_clear(hw, DC_OVERLAY_CONFIG + offset, BIT(31), 0);
+ } else {
+ if (id == PRIMARY_PLANE_0 || id == PRIMARY_PLANE_1)
+ dc_set_clear(hw, DC_FRAMEBUFFER_CONFIG_EX + offset, 0, BIT(12));
+ else
+ dc_set_clear(hw, DC_OVERLAY_CONFIG + offset, 0, BIT(31));
+ }
+ }
+
+ for (i = 0; i< panel_num; i++) {
+ offset = i << 2;
+ if (enable)
+ dc_set_clear(hw, DC_DISPLAY_PANEL_CONFIG_EX + offset, 0, BIT(0));
+ else
+ dc_set_clear(hw, DC_DISPLAY_PANEL_CONFIG_EX + offset, BIT(0), 0);
+ }
+}
+
+void dc_hw_set_out(struct dc_hw *hw, enum dc_hw_out out, u8 id)
+{
+ if (out <= OUT_DP)
+ hw->out[id] = out;
+}
+
+static void gamma_ex_commit(struct dc_hw *hw)
+{
+ u8 panel_num = hw->info->panel_num;
+ u16 i, j;
+ u32 value;
+
+ for (j = 0; j < panel_num; j++) {
+ if (hw->gamma[j].dirty) {
+ if (hw->gamma[j].enable) {
+ dc_write(hw, DC_DISPLAY_GAMMA_EX_INDEX + (j << 2), 0x00);
+ for (i = 0; i < GAMMA_EX_SIZE; i++) {
+ value = hw->gamma[j].gamma[i][2] |
+ (hw->gamma[j].gamma[i][1] << 12);
+ dc_write(hw, DC_DISPLAY_GAMMA_EX_DATA + (j << 2), value);
+ dc_write(hw, DC_DISPLAY_GAMMA_EX_ONE_DATA + (j << 2),
+ hw->gamma[j].gamma[i][0]);
+ }
+ dc_set_clear(hw, DC_DISPLAY_PANEL_CONFIG + (j << 2),
+ BIT(13), 0);
+ } else {
+ dc_set_clear(hw, DC_DISPLAY_PANEL_CONFIG + (j << 2),
+ 0, BIT(13));
+ }
+ hw->gamma[j].dirty = false;
+ }
+ }
+}
+
+static void plane_commit(struct dc_hw *hw)
+{
+ struct dc_hw_plane *plane;
+ const struct dc_hw_plane_reg *reg;
+ bool primary = false;
+ u8 id, layer_num = hw->info->layer_num;
+ u32 i, offset;
+
+ for (i = 0; i < layer_num; i++) {
+ plane = &hw->plane[i];
+ id = hw->info->planes[i].id;
+ offset = get_addr_offset(id);
+ if (id == PRIMARY_PLANE_0 || id == PRIMARY_PLANE_1) {
+ reg = &dc_plane_reg[0];
+ primary = true;
+ } else {
+ reg = &dc_plane_reg[1];
+ primary = false;
+ }
+
+ if (plane->fb.dirty) {
+ if (plane->fb.enable) {
+ dc_write(hw, reg->y_address + offset,
+ plane->fb.y_address);
+ dc_write(hw, reg->u_address + offset,
+ plane->fb.u_address);
+ dc_write(hw, reg->v_address + offset,
+ plane->fb.v_address);
+ dc_write(hw, reg->y_stride + offset,
+ plane->fb.y_stride);
+ dc_write(hw, reg->u_stride + offset,
+ plane->fb.u_stride);
+ dc_write(hw, reg->v_stride + offset,
+ plane->fb.v_stride);
+ dc_write(hw, reg->size + offset,
+ plane->fb.width |
+ (plane->fb.height << 15));
+ dc_write(hw, reg->water_mark + offset,
+ plane->fb.water_mark);
+
+ if (plane->fb.clear_enable)
+ dc_write(hw, reg->clear_value + offset,
+ plane->fb.clear_value);
+ }
+
+ if (primary) {
+ dc_set_clear(hw, DC_FRAMEBUFFER_CONFIG + offset,
+ (plane->fb.format << 26) |
+ (plane->fb.uv_swizzle << 25) |
+ (plane->fb.swizzle << 23) |
+ (plane->fb.tile_mode << 17) |
+ (plane->fb.yuv_color_space << 14) |
+ (plane->fb.rotation << 11) |
+ (plane->fb.clear_enable << 8),
+ (0x1F << 26) |
+ BIT(25) |
+ (0x03 << 23) |
+ (0x1F << 17) |
+ (0x07 << 14) |
+ (0x07 << 11) |
+ BIT(8));
+ dc_set_clear(hw, DC_FRAMEBUFFER_CONFIG_EX + offset,
+ (plane->fb.dec_enable << 1) |
+ (plane->fb.enable << 13) |
+ (plane->fb.zpos << 16) |
+ (plane->fb.display_id << 19),
+ BIT(1) | BIT(13) |(0x07 << 16) | BIT(19));
+ } else {
+ dc_set_clear(hw, DC_OVERLAY_CONFIG + offset,
+ (plane->fb.dec_enable << 27) |
+ (plane->fb.clear_enable << 25) |
+ (plane->fb.enable << 24) |
+ (plane->fb.format << 16) |
+ (plane->fb.uv_swizzle << 15) |
+ (plane->fb.swizzle << 13) |
+ (plane->fb.tile_mode << 8) |
+ (plane->fb.yuv_color_space << 5) |
+ (plane->fb.rotation << 2),
+ BIT(27) |
+ BIT(25) |
+ BIT(24) |
+ (0x1F << 16) |
+ BIT(15) |
+ (0x03 << 13) |
+ (0x1F << 8) |
+ (0x07 << 5) |
+ (0x07 << 2));
+ dc_set_clear(hw, DC_OVERLAY_CONFIG_EX + offset,
+ plane->fb.zpos | (plane->fb.display_id << 3),
+ 0x07| BIT(3));
+ }
+ plane->fb.dirty = false;
+ }
+
+ if (plane->scale.dirty) {
+ if (plane->scale.enable) {
+ dc_write(hw, reg->scale_factor_x + offset,
+ plane->scale.scale_factor_x);
+ dc_write(hw, reg->scale_factor_y + offset,
+ plane->scale.scale_factor_y);
+ if (primary)
+ dc_set_clear(hw,
+ DC_FRAMEBUFFER_CONFIG + offset,
+ BIT(22), 0);
+ else
+ dc_set_clear(hw,
+ DC_OVERLAY_SCALE_CONFIG + offset,
+ BIT(8), 0);
+ } else {
+ if (primary)
+ dc_set_clear(hw,
+ DC_FRAMEBUFFER_CONFIG + offset,
+ 0, BIT(22));
+ else
+ dc_set_clear(hw,
+ DC_OVERLAY_SCALE_CONFIG + offset,
+ 0, BIT(8));
+ }
+ plane->scale.dirty = false;
+ }
+
+ if (plane->pos.dirty) {
+ dc_write(hw, reg->top_left + offset,
+ plane->pos.start_x |
+ (plane->pos.start_y << 15));
+ dc_write(hw, reg->bottom_right + offset,
+ plane->pos.end_x |
+ (plane->pos.end_y << 15));
+ plane->pos.dirty = false;
+ }
+
+ if (plane->blend.dirty) {
+ dc_write(hw, reg->src_global_color + offset,
+ plane->blend.alpha << 24);
+ dc_write(hw, reg->dst_global_color + offset,
+ plane->blend.alpha << 24);
+ switch (plane->blend.blend_mode) {
+ case BLEND_PREMULTI:
+ dc_write(hw, reg->blend_config + offset, 0x3450);
+ break;
+ case BLEND_COVERAGE:
+ dc_write(hw, reg->blend_config + offset, 0x3950);
+ break;
+ case BLEND_PIXEL_NONE:
+ dc_write(hw, reg->blend_config + offset, 0x3548);
+ break;
+ default:
+ break;
+ }
+ plane->blend.dirty = false;
+ }
+
+ if (plane->colorkey.dirty) {
+ dc_write(hw, reg->color_key + offset, plane->colorkey.colorkey);
+ dc_write(hw, reg->color_key_high + offset,
+ plane->colorkey.colorkey_high);
+
+ if (primary)
+ dc_set_clear(hw, DC_FRAMEBUFFER_CONFIG + offset,
+ plane->colorkey.transparency << 9, 0x03 << 9);
+ else
+ dc_set_clear(hw, DC_OVERLAY_CONFIG + offset,
+ plane->colorkey.transparency, 0x03);
+
+ plane->colorkey.dirty = false;
+ }
+
+ if (plane->roi.dirty) {
+ if (plane->roi.enable) {
+ dc_write(hw, reg->roi_origin + offset,
+ plane->roi.x | (plane->roi.y << 16));
+ dc_write(hw, reg->roi_size + offset,
+ plane->roi.width | (plane->roi.height << 16));
+ if (primary)
+ dc_set_clear(hw, DC_FRAMEBUFFER_CONFIG_EX + offset,
+ BIT(0), 0);
+ else
+ dc_set_clear(hw, DC_OVERLAY_CONFIG + offset,
+ BIT(22), 0);
+ } else {
+ if (primary)
+ dc_set_clear(hw, DC_FRAMEBUFFER_CONFIG_EX + offset,
+ 0, BIT(0));
+ else
+ dc_set_clear(hw, DC_OVERLAY_CONFIG + offset,
+ 0, BIT(22));
+ }
+ plane->roi.dirty = false;
+ }
+ }
+}
+
+static void plane_ex_commit(struct dc_hw *hw)
+{
+ struct dc_hw_plane *plane;
+ const struct dc_hw_plane_reg *reg;
+ bool primary = false;
+ u8 id, layer_num = hw->info->layer_num;
+ u32 i, offset;
+
+ for (i = 0; i < layer_num; i++) {
+ plane = &hw->plane[i];
+ id = hw->info->planes[i].id;
+ offset = get_addr_offset(id);
+ if (id == PRIMARY_PLANE_0 || id == PRIMARY_PLANE_1) {
+ reg = &dc_plane_reg[0];
+ primary = true;
+ } else {
+ reg = &dc_plane_reg[1];
+ primary = false;
+ }
+
+ if (plane->fb.dirty) {
+ if (is_rgb(plane->fb.format)) {
+ if (primary)
+ dc_set_clear(hw,
+ DC_FRAMEBUFFER_CONFIG_EX + offset,
+ BIT(6), BIT(8));
+ else
+ dc_set_clear(hw,
+ DC_OVERLAY_CONFIG + offset,
+ BIT(29), BIT(30));
+ } else {
+ if (primary)
+ dc_set_clear(hw,
+ DC_FRAMEBUFFER_CONFIG_EX + offset,
+ BIT(8), BIT(6));
+ else
+ dc_set_clear(hw,
+ DC_OVERLAY_CONFIG + offset,
+ BIT(30), BIT(29));
+ switch (plane->fb.yuv_color_space) {
+ case COLOR_SPACE_601:
+ load_yuv_to_rgb(hw, reg, offset, YUV601_2RGB);
+ break;
+ case COLOR_SPACE_709:
+ load_yuv_to_rgb(hw, reg, offset, YUV709_2RGB);
+ break;
+ case COLOR_SPACE_2020:
+ load_yuv_to_rgb(hw, reg, offset, YUV2020_2RGB);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ if (plane->degamma.dirty) {
+ switch (plane->degamma.mode) {
+ case VS_DEGAMMA_DISABLE:
+ if (primary)
+ dc_set_clear(hw,
+ DC_FRAMEBUFFER_CONFIG_EX + offset,
+ 0, BIT(5));
+ else
+ dc_set_clear(hw,
+ DC_OVERLAY_CONFIG + offset,
+ 0, BIT(28));
+ break;
+ case VS_DEGAMMA_BT709:
+ load_degamma_table(hw, reg, offset, DEGAMMA_709);
+ if (primary)
+ dc_set_clear(hw,
+ DC_FRAMEBUFFER_CONFIG_EX + offset,
+ BIT(5), 0);
+ else
+ dc_set_clear(hw,
+ DC_OVERLAY_CONFIG + offset,
+ BIT(28), 0);
+ break;
+ case VS_DEGAMMA_BT2020:
+ load_degamma_table(hw, reg, offset, DEGAMMA_2020);
+ if (primary)
+ dc_set_clear(hw,
+ DC_FRAMEBUFFER_CONFIG_EX + offset,
+ BIT(5), 0);
+ else
+ dc_set_clear(hw,
+ DC_OVERLAY_CONFIG + offset,
+ BIT(28), 0);
+ break;
+ default:
+ break;
+ }
+ plane->degamma.dirty = false;
+ }
+ }
+ plane_commit(hw);
+}
+
+static void setup_display(struct dc_hw *hw, struct dc_hw_display *display)
+{
+ u8 id = display->id;
+ u32 dpi_cfg, offset = id << 2;
+ if (hw->display[id].enable) {
+ switch (display->bus_format) {
+ case MEDIA_BUS_FMT_RGB565_1X16:
+ dpi_cfg = 0;
+ break;
+ case MEDIA_BUS_FMT_RGB666_1X18:
+ dpi_cfg = 3;
+ break;
+ case MEDIA_BUS_FMT_RGB666_1X24_CPADHI:
+ dpi_cfg = 4;
+ break;
+ case MEDIA_BUS_FMT_RGB888_1X24:
+ dpi_cfg = 5;
+ break;
+ case MEDIA_BUS_FMT_RGB101010_1X30:
+ dpi_cfg = 6;
+ break;
+ default:
+ dpi_cfg = 5;
+ break;
+ }
+ dc_write(hw, DC_DISPLAY_DPI_CONFIG + offset, dpi_cfg);
+
+ if (id == 0)
+ dc_set_clear(hw, DC_DISPLAY_PANEL_START, 0, BIT(0) | BIT(2));
+ else
+ dc_set_clear(hw, DC_DISPLAY_PANEL_START, 0, BIT(1) | BIT(2));
+
+ dc_write(hw, DC_DISPLAY_H + offset, hw->display[id].h_active |
+ (hw->display[id].h_total << 16));
+ dc_write(hw, DC_DISPLAY_H_SYNC + offset,
+ hw->display[id].h_sync_start |
+ (hw->display[id].h_sync_end << 15) |
+ (hw->display[id].h_sync_polarity ? 0 : BIT(31)) |
+ BIT(30));
+ dc_write(hw, DC_DISPLAY_V + offset, hw->display[id].v_active |
+ (hw->display[id].v_total << 16));
+ dc_write(hw, DC_DISPLAY_V_SYNC + offset,
+ hw->display[id].v_sync_start |
+ (hw->display[id].v_sync_end << 15) |
+ (hw->display[id].v_sync_polarity ? 0 : BIT(31)) |
+ BIT(30));
+
+ if (hw->info->pipe_sync) {
+ switch (display->sync_mode) {
+ case VS_SINGLE_DC:
+ dc_set_clear(hw, DC_FRAMEBUFFER_CONFIG_EX,
+ 0, BIT(3) | BIT(4));
+ break;
+ case VS_MULTI_DC_PRIMARY:
+ dc_set_clear(hw, DC_FRAMEBUFFER_CONFIG_EX,
+ BIT(3) | BIT(4), 0);
+ break;
+ case VS_MULTI_DC_SECONDARY:
+ dc_set_clear(hw, DC_FRAMEBUFFER_CONFIG_EX,
+ BIT(3), BIT(4));
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (hw->info->background)
+ dc_write(hw, DC_FRAMEBUFFER_BG_COLOR + offset,
+ hw->display[id].bg_color);
+
+ if (hw->display[id].dither_enable) {
+ dc_write(hw, DC_DISPLAY_DITHER_TABLE_LOW + offset,
+ DC_DISPLAY_DITHERTABLE_LOW);
+ dc_write(hw, DC_DISPLAY_DITHER_TABLE_HIGH + offset,
+ DC_DISPLAY_DITHERTABLE_HIGH);
+ dc_write(hw, DC_DISPLAY_DITHER_CONFIG + offset, BIT(31));
+ } else {
+ dc_write(hw, DC_DISPLAY_DITHER_CONFIG + offset, 0);
+ }
+
+ dc_set_clear(hw, DC_DISPLAY_PANEL_CONFIG + offset, BIT(12), 0);
+ if (hw->display[id].sync_enable)
+ dc_set_clear(hw, DC_DISPLAY_PANEL_START, BIT(2) | BIT(3), 0);
+ else if (id == 0)
+ dc_set_clear(hw, DC_DISPLAY_PANEL_START, BIT(0), BIT(3));
+ else
+ dc_set_clear(hw, DC_DISPLAY_PANEL_START, BIT(1), BIT(3));
+ } else {
+ dc_set_clear(hw, DC_DISPLAY_PANEL_CONFIG + offset, 0, BIT(12));
+ if (id == 0)
+ dc_set_clear(hw, DC_DISPLAY_PANEL_START, 0, BIT(0) | BIT(2));
+ else
+ dc_set_clear(hw, DC_DISPLAY_PANEL_START, 0, BIT(1) | BIT(2));
+ }
+}
+
+static void setup_display_ex(struct dc_hw *hw, struct dc_hw_display *display)
+{
+ u8 id = display->id;
+ u32 dp_cfg, offset = id << 2;
+ bool is_yuv = false;
+
+ if (hw->display[id].enable && hw->out[id] == OUT_DP) {
+ switch (display->bus_format) {
+ case MEDIA_BUS_FMT_RGB565_1X16:
+ dp_cfg = 0;
+ break;
+ case MEDIA_BUS_FMT_RGB666_1X18:
+ dp_cfg = 1;
+ break;
+ case MEDIA_BUS_FMT_RGB888_1X24:
+ dp_cfg = 2;
+ break;
+ case MEDIA_BUS_FMT_RGB101010_1X30:
+ dp_cfg = 3;
+ break;
+ case MEDIA_BUS_FMT_UYVY8_1X16:
+ dp_cfg = 2 << 4;
+ is_yuv = true;
+ break;
+ case MEDIA_BUS_FMT_YUV8_1X24:
+ dp_cfg = 4 << 4;
+ is_yuv = true;
+ break;
+ case MEDIA_BUS_FMT_UYVY10_1X20:
+ dp_cfg = 8 << 4;
+ is_yuv = true;
+ break;
+ case MEDIA_BUS_FMT_YUV10_1X30:
+ dp_cfg = 10 << 4;
+ is_yuv = true;
+ break;
+ case MEDIA_BUS_FMT_UYYVYY8_0_5X24:
+ dp_cfg = 12 << 4;
+ is_yuv = true;
+ break;
+ case MEDIA_BUS_FMT_UYYVYY10_0_5X30:
+ dp_cfg = 13 << 4;
+ is_yuv = true;
+ break;
+ default:
+ dp_cfg = 2;
+ break;
+ }
+ if (is_yuv)
+ dc_set_clear(hw, DC_DISPLAY_PANEL_CONFIG + offset, BIT(16), 0);
+ else
+ dc_set_clear(hw, DC_DISPLAY_PANEL_CONFIG + offset, 0, BIT(16));
+ dc_write(hw, DC_DISPLAY_DP_CONFIG + offset, dp_cfg | BIT(3));
+ }
+
+ if (hw->out[id] == OUT_DPI)
+ dc_set_clear(hw, DC_DISPLAY_DP_CONFIG + offset, 0, BIT(3));
+
+ setup_display(hw, display);
+}
+
+static const struct dc_hw_funcs hw_func= {
+ .gamma = &gamma_ex_commit,
+ .plane = &plane_ex_commit,
+ .display = setup_display_ex,
+};
+
+void dc_hw_commit(struct dc_hw *hw)
+{
+ u32 i, offset = 0;
+ u8 plane_num = hw->info->plane_num;
+ u8 layer_num = hw->info->layer_num;
+ u8 cursor_num = plane_num - layer_num;
+
+ hw->func->gamma(hw);
+ hw->func->plane(hw);
+
+ for (i = 0; i < cursor_num; i++) {
+ if (hw->cursor[i].dirty) {
+ offset = hw->cursor[i].display_id ? DC_CURSOR_OFFSET : 0;
+ if (hw->cursor[i].enable) {
+ dc_write(hw, DC_CURSOR_ADDRESS + offset,
+ hw->cursor[i].address);
+ dc_write(hw, DC_CURSOR_LOCATION + offset, hw->cursor[i].x |
+ (hw->cursor[i].y << 16));
+ dc_set_clear(hw, DC_CURSOR_CONFIG + offset,
+ (hw->cursor[i].hot_x << 16) |
+ (hw->cursor[i].hot_y << 8) |
+ (hw->cursor[i].size << 5) |
+ BIT(3) | BIT(2) | 0x02,
+ (0xFF << 16) |
+ (0xFF << 8) |
+ (0x07 << 5) | 0x1F);
+ } else {
+ dc_set_clear(hw, DC_CURSOR_CONFIG + offset, BIT(3), 0x03);
+ }
+ hw->cursor[i].dirty = false;
+ }
+ }
+
+ if (hw->qos.dirty) {
+ dc_set_clear(hw, DC_QOS_CONFIG, (hw->qos.high_value << 4) |
+ hw->qos.low_value, 0xFF);
+ hw->qos.dirty = false;
+ }
+}
+
+#ifdef CONFIG_VERISILICON_DEC
+void dc_hw_dec_init(struct dc_hw *hw)
+{
+ u32 config = 0;
+
+ config = DEC_CONTROL_RESET & (~COMPRESSION_DISABLE);
+ dc_write(hw, DEC_CONTROL, config | FLUSH_ENABLE);
+
+ config = DEC_CONTROL_EX2_RESET &
+ (~TILE_STATUS_READ_ID_MASK) &
+ (~TILE_STATUS_READ_ID_H_MASK) &
+ (~DISABLE_HW_DEC_FLUSH);
+ dc_write(hw, DEC_CONTROL_EX2,
+ config | (TILE_STATUS_READ_ID_H << 22) |
+ TILE_STATUS_READ_ID);
+
+ config = DEC_CONTROL_EX_RESET &
+ (~WRITE_MISS_POLICY_MASK) &
+ (~READ_MISS_POLICY_MASK);
+ dc_write(hw, DEC_CONTROL_EX, config | (WRITE_MISS_POLICY1 << 19));
+}
+
+void dc_hw_dec_stream_set(struct dc_hw *hw,u32 main_base_addr,
+ u32 ts_base_addr, u8 tile_mode, u8 align_mode,
+ u8 format, u8 depth, u8 stream_id)
+{
+ u32 offset = stream_id << 2;
+
+ dc_set_clear(hw, DEC_READ_CONFIG + offset,
+ (tile_mode << 25) |
+ (align_mode << 16) |
+ (format << 3) |
+ COMPRESSION_EN,
+ TILE_MODE_MASK |
+ COMPRESSION_ALIGN_MODE_MASK |
+ COMPRESSION_FORMAT_MASK);
+
+ dc_set_clear(hw, DEC_READ_EX_CONFIG + offset,
+ (depth << 16), BIT_DEPTH_MASK);
+
+ dc_write(hw, DEC_READ_BUFFER_BASE + offset, main_base_addr);
+ dc_write(hw, DEC_READ_BUFFER_END + offset, ts_base_addr - 128);
+ dc_write(hw, DEC_READ_CACHE_BASE + offset, ts_base_addr);
+}
+
+void dc_hw_dec_stream_disable(struct dc_hw *hw, u8 stream_id)
+{
+ u32 offset = stream_id << 2;
+
+ dc_write(hw, DEC_READ_CONFIG + offset, DEC_READ_CONFIG_RESET);
+ dc_write(hw, DEC_READ_BUFFER_BASE + offset, 0xFFFFFFFF);
+ dc_write(hw, DEC_READ_BUFFER_END + offset, 0xFFFFFFFF);
+}
+#endif
+
+#ifdef CONFIG_VERISILICON_MMU
+static u32 mmu_read(struct dc_hw *hw, u32 reg)
+{
+ return readl(hw->mmu_base + reg - mmu_reg_base);
+}
+
+static void mmu_write(struct dc_hw *hw, u32 reg, u32 value)
+{
+ writel(value, hw->mmu_base + reg - mmu_reg_base);
+}
+
+static void mmu_set_clear(struct dc_hw *hw, u32 reg, u32 set, u32 clear)
+{
+ u32 value = mmu_read(hw, reg);
+
+ value &= ~clear;
+ value |= set;
+ mmu_write(hw, reg, value);
+}
+
+int dc_hw_mmu_init(struct dc_hw *hw, dc_mmu_pt mmu)
+{
+ const struct dc_hw_mmu_reg *reg;
+ u32 mtlb = 0, ext_mtlb = 0;
+ u32 safe_addr = 0, ext_safe_addr = 0;
+ u32 config = 0;
+
+ reg = &dc_mmu_reg;
+
+ mtlb = (u32)(mmu->mtlb_physical & 0xFFFFFFFF);
+ ext_mtlb = (u32)(mmu->mtlb_physical >> 32);
+
+ /* more than 40bit physical address */
+ if (ext_mtlb & 0xFFFFFF00) {
+ pr_err("Mtlb address out of range.\n");
+ return -EFAULT;
+ }
+
+ config = (ext_mtlb << 20) | (mtlb >> 12);
+ if (mmu->mode == MMU_MODE_1K)
+ mmu_set_clear(hw, reg->context_pd_entry,
+ (config << 4) |BIT(0),
+ (0xFFFFFFF << 4) | (0x07));
+ else
+ mmu_set_clear(hw, reg->context_pd_entry,
+ (config << 4),
+ (0xFFFFFFF << 4) | (0x07));
+
+ safe_addr = (u32)(mmu->safe_page_physical & 0xFFFFFFFF);
+ ext_safe_addr = (u32)(mmu->safe_page_physical >> 32);
+
+ if ((safe_addr & 0x3F) || (ext_safe_addr & 0xFFFFFF00)) {
+ pr_err("Invalid safe_address.\n");
+ return -EFAULT;
+ }
+
+ mmu_write(hw, reg->table_array_size, 1);
+ mmu_write(hw, reg->safe_secure, safe_addr);
+ mmu_write(hw, reg->safe_non_secure, safe_addr);
+
+ mmu_set_clear(hw, reg->safe_ex,
+ (ext_safe_addr << 16) |ext_safe_addr,
+ BIT(31) |(0xFF << 16) | BIT(15) | 0xFF);
+
+ /* mmu configuration for ree driver */
+ mmu_write(hw, reg->mmu_control, BIT(5) | BIT(0));
+
+ mmu_write(hw, SE_MMU_REG_INTR_ENBL, 0xFFFFFFFF);
+
+ return 0;
+}
+
+void dc_hw_enable_mmu_prefetch(struct dc_hw *hw, bool enable)
+{
+ if (!hw->info->mmu_prefetch)
+ return;
+
+ if (enable)
+ dc_write(hw, DC_MMU_PREFETCH, BIT(0));
+ else
+ dc_write(hw, DC_MMU_PREFETCH, 0);
+}
+
+void dc_hw_mmu_flush(struct dc_hw *hw)
+{
+ const struct dc_hw_mmu_reg *reg = &dc_mmu_reg;
+ u32 value = mmu_read(hw, reg->mmu_config);
+
+ mmu_write(hw, reg->mmu_config, value | BIT(4));
+ mmu_write(hw, reg->mmu_config, value);
+}
+#endif
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2020 VeriSilicon Holdings Co., Ltd.
+ */
+
+#ifndef __VS_DC_HW_H__
+#define __VS_DC_HW_H__
+
+#include <linux/version.h>
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 5, 0)
+#include <drm/drmP.h>
+#endif
+
+#ifdef CONFIG_VERISILICON_MMU
+#include "vs_dc_mmu.h"
+#endif
+
+#define AQ_INTR_ACKNOWLEDGE 0x0010
+#define AQ_INTR_ENBL 0x0014
+#define DC_HW_REVISION 0x0024
+#define DC_HW_CHIP_CID 0x0030
+
+#define DC_REG_BASE 0x0800
+#define DC_REG_RANGE 0x2000
+#define DC_SEC_REG_OFFSET 0x100000
+
+#define DC_FRAMEBUFFER_CONFIG 0x1518
+#define DC_FRAMEBUFFER_CONFIG_EX 0x1CC0
+#define DC_FRAMEBUFFER_SCALE_CONFIG 0x1520
+#define DC_FRAMEBUFFER_TOP_LEFT 0x24D8
+#define DC_FRAMEBUFFER_BOTTOM_RIGHT 0x24E0
+#define DC_FRAMEBUFFER_ADDRESS 0x1400
+#define DC_FRAMEBUFFER_U_ADDRESS 0x1530
+#define DC_FRAMEBUFFER_V_ADDRESS 0x1538
+#define DC_FRAMEBUFFER_STRIDE 0x1408
+#define DC_FRAMEBUFFER_U_STRIDE 0x1800
+#define DC_FRAMEBUFFER_V_STRIDE 0x1808
+#define DC_FRAMEBUFFER_SIZE 0x1810
+#define DC_FRAMEBUFFER_SCALE_FACTOR_X 0x1828
+#define DC_FRAMEBUFFER_SCALE_FACTOR_Y 0x1830
+#define DC_FRAMEBUFFER_H_FILTER_COEF_INDEX 0x1838
+#define DC_FRAMEBUFFER_H_FILTER_COEF_DATA 0x1A00
+#define DC_FRAMEBUFFER_V_FILTER_COEF_INDEX 0x1A08
+#define DC_FRAMEBUFFER_V_FILTER_COEF_DATA 0x1A10
+#define DC_FRAMEBUFFER_INIT_OFFSET 0x1A20
+#define DC_FRAMEBUFFER_COLOR_KEY 0x1508
+#define DC_FRAMEBUFFER_COLOR_KEY_HIGH 0x1510
+#define DC_FRAMEBUFFER_CLEAR_VALUE 0x1A18
+#define DC_FRAMEBUFFER_COLOR_TABLE_INDEX 0x1818
+#define DC_FRAMEBUFFER_COLOR_TABLE_DATA 0x1820
+#define DC_FRAMEBUFFER_BG_COLOR 0x1528
+#define DC_FRAMEBUFFER_ROI_ORIGIN 0x1CB0
+#define DC_FRAMEBUFFER_ROI_SIZE 0x1CB8
+#define DC_FRAMEBUFFER_WATER_MARK 0x1CE8
+#define DC_FRAMEBUFFER_DEGAMMA_INDEX 0x1D88
+#define DC_FRAMEBUFFER_DEGAMMA_DATA 0x1D90
+#define DC_FRAMEBUFFER_DEGAMMA_EX_DATA 0x1D98
+#define DC_FRAMEBUFFER_YUVTORGB_COEF0 0x1DA0
+#define DC_FRAMEBUFFER_YUVTORGB_COEF1 0x1DA8
+#define DC_FRAMEBUFFER_YUVTORGB_COEF2 0x1DB0
+#define DC_FRAMEBUFFER_YUVTORGB_COEF3 0x1DB8
+#define DC_FRAMEBUFFER_YUVTORGB_COEF4 0x1E00
+#define DC_FRAMEBUFFER_YUVTORGB_COEFD0 0x1E08
+#define DC_FRAMEBUFFER_YUVTORGB_COEFD1 0x1E10
+#define DC_FRAMEBUFFER_YUVTORGB_COEFD2 0x1E18
+#define DC_FRAMEBUFFER_Y_CLAMP_BOUND 0x1E88
+#define DC_FRAMEBUFFER_UV_CLAMP_BOUND 0x1E90
+#define DC_FRAMEBUFFER_RGBTORGB_COEF0 0x1E20
+#define DC_FRAMEBUFFER_RGBTORGB_COEF1 0x1E28
+#define DC_FRAMEBUFFER_RGBTORGB_COEF2 0x1E30
+#define DC_FRAMEBUFFER_RGBTORGB_COEF3 0x1E38
+#define DC_FRAMEBUFFER_RGBTORGB_COEF4 0x1E40
+#define DC_FRAMEBUFFER_BLEND_CONFIG 0x2510
+#define DC_FRAMEBUFFER_SRC_GLOBAL_COLOR 0x2500
+#define DC_FRAMEBUFFER_DST_GLOBAL_COLOR 0x2508
+
+#define DC_OVERLAY_CONFIG 0x1540
+#define DC_OVERLAY_CONFIG_EX 0x2540
+#define DC_OVERLAY_SCALE_CONFIG 0x1C00
+#define DC_OVERLAY_BLEND_CONFIG 0x1580
+#define DC_OVERLAY_TOP_LEFT 0x1640
+#define DC_OVERLAY_BOTTOM_RIGHT 0x1680
+#define DC_OVERLAY_ADDRESS 0x15C0
+#define DC_OVERLAY_U_ADDRESS 0x1840
+#define DC_OVERLAY_V_ADDRESS 0x1880
+#define DC_OVERLAY_STRIDE 0x1600
+#define DC_OVERLAY_U_STRIDE 0x18C0
+#define DC_OVERLAY_V_STRIDE 0x1900
+#define DC_OVERLAY_SIZE 0x17C0
+#define DC_OVERLAY_SCALE_FACTOR_X 0x1A40
+#define DC_OVERLAY_SCALE_FACTOR_Y 0x1A80
+#define DC_OVERLAY_H_FILTER_COEF_INDEX 0x1AC0
+#define DC_OVERLAY_H_FILTER_COEF_DATA 0x1B00
+#define DC_OVERLAY_V_FILTER_COEF_INDEX 0x1B40
+#define DC_OVERLAY_V_FILTER_COEF_DATA 0x1B80
+#define DC_OVERLAY_INIT_OFFSET 0x1BC0
+#define DC_OVERLAY_COLOR_KEY 0x1740
+#define DC_OVERLAY_COLOR_KEY_HIGH 0x1780
+#define DC_OVERLAY_CLEAR_VALUE 0x1940
+#define DC_OVERLAY_COLOR_TABLE_INDEX 0x1980
+#define DC_OVERLAY_COLOR_TABLE_DATA 0x19C0
+#define DC_OVERLAY_SRC_GLOBAL_COLOR 0x16C0
+#define DC_OVERLAY_DST_GLOBAL_COLOR 0x1700
+#define DC_OVERLAY_ROI_ORIGIN 0x1D00
+#define DC_OVERLAY_ROI_SIZE 0x1D40
+#define DC_OVERLAY_WATER_MARK 0x1DC0
+#define DC_OVERLAY_DEGAMMA_INDEX 0x2200
+#define DC_OVERLAY_DEGAMMA_DATA 0x2240
+#define DC_OVERLAY_DEGAMMA_EX_DATA 0x2280
+#define DC_OVERLAY_YUVTORGB_COEF0 0x1EC0
+#define DC_OVERLAY_YUVTORGB_COEF1 0x1F00
+#define DC_OVERLAY_YUVTORGB_COEF2 0x1F40
+#define DC_OVERLAY_YUVTORGB_COEF3 0x1F80
+#define DC_OVERLAY_YUVTORGB_COEF4 0x1FC0
+#define DC_OVERLAY_YUVTORGB_COEFD0 0x2000
+#define DC_OVERLAY_YUVTORGB_COEFD1 0x2040
+#define DC_OVERLAY_YUVTORGB_COEFD2 0x2080
+#define DC_OVERLAY_Y_CLAMP_BOUND 0x22C0
+#define DC_OVERLAY_UV_CLAMP_BOUND 0x2300
+#define DC_OVERLAY_RGBTORGB_COEF0 0x20C0
+#define DC_OVERLAY_RGBTORGB_COEF1 0x2100
+#define DC_OVERLAY_RGBTORGB_COEF2 0x2140
+#define DC_OVERLAY_RGBTORGB_COEF3 0x2180
+#define DC_OVERLAY_RGBTORGB_COEF4 0x21C0
+
+#define DC_CURSOR_CONFIG 0x1468
+#define DC_CURSOR_ADDRESS 0x146C
+#define DC_CURSOR_LOCATION 0x1470
+#define DC_CURSOR_BACKGROUND 0x1474
+#define DC_CURSOR_FOREGROUND 0x1478
+#define DC_CURSOR_CLK_GATING 0x1484
+#define DC_CURSOR_CONFIG_EX 0x24E8
+#define DC_CURSOR_OFFSET 0x1080
+
+#define DC_DISPLAY_DITHER_CONFIG 0x1410
+#define DC_DISPLAY_PANEL_CONFIG 0x1418
+#define DC_DISPLAY_PANEL_CONFIG_EX 0x2518
+#define DC_DISPLAY_DITHER_TABLE_LOW 0x1420
+#define DC_DISPLAY_DITHER_TABLE_HIGH 0x1428
+#define DC_DISPLAY_H 0x1430
+#define DC_DISPLAY_H_SYNC 0x1438
+#define DC_DISPLAY_V 0x1440
+#define DC_DISPLAY_V_SYNC 0x1448
+#define DC_DISPLAY_CURRENT_LOCATION 0x1450
+#define DC_DISPLAY_GAMMA_INDEX 0x1458
+#define DC_DISPLAY_GAMMA_DATA 0x1460
+#define DC_DISPLAY_INT 0x147C
+#define DC_DISPLAY_INT_ENABLE 0x1480
+#define DC_DISPLAY_DBI_CONFIG 0x1488
+#define DC_DISPLAY_GENERAL_CONFIG 0x14B0
+#define DC_DISPLAY_DPI_CONFIG 0x14B8
+#define DC_DISPLAY_PANEL_START 0x1CCC
+#define DC_DISPLAY_DEBUG_COUNTER_SELECT 0x14D0
+#define DC_DISPLAY_DEBUG_COUNTER_VALUE 0x14D8
+#define DC_DISPLAY_DP_CONFIG 0x1CD0
+#define DC_DISPLAY_GAMMA_EX_INDEX 0x1CF0
+#define DC_DISPLAY_GAMMA_EX_DATA 0x1CF8
+#define DC_DISPLAY_GAMMA_EX_ONE_DATA 0x1D80
+#define DC_DISPLAY_RGBTOYUV_COEF0 0x1E48
+#define DC_DISPLAY_RGBTOYUV_COEF1 0x1E50
+#define DC_DISPLAY_RGBTOYUV_COEF2 0x1E58
+#define DC_DISPLAY_RGBTOYUV_COEF3 0x1E60
+#define DC_DISPLAY_RGBTOYUV_COEF4 0x1E68
+#define DC_DISPLAY_RGBTOYUV_COEFD0 0x1E70
+#define DC_DISPLAY_RGBTOYUV_COEFD1 0x1E78
+#define DC_DISPLAY_RGBTOYUV_COEFD2 0x1E80
+
+#define DC_CLK_GATTING 0x1A28
+#define DC_QOS_CONFIG 0x1A38
+
+#define DC_TRANSPARENCY_OPAQUE 0x00
+#define DC_TRANSPARENCY_KEY 0x02
+#define DC_DISPLAY_DITHERTABLE_LOW 0x7B48F3C0
+#define DC_DISPLAY_DITHERTABLE_HIGH 0x596AD1E2
+
+#define GAMMA_SIZE 256
+#define GAMMA_EX_SIZE 300
+#define DEGAMMA_SIZE 260
+
+#define RGB_TO_RGB_TABLE_SIZE 9
+#define YUV_TO_RGB_TABLE_SIZE 16
+#define RGB_TO_YUV_TABLE_SIZE 12
+
+#ifdef CONFIG_VERISILICON_DEC
+/* DEC400 register */
+#define DEC_CONTROL 0x0800
+#define DEC_CONTROL_EX 0x0804
+#define DEC_CONTROL_EX2 0x0808
+
+#define DEC_READ_CONFIG 0x0880
+#define DEC_READ_EX_CONFIG 0x0900
+#define DEC_READ_BUFFER_BASE 0x0A80
+#define DEC_READ_BUFFER_END 0x0B80
+#define DEC_READ_CACHE_BASE 0x1080
+
+#define DEC_CONTROL_RESET 0x0301018A
+#define DEC_CONTROL_EX_RESET 0x00080000
+#define DEC_CONTROL_EX2_RESET 0x103FC810
+#define DEC_READ_CONFIG_RESET 0x00020000
+#endif
+
+#ifdef CONFIG_VERISILICON_MMU
+#define DC_MMU_PREFETCH 0x1E98
+
+#define MMU_REG_BASE 0x0180
+#define MMU_REG_RANGE 0x700
+
+#define MMU_REG_CONFIG 0x0184
+#define MMU_REG_CONTROL 0x0388
+#define MMU_REG_TABLE_ARRAY_SIZE 0x0394
+#define MMU_REG_SAFE_NON_SECURE 0x0398
+#define MMU_REG_SAFE_SECURE 0x039C
+#define MMU_REG_SAFE_EXT_ADDRESS 0x03A0
+#define MMU_REG_CONTEXT_PD 0x03B4
+
+#define DEC_REG_CONTROL 0x0800
+#define DEC_REG_CONTROL_VALUE 0x02010188
+
+#define SE_MMU_REG_BASE 0x10010
+#define SE_MMU_REG_RANGE 0x60
+
+#define SE_MMU_REG_CONFIG 0x10010
+#define SE_MMU_REG_CONTROL 0x10024
+#define SE_MMU_REG_TABLE_ARRAY_SIZE 0x10030
+#define SE_MMU_REG_SAFE_NON_SECUR 0x10034
+#define SE_MMU_REG_SAFE_SECURE 0x10038
+#define SE_MMU_REG_SAFE_EXT_ADDRESS 0x1003C
+#define SE_MMU_REG_CONTEXT_PD 0x10040
+#define SE_MMU_REG_INTR_ENBL 0x10044
+#endif
+
+#define DC_LAYER_NUM 6
+#define DC_DISPLAY_NUM 2
+#define DC_CURSOR_NUM 2
+
+enum dc_chip_rev {
+ DC_REV_0, /* For HW_REV_5720;
+ * HW_REV_5721_311 */
+ DC_REV_1, /* For HW_REV_5721_30B */
+ DC_REV_2, /* For HW_REV_5721_310 */
+};
+
+enum dc_hw_plane_id {
+ PRIMARY_PLANE_0,
+ OVERLAY_PLANE_0,
+ OVERLAY_PLANE_1,
+ PRIMARY_PLANE_1,
+ OVERLAY_PLANE_2,
+ OVERLAY_PLANE_3,
+ CURSOR_PLANE_0,
+ CURSOR_PLANE_1,
+ PLANE_NUM
+};
+
+enum dc_hw_color_format {
+ FORMAT_X4R4G4B4,//0
+ FORMAT_A4R4G4B4,//1
+ FORMAT_X1R5G5B5,//2
+ FORMAT_A1R5G5B5,//3
+ FORMAT_R5G6B5,//4
+ FORMAT_X8R8G8B8,//5
+ FORMAT_A8R8G8B8,//6
+ FORMAT_YUY2,//7
+ FORMAT_UYVY,//8
+ FORMAT_INDEX8,//9
+ FORMAT_MONOCHROME,//10
+ FORMAT_YV12 = 0xf,
+ FORMAT_A8,//16
+ FORMAT_NV12,//17
+ FORMAT_NV16,//18
+ FORMAT_RG16,//19
+ FORMAT_R8,//20
+ FORMAT_NV12_10BIT,//21
+ FORMAT_A2R10G10B10,//22
+ FORMAT_NV16_10BIT,//23
+ FORMAT_INDEX1,//24
+ FORMAT_INDEX2,//25
+ FORMAT_INDEX4,//26
+ FORMAT_P010,//27
+ FORMAT_YUV444,//28
+ FORMAT_YUV444_10BIT,//29
+};
+
+enum dc_hw_yuv_color_space {
+ COLOR_SPACE_601 = 0,
+ COLOR_SPACE_709 = 1,
+ COLOR_SPACE_2020 = 3,
+};
+
+enum dc_hw_rotation {
+ ROT_0 = 0,
+ ROT_90 = 4,
+ ROT_180 = 5,
+ ROT_270 = 6,
+ FLIP_X = 1,
+ FLIP_Y = 2,
+ FLIP_XY = 3,
+};
+
+enum dc_hw_swizzle {
+ SWIZZLE_ARGB = 0,
+ SWIZZLE_RGBA,
+ SWIZZLE_ABGR,
+ SWIZZLE_BGRA,
+};
+
+enum dc_hw_out {
+ OUT_DPI,
+ OUT_DP,
+};
+
+enum dc_hw_cursor_size {
+ CURSOR_SIZE_32X32 = 0,
+ CURSOR_SIZE_64X64,
+};
+
+enum dc_hw_blend_mode {
+ /* out.rgb = plane_alpha * fg.rgb +
+ * (1 - (plane_alpha * fg.alpha)) * bg.rgb
+ */
+ BLEND_PREMULTI,
+ /* out.rgb = plane_alpha * fg.alpha * fg.rgb +
+ * (1 - (plane_alpha * fg.alpha)) * bg.rgb
+ */
+ BLEND_COVERAGE,
+ /* out.rgb = plane_alpha * fg.rgb +
+ * (1 - plane_alpha) * bg.rgb
+ */
+ BLEND_PIXEL_NONE,
+};
+
+struct dc_hw_plane_reg {
+ u32 y_address;
+ u32 u_address;
+ u32 v_address;
+ u32 y_stride;
+ u32 u_stride;
+ u32 v_stride;
+ u32 size;
+ u32 top_left;
+ u32 bottom_right;
+ u32 scale_factor_x;
+ u32 scale_factor_y;
+ u32 h_filter_coef_index;
+ u32 h_filter_coef_data;
+ u32 v_filter_coef_index;
+ u32 v_filter_coef_data;
+ u32 init_offset;
+ u32 color_key;
+ u32 color_key_high;
+ u32 clear_value;
+ u32 color_table_index;
+ u32 color_table_data;
+ u32 scale_config;
+ u32 water_mark;
+ u32 degamma_index;
+ u32 degamma_data;
+ u32 degamma_ex_data;
+ u32 src_global_color;
+ u32 dst_global_color;
+ u32 blend_config;
+ u32 roi_origin;
+ u32 roi_size;
+ u32 YUVToRGBCoef0;
+ u32 YUVToRGBCoef1;
+ u32 YUVToRGBCoef2;
+ u32 YUVToRGBCoef3;
+ u32 YUVToRGBCoef4;
+ u32 YUVToRGBCoefD0;
+ u32 YUVToRGBCoefD1;
+ u32 YUVToRGBCoefD2;
+ u32 YClampBound;
+ u32 UVClampBound;
+ u32 RGBToRGBCoef0;
+ u32 RGBToRGBCoef1;
+ u32 RGBToRGBCoef2;
+ u32 RGBToRGBCoef3;
+ u32 RGBToRGBCoef4;
+};
+
+#ifdef CONFIG_VERISILICON_MMU
+struct dc_hw_mmu_reg {
+ u32 mmu_config;
+ u32 mmu_control;
+ u32 table_array_size;
+ u32 safe_non_secure;
+ u32 safe_secure;
+ u32 safe_ex;
+ u32 context_pd_entry;
+};
+#endif
+
+struct dc_hw_fb {
+ u32 y_address;
+ u32 u_address;
+ u32 v_address;
+ u32 clear_value;
+ u32 water_mark;
+ u16 y_stride;
+ u16 u_stride;
+ u16 v_stride;
+ u16 width;
+ u16 height;
+ u8 format;
+ u8 tile_mode;
+ u8 rotation;
+ u8 yuv_color_space;
+ u8 swizzle;
+ u8 uv_swizzle;
+ u8 zpos;
+ u8 display_id;
+ bool clear_enable;
+ bool dec_enable;
+ bool enable;
+ bool dirty;
+};
+
+struct dc_hw_scale {
+ u32 scale_factor_x;
+ u32 scale_factor_y;
+ bool enable;
+ bool dirty;
+};
+
+struct dc_hw_position {
+ u16 start_x;
+ u16 start_y;
+ u16 end_x;
+ u16 end_y;
+ bool dirty;
+};
+
+struct dc_hw_blend {
+ u8 alpha;
+ u8 blend_mode;
+ bool dirty;
+};
+
+struct dc_hw_colorkey {
+ u32 colorkey;
+ u32 colorkey_high;
+ u8 transparency;
+ bool dirty;
+};
+
+struct dc_hw_roi {
+ u16 x;
+ u16 y;
+ u16 width;
+ u16 height;
+ bool enable;
+ bool dirty;
+};
+
+struct dc_hw_cursor {
+ u32 address;
+ u16 x;
+ u16 y;
+ u16 hot_x;
+ u16 hot_y;
+ u8 size;
+ u8 display_id;
+ bool enable;
+ bool dirty;
+};
+
+struct dc_hw_display {
+ u32 bus_format;
+ u16 h_active;
+ u16 h_total;
+ u16 h_sync_start;
+ u16 h_sync_end;
+ u16 v_active;
+ u16 v_total;
+ u16 v_sync_start;
+ u16 v_sync_end;
+ u16 sync_mode;
+ u32 bg_color;
+ u8 id;
+ bool h_sync_polarity;
+ bool v_sync_polarity;
+ bool enable;
+ bool sync_enable;
+ bool dither_enable;
+};
+
+struct dc_hw_gamma {
+ u16 gamma[GAMMA_EX_SIZE][3];
+ bool enable;
+ bool dirty;
+};
+
+struct dc_hw_degamma {
+ u16 degamma[DEGAMMA_SIZE][3];
+ u32 mode;
+ bool dirty;
+};
+
+struct dc_hw_plane {
+ struct dc_hw_fb fb;
+ struct dc_hw_position pos;
+ struct dc_hw_scale scale;
+ struct dc_hw_blend blend;
+ struct dc_hw_roi roi;
+ struct dc_hw_colorkey colorkey;
+ struct dc_hw_degamma degamma;
+};
+
+struct dc_hw_qos {
+ u8 low_value;
+ u8 high_value;
+ bool dirty;
+};
+
+struct dc_hw_read {
+ u32 reg;
+ u32 value;
+};
+
+struct dc_hw;
+struct dc_hw_funcs {
+ void (*gamma)(struct dc_hw *hw);
+ void (*plane)(struct dc_hw *hw);
+ void (*display)(struct dc_hw *hw, struct dc_hw_display *display);
+};
+
+struct dc_hw {
+ enum dc_chip_rev rev;
+ enum dc_hw_out out[DC_DISPLAY_NUM];
+ void *hi_base;
+ void *reg_base;
+#ifdef CONFIG_VERISILICON_MMU
+ void *mmu_base;
+#endif
+ struct dc_hw_display display[DC_DISPLAY_NUM];
+ struct dc_hw_gamma gamma[DC_DISPLAY_NUM];
+ struct dc_hw_plane plane[DC_LAYER_NUM];
+ struct dc_hw_cursor cursor[DC_CURSOR_NUM];
+ struct dc_hw_qos qos;
+ struct dc_hw_funcs *func;
+ struct vs_dc_info *info;
+};
+
+int dc_hw_init(struct dc_hw *hw);
+void dc_hw_deinit(struct dc_hw *hw);
+void dc_hw_update_plane(struct dc_hw *hw, u8 id,
+ struct dc_hw_fb *fb, struct dc_hw_scale *scale,
+ struct dc_hw_position *pos, struct dc_hw_blend *blend);
+void dc_hw_update_degamma(struct dc_hw *hw, u8 id, u32 mode);
+void dc_hw_update_roi(struct dc_hw *hw, u8 id, struct dc_hw_roi *roi);
+void dc_hw_update_colorkey(struct dc_hw *hw, u8 id,
+ struct dc_hw_colorkey *colorkey);
+void dc_hw_update_qos(struct dc_hw *hw, struct dc_hw_qos *qos);
+void dc_hw_update_cursor(struct dc_hw *hw, u8 id, struct dc_hw_cursor *cursor);
+void dc_hw_update_gamma(struct dc_hw *hw, u8 id, u16 index,
+ u16 r, u16 g, u16 b);
+void dc_hw_enable_gamma(struct dc_hw *hw, u8 id, bool enable);
+void dc_hw_enable_dump(struct dc_hw *hw, u32 addr, u32 pitch);
+void dc_hw_disable_dump(struct dc_hw *hw);
+void dc_hw_setup_display(struct dc_hw *hw, struct dc_hw_display *display);
+void dc_hw_enable_interrupt(struct dc_hw *hw, bool enable);
+u32 dc_hw_get_interrupt(struct dc_hw *hw);
+bool dc_hw_check_underflow(struct dc_hw *hw);
+void dc_hw_enable_shadow_register(struct dc_hw *hw, bool enable);
+void dc_hw_set_out(struct dc_hw *hw, enum dc_hw_out out, u8 id);
+void dc_hw_commit(struct dc_hw *hw);
+#ifdef CONFIG_VERISILICON_DEC
+void dc_hw_dec_init(struct dc_hw *hw);
+void dc_hw_dec_stream_set(struct dc_hw *hw,u32 main_base_addr,
+ u32 ts_base_addr, u8 tile_mode, u8 align_mode,
+ u8 format, u8 depth, u8 stream_id);
+void dc_hw_dec_stream_disable(struct dc_hw *hw, u8 stream_id);
+#endif
+
+#ifdef CONFIG_VERISILICON_MMU
+int dc_hw_mmu_init(struct dc_hw *hw, dc_mmu_pt mmu);
+void dc_hw_enable_mmu_prefetch(struct dc_hw *hw, bool enable);
+#endif
+
+#endif /* __VS_DC_HW_H__ */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020 VeriSilicon Holdings Co., Ltd.
+ */
+
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/mman.h>
+#include <linux/pagemap.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+#include <linux/vmalloc.h>
+
+#include <asm/io.h>
+#include <asm/delay.h>
+
+#include "vs_dc_mmu.h"
+
+static bool mmu_construct = false;
+
+int _allocate_memory(u32 bytes, void **memory)
+{
+ void *mem = NULL;
+
+ if (bytes == 0 || memory == NULL) {
+ pr_err("%s has invalid arguments.\n", __FUNCTION__);
+ return -EINVAL;
+ }
+
+ if (bytes > PAGE_SIZE) {
+ mem = vmalloc(bytes);
+ }
+ else {
+ mem = kmalloc(bytes, GFP_KERNEL);
+ }
+
+ if (!mem) {
+ pr_err("%s out of memory.\n", __FUNCTION__);
+ return -ENOMEM;
+ }
+
+ memset((u8 *)mem, 0, bytes);
+ *memory = mem;
+
+ return 0;
+}
+
+static int _create_mutex(void **mutex)
+{
+ int ret =0;
+
+ if (mutex == NULL) {
+ return -EINVAL;
+ }
+
+ ret = _allocate_memory(sizeof(struct mutex), mutex);
+ if (ret)
+ return ret;
+
+ mutex_init(*(struct mutex **)mutex);
+
+ return 0;
+}
+
+static int _acquire_mutex(void *mutex, u32 timeout)
+{
+ if (mutex == NULL) {
+ pr_err("%s has invalid argument.\n", __FUNCTION__);
+ return -EINVAL;
+ }
+
+ if (timeout == DC_INFINITE) {
+ mutex_lock(mutex);
+ return 0;
+ }
+
+ for (;;) {
+ /* Try to acquire the mutex. */
+ if (mutex_trylock(mutex)) {
+ /* Success. */
+ return 0;
+ }
+
+ if (timeout-- == 0) {
+ break;
+ }
+
+ /* Wait for 1 millisecond. */
+ udelay(1000);
+ }
+
+ return -ETIMEDOUT;
+}
+
+static int _release_mutex(void *mutex)
+{
+ if (mutex == NULL) {
+ pr_err("%s has invalid argument.\n", __FUNCTION__);
+ return -EINVAL;
+ }
+
+ mutex_unlock(mutex);
+
+ return 0;
+}
+
+static u32 _mtlb_offset(u32 address)
+{
+ return (address & MMU_MTLB_MASK) >> MMU_MTLB_SHIFT;
+}
+
+static u32 _stlb_offset(u32 address)
+{
+ return (address & MMU_STLB_4K_MASK) >> MMU_STLB_4K_SHIFT;
+}
+
+static u32 _address_to_index(dc_mmu_pt mmu, u32 address)
+{
+ return _mtlb_offset(address) * MMU_STLB_4K_ENTRY_NUM + _stlb_offset(address);
+}
+
+static u32 _set_page(u32 page_address, u32 page_address_ext, bool writable)
+{
+ u32 entry = page_address
+ /* AddressExt */
+ | (page_address_ext << 4)
+ /* Ignore exception */
+ | (0 << 1)
+ /* Present */
+ | (1 << 0);
+
+ if (writable) {
+ /* writable */
+ entry |= (1 << 2);
+ }
+
+ return entry;
+}
+
+static void _write_page_entry(u32 *page_entry, u32 entry_value)
+{
+ *page_entry = entry_value;
+}
+
+static u32 _read_page_entry(u32 *page_entry)
+{
+ return *page_entry;
+}
+
+int _allocate_stlb(dc_mmu_stlb_pt *stlb)
+{
+ dc_mmu_stlb_pt stlb_t = NULL;
+ void *mem = NULL;
+
+ mem = kzalloc(sizeof(dc_mmu_stlb), GFP_KERNEL);
+ if (!mem)
+ return -ENOMEM;
+
+ stlb_t = (dc_mmu_stlb_pt)mem;
+
+ stlb_t->size = MMU_STLB_4K_SIZE;
+
+ *stlb = stlb_t;
+
+ return 0;
+}
+
+int _allocate_all_stlb(struct device *dev, dc_mmu_stlb_pt *stlb)
+{
+ dc_mmu_stlb_pt stlb_t = NULL;
+ void *mem = NULL;
+ void *cookie = NULL;
+ dma_addr_t dma_addr;
+ size_t size;
+
+ mem = kzalloc(sizeof(dc_mmu_stlb), GFP_KERNEL);
+ if (!mem)
+ return -ENOMEM;
+
+ stlb_t = (dc_mmu_stlb_pt)mem;
+
+ stlb_t->size = MMU_STLB_4K_SIZE * MMU_MTLB_ENTRY_NUM;
+ size = PAGE_ALIGN(stlb_t->size);
+
+ cookie = dma_alloc_wc(dev, size, &dma_addr, GFP_KERNEL);
+ if (!cookie) {
+ dev_err(dev, "Failed to alloc stlb buffer.\n");
+ return -ENOMEM;
+ }
+
+ stlb_t->logical = cookie;
+ stlb_t->physBase = (u64)dma_addr;
+ memset(stlb_t->logical, 0, size);
+
+ *stlb = stlb_t;
+
+ return 0;
+}
+
+int _setup_process_address_space(struct device *dev, dc_mmu_pt mmu)
+{
+ u32 *map = NULL;
+ u32 free, i;
+ u32 dynamic_mapping_entries, address;
+ dc_mmu_stlb_pt all_stlb;
+ int ret =0;
+
+ dynamic_mapping_entries = MMU_MTLB_ENTRY_NUM;
+ mmu->dynamic_mapping_start = 0;
+ mmu->page_table_size = dynamic_mapping_entries * MMU_STLB_4K_SIZE;
+
+ mmu->page_table_entries = mmu->page_table_size / sizeof(u32);
+
+ ret = _allocate_memory(mmu->page_table_size,
+ (void **)&mmu->map_logical);
+ if (ret) {
+ pr_err("Failed to alloc mmu map buffer.\n");
+ return ret;;
+ }
+
+ map = mmu->map_logical;
+
+ /* Initialize free area*/
+ free = mmu->page_table_entries;
+ _write_page_entry(map, (free << 8) | DC_MMU_FREE);
+ _write_page_entry(map + 1, ~0U);
+
+ mmu->heap_list = 0;
+ mmu->free_nodes = false;
+
+ ret = _allocate_all_stlb(dev, &all_stlb);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < dynamic_mapping_entries; i++) {
+ dc_mmu_stlb_pt stlb;
+ dc_mmu_stlb_pt *stlbs = (dc_mmu_stlb_pt *)mmu->stlbs;
+
+ ret = _allocate_stlb(&stlb);
+ if (ret)
+ return ret;
+
+ stlb->physBase = all_stlb->physBase + i * MMU_STLB_4K_SIZE;
+ stlb->logical = all_stlb->logical + i * MMU_STLB_4K_SIZE / sizeof(u32);
+
+ stlbs[i] = stlb;
+ }
+
+ address = (u32)all_stlb->physBase;
+
+ ret = _acquire_mutex(mmu->page_table_mutex, DC_INFINITE);
+ if (ret)
+ return ret;
+
+ for (i = mmu->dynamic_mapping_start;
+ i < mmu->dynamic_mapping_start + dynamic_mapping_entries;
+ i++) {
+ u32 mtlb_entry;
+
+ mtlb_entry = address
+ | MMU_MTLB_4K_PAGE
+ | MMU_MTLB_PRESENT;
+
+ address += MMU_STLB_4K_SIZE;
+
+ /* Insert Slave TLB address to Master TLB entry.*/
+ _write_page_entry(mmu->mtlb_logical + i, mtlb_entry);
+ }
+
+ _release_mutex(mmu->page_table_mutex);
+
+ return 0;
+}
+
+/* MMU Construct */
+int dc_mmu_construct(struct device *dev, dc_mmu_pt *mmu)
+{
+ dc_mmu_pt mmu_t = NULL;
+ void *mem = NULL;
+ void *cookie = NULL, *cookie_safe =NULL;
+ dma_addr_t dma_addr, dma_addr_safe;
+ u32 size = 0;
+ int ret = 0;
+
+ if (mmu_construct)
+ return 0;
+
+ mem = kzalloc(sizeof(dc_mmu), GFP_KERNEL);
+ if (!mem)
+ return -ENOMEM;
+
+ mmu_t = (dc_mmu_pt)mem;
+ mmu_t->mtlb_bytes = MMU_MTLB_SIZE;
+ size = PAGE_ALIGN(mmu_t->mtlb_bytes);
+
+ /* Allocate MTLB */
+ cookie = dma_alloc_wc(dev, size, &dma_addr, GFP_KERNEL);
+ if (!cookie) {
+ dev_err(dev, "Failed to alloc mtlb buffer.\n");
+ return -ENOMEM;
+ }
+
+ mmu_t->mtlb_logical = cookie;
+ mmu_t->mtlb_physical = (u64)dma_addr;
+ memset(mmu_t->mtlb_logical, 0, size);
+
+ size = MMU_MTLB_ENTRY_NUM * sizeof(dc_mmu_stlb_pt);
+
+ ret = _allocate_memory(size, &mmu_t->stlbs);
+ if (ret)
+ return ret;
+
+ ret = _create_mutex(&mmu_t->page_table_mutex);
+ if (ret)
+ return ret;
+
+ mmu_t->mode = MMU_MODE_1K;
+
+ ret = _setup_process_address_space(dev, mmu_t);
+ if (ret)
+ return ret;
+
+ /* Allocate safe page */
+ cookie_safe = dma_alloc_wc(dev, 4096, &dma_addr_safe, GFP_KERNEL);
+ if (!cookie_safe) {
+ dev_err(dev, "Failed to alloc safe page.\n");
+ return -ENOMEM;
+ }
+
+ mmu_t->safe_page_logical = cookie_safe;
+ mmu_t->safe_page_physical = (u64)dma_addr_safe;
+ memset(mmu_t->safe_page_logical, 0, size);
+
+ *mmu = mmu_t;
+ mmu_construct = true;
+
+ return 0;
+}
+
+int dc_mmu_get_page_entry(dc_mmu_pt mmu, u32 address, u32 **page_table)
+{
+ dc_mmu_stlb_pt stlb;
+ dc_mmu_stlb_pt *stlbs = (dc_mmu_stlb_pt *)mmu->stlbs;
+ u32 mtlb_offset = _mtlb_offset(address);
+ u32 stlb_offset = _stlb_offset(address);
+
+ stlb = stlbs[mtlb_offset - mmu->dynamic_mapping_start];
+ if (stlb == NULL) {
+ pr_err("BUG: invalid stlb, mmu=%p stlbs=%p mtlb_offset=0x%x %s(%d)\n",
+ mmu, stlbs ,mtlb_offset,__FUNCTION__,__LINE__);
+ return -ENXIO;
+ }
+
+ *page_table = &stlb->logical[stlb_offset];
+
+ return 0;
+}
+
+int _link(dc_mmu_pt mmu, u32 index, u32 node)
+{
+ if (index >= mmu->page_table_entries) {
+ mmu->heap_list = node;
+ }
+ else {
+ u32 *map = mmu->map_logical;
+
+ switch (DC_ENTRY_TYPE(_read_page_entry(&map[index]))) {
+ case DC_MMU_SINGLE:
+ /* Previous is a single node, link to it*/
+ _write_page_entry(&map[index], (node << 8) | DC_MMU_SINGLE);
+ break;
+ case DC_MMU_FREE:
+ /* Link to FREE TYPE node */
+ _write_page_entry(&map[index + 1], node);
+ break;
+ default:
+ pr_err("MMU table corrupted at index %u!", index);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+int _add_free(dc_mmu_pt mmu, u32 index, u32 node, u32 count)
+{
+ u32 *map = mmu->map_logical;
+
+ if (count == 1) {
+ /* Initialize a single page node */
+ _write_page_entry(map + node, DC_SINGLE_PAGE_NODE_INITIALIZE | DC_MMU_SINGLE);
+ }
+ else {
+ /* Initialize the FREE node*/
+ _write_page_entry(map + node, (count << 8) | DC_MMU_FREE);
+ _write_page_entry(map + node + 1, ~0U);
+ }
+
+ return _link(mmu, index, node);
+}
+
+/* Collect free nodes */
+int _collect(dc_mmu_pt mmu)
+{
+ u32 *map = mmu->map_logical;
+ u32 count = 0, start = 0, i = 0;
+ u32 previous = ~0U;
+ int ret = 0;
+
+ mmu->heap_list = ~0U;
+ mmu->free_nodes = false;
+
+ /* Walk the entire page table */
+ for (i = 0; i < mmu->page_table_entries; i++) {
+ switch (DC_ENTRY_TYPE(_read_page_entry(&map[i]))) {
+ case DC_MMU_SINGLE:
+ if (count++ == 0) {
+ /* Set new start node */
+ start = i;
+ }
+ break;
+ case DC_MMU_FREE:
+ if (count == 0) {
+ /* Set new start node */
+ start = i;
+ }
+
+ count += _read_page_entry(&map[i]) >> 8;
+ /* Advance the index of the page table */
+ i += (_read_page_entry(&map[i]) >> 8) - 1;
+ break;
+ case DC_MMU_USED:
+ /* Meet used node, start to collect */
+ if (count > 0) {
+ /* Add free node to list*/
+ ret = _add_free(mmu, previous, start, count);
+ if (ret)
+ return ret;
+ /* Reset previous unused node index */
+ previous = start;
+ count = 0;
+ }
+ break;
+ default:
+ pr_err("MMU page table corrupted at index %u!", i);
+ return -EINVAL;
+ }
+ }
+
+ /* If left node is an open node. */
+ if (count > 0) {
+ ret = _add_free(mmu, previous, start, count);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+int _fill_page_table(u32 *page_table, u32 page_count, u32 entry_value)
+{
+ u32 i;
+
+ for (i = 0; i < page_count; i++) {
+ _write_page_entry(page_table + i, entry_value);
+ }
+
+ return 0;
+}
+
+int dc_mmu_allocate_pages(dc_mmu_pt mmu, u32 page_count, u32 *address)
+{
+ bool got = false, acquired = false;
+ u32 *map;
+ u32 index = 0, vaddr, left;
+ u32 previous = ~0U;
+ u32 mtlb_offset, stlb_offset;
+ int ret = 0;
+
+ if (page_count == 0 || page_count > mmu->page_table_entries) {
+ pr_err("%s has invalid arguments.\n", __FUNCTION__);
+ return -EINVAL;
+ }
+
+ _acquire_mutex(mmu->page_table_mutex, DC_INFINITE);
+ acquired = true;
+
+ for (map = mmu->map_logical; !got;) {
+ for (index = mmu->heap_list; !got && (index < mmu->page_table_entries);) {
+ switch (DC_ENTRY_TYPE(_read_page_entry(&map[index]))) {
+ case DC_MMU_SINGLE:
+ if (page_count == 1) {
+ got = true;
+ }
+ else {
+ /* Move to next node */
+ previous = index;
+ index = _read_page_entry(&map[index]) >> 8;
+ }
+ break;
+ case DC_MMU_FREE:
+ if (page_count <= (_read_page_entry(&map[index]) >> 8)) {
+ got = true;
+ }
+ else {
+ /* Move to next node */
+ previous = index;
+ index = _read_page_entry(&map[index + 1]);
+ }
+ break;
+ default:
+ /* Only link SINGLE and FREE node */
+ pr_err("MMU table corrupted at index %u!", index);
+ ret = -EINVAL;
+ goto OnError;
+ }
+ }
+
+ /* If out of index */
+ if (index >= mmu->page_table_entries) {
+ if (mmu->free_nodes) {
+ /* Collect the free node */
+ ret = _collect(mmu);
+ if (ret)
+ goto OnError;
+ }
+ else {
+ ret = -ENODATA;
+ goto OnError;
+ }
+ }
+ }
+
+ switch (DC_ENTRY_TYPE(_read_page_entry(&map[index]))) {
+ case DC_MMU_SINGLE:
+ /* Unlink single node from node list */
+ ret = _link(mmu, previous, _read_page_entry(&map[index]) >> 8);
+ if (ret)
+ goto OnError;
+ break;
+
+ case DC_MMU_FREE:
+ left = (_read_page_entry(&map[index]) >> 8) - page_count;
+ switch (left) {
+ case 0:
+ /* Unlink the entire FREE type node */
+ ret = _link(mmu, previous, _read_page_entry(&map[index + 1]));
+ if (ret)
+ goto OnError;
+ break;
+ case 1:
+ /* Keep the map[index] as a single node,
+ * mark the left as used
+ */
+ _write_page_entry(&map[index],
+ (_read_page_entry(&map[index + 1]) << 8) |
+ DC_MMU_SINGLE);
+ index++;
+ break;
+ default:
+ /* FREE type node left */
+ _write_page_entry(&map[index],
+ (left << 8) | DC_MMU_FREE);
+ index += left;
+ break;
+ }
+ break;
+ default:
+ /* Only link SINGLE and FREE node */
+ pr_err("MMU table corrupted at index %u!", index);
+ ret = -EINVAL;
+ goto OnError;
+ }
+
+ /* Mark node as used */
+ ret = _fill_page_table(&map[index], page_count, DC_MMU_USED);
+ if (ret)
+ goto OnError;
+
+ _release_mutex(mmu->page_table_mutex);
+
+ mtlb_offset = index / MMU_STLB_4K_ENTRY_NUM + mmu->dynamic_mapping_start;
+ stlb_offset = index % MMU_STLB_4K_ENTRY_NUM;
+
+ vaddr = (mtlb_offset << MMU_MTLB_SHIFT) | (stlb_offset << MMU_STLB_4K_SHIFT);
+
+ if (address != NULL) {
+ *address = vaddr;
+ }
+
+ return 0;
+
+OnError:
+ if (acquired) {
+ _release_mutex(mmu->page_table_mutex);
+ }
+
+ return ret;
+}
+
+int dc_mmu_free_pages(dc_mmu_pt mmu, u32 address, u32 page_count)
+{
+ u32 *node;
+
+ if (page_count == 0)
+ return -EINVAL;
+
+ node = mmu->map_logical + _address_to_index(mmu, address);
+
+ _acquire_mutex(mmu->page_table_mutex, DC_INFINITE);
+
+ if (page_count == 1) {
+ /* Mark the Single page node free */
+ _write_page_entry(node, DC_SINGLE_PAGE_NODE_INITIALIZE | DC_MMU_SINGLE);
+ }
+ else {
+ /* Mark the FREE type node free */
+ _write_page_entry(node, (page_count << 8) | DC_MMU_FREE);
+ _write_page_entry(node + 1, ~0U);
+ }
+
+ mmu->free_nodes = true;
+
+ _release_mutex(mmu->page_table_mutex);
+
+ return 0;
+}
+
+int dc_mmu_set_page(dc_mmu_pt mmu, u64 page_address, u32 *page_entry)
+{
+ u32 address_ext;
+ u32 address;
+
+ if (page_entry == NULL || (page_address & 0xFFF)) {
+ return -EINVAL;
+ }
+
+ /* [31:0]. */
+ address = (u32)(page_address & 0xFFFFFFFF);
+ /* [39:32]. */
+ address_ext = (u32)((page_address >> 32) & 0xFF);
+
+ _write_page_entry(page_entry, _set_page(address, address_ext, true));
+
+ return 0;
+}
+
+int dc_mmu_map_memory(dc_mmu_pt mmu, u64 physical, u32 page_count,
+ u32 *address, bool continuous, bool security)
+{
+ u32 virutal_address, i= 0;
+ u32 mtlb_num, mtlb_entry, mtlb_offset;
+ bool allocated = false;
+ int ret = 0;
+
+ ret = dc_mmu_allocate_pages(mmu, page_count, &virutal_address);
+ if (ret)
+ goto OnError;
+
+ *address = virutal_address;
+ allocated = true;
+
+ /*Fill mtlb security bit*/
+ mtlb_num = _mtlb_offset(virutal_address + page_count * MMU_PAGE_4K_SIZE - 1) -
+ _mtlb_offset(virutal_address) + 1;
+ mtlb_offset = _mtlb_offset(virutal_address);
+ mtlb_entry = mmu->mtlb_logical[mtlb_offset];
+
+ for (i = 0; i < mtlb_num ; i++) {
+ mtlb_entry = mmu->mtlb_logical[mtlb_offset + i];
+ if(security) {
+ mtlb_entry = mtlb_entry
+ | MMU_MTLB_SECURITY
+ | MMU_MTLB_EXCEPTION;
+ _write_page_entry(&mmu->mtlb_logical[mtlb_offset + i], mtlb_entry);
+ } else {
+ mtlb_entry = mtlb_entry & (~MMU_MTLB_SECURITY);
+ _write_page_entry(&mmu->mtlb_logical[mtlb_offset + i], mtlb_entry);
+ }
+ }
+
+ /* Fill in page table */
+ for (i = 0; i < page_count; i++) {
+ u64 page_phy;
+ u32 *page_entry;
+ struct page **pages;
+
+ if (continuous == true) {
+ page_phy = physical + i * MMU_PAGE_4K_SIZE;
+ }
+ else {
+ pages = (struct page **)physical;
+ page_phy = page_to_phys(pages[i]);
+ }
+
+ ret = dc_mmu_get_page_entry(mmu, virutal_address, &page_entry);
+ if (ret)
+ goto OnError;
+
+ /* Write the page address to the page entry */
+ ret = dc_mmu_set_page(mmu, page_phy, page_entry);
+ if (ret)
+ goto OnError;
+
+ /* Get next page */
+ virutal_address += MMU_PAGE_4K_SIZE;
+ }
+
+ return 0;
+
+OnError:
+ if (allocated)
+ dc_mmu_free_pages(mmu, virutal_address, page_count);
+ pr_info("%s fail!\n", __FUNCTION__);
+
+ return ret;
+}
+
+int dc_mmu_unmap_memory(dc_mmu_pt mmu, u32 gpu_address, u32 page_count)
+{
+ return dc_mmu_free_pages(mmu, gpu_address, page_count);
+}
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2020 VeriSilicon Holdings Co., Ltd.
+ */
+
+#ifndef _VS_DC_MMU_H_
+#define _VS_DC_MMU_H_
+
+#include "vs_type.h"
+
+#define DC_INFINITE ((u32)(~0U))
+
+#define DC_ENTRY_TYPE(x) (x & 0xF0)
+#define DC_SINGLE_PAGE_NODE_INITIALIZE (~((1U << 8) - 1))
+
+#define DC_INVALID_PHYSICAL_ADDRESS ~0ULL
+#define DC_INVALID_ADDRESS ~0U
+
+/* 1k mode */
+#define MMU_MTLB_SHIFT 24
+#define MMU_STLB_4K_SHIFT 12
+
+#define MMU_MTLB_BITS (32 - MMU_MTLB_SHIFT)
+#define MMU_PAGE_4K_BITS MMU_STLB_4K_SHIFT
+#define MMU_STLB_4K_BITS (32 - MMU_MTLB_BITS - MMU_PAGE_4K_BITS)
+
+#define MMU_MTLB_ENTRY_NUM (1 << MMU_MTLB_BITS)
+#define MMU_MTLB_SIZE (MMU_MTLB_ENTRY_NUM << 2)
+#define MMU_STLB_4K_ENTRY_NUM (1 << MMU_STLB_4K_BITS)
+#define MMU_STLB_4K_SIZE (MMU_STLB_4K_ENTRY_NUM << 2)
+#define MMU_PAGE_4K_SIZE (1 << MMU_STLB_4K_SHIFT)
+
+#define MMU_MTLB_MASK (~((1U << MMU_MTLB_SHIFT)-1))
+#define MMU_STLB_4K_MASK ((~0U << MMU_STLB_4K_SHIFT) ^ MMU_MTLB_MASK)
+#define MMU_PAGE_4K_MASK (MMU_PAGE_4K_SIZE - 1)
+
+/* page offset definitions. */
+#define MMU_OFFSET_4K_BITS (32 - MMU_MTLB_BITS - MMU_STLB_4K_BITS)
+#define MMU_OFFSET_4K_MASK ((1U << MMU_OFFSET_4K_BITS) - 1)
+
+#define MMU_MTLB_PRESENT 0x00000001
+#define MMU_MTLB_EXCEPTION 0x00000002
+#define MMU_MTLB_SECURITY 0x00000010
+#define MMU_MTLB_4K_PAGE 0x00000000
+
+typedef enum _dc_mmu_type
+{
+ DC_MMU_USED = (0 << 4),
+ DC_MMU_SINGLE = (1 << 4),
+ DC_MMU_FREE = (2 << 4),
+}
+dc_mmu_type;
+
+typedef enum _dc_mmu_mode
+{
+ MMU_MODE_1K,
+ MMU_MODE_4K,
+}
+dc_mmu_mode;
+
+typedef struct _dc_mmu_stlb
+{
+ u32 *logical;
+ void *physical;
+ u32 size;
+ u64 physBase;
+ u32 pageCount;
+}
+dc_mmu_stlb, *dc_mmu_stlb_pt;
+
+typedef struct _dc_mmu {
+ u32 mtlb_bytes;
+ u64 mtlb_physical;
+ u32 *mtlb_logical;
+
+ void *safe_page_logical;
+ u64 safe_page_physical;
+
+ u32 dynamic_mapping_start;
+
+ void *stlbs;
+
+ u64 stlb_physicals[MMU_MTLB_ENTRY_NUM];
+
+ u32 page_table_entries;
+ u32 page_table_size;
+ u32 heap_list;
+
+ u32 *map_logical;
+ bool free_nodes;
+
+ void *page_table_mutex;
+
+ dc_mmu_mode mode;
+
+ void *static_stlb;
+}
+dc_mmu, *dc_mmu_pt;
+
+int dc_mmu_construct(struct device *dev, dc_mmu_pt *mmu);
+int dc_mmu_map_memory(dc_mmu_pt mmu, u64 physical, u32 page_count,
+ u32 *address, bool continuous, bool security);
+int dc_mmu_unmap_memory(dc_mmu_pt mmu, u32 gpu_address, u32 page_count);
+
+#endif /* _VS_DC_MMU_H_ */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020 VeriSilicon Holdings Co., Ltd.
+ */
+
+#include <linux/of_graph.h>
+#include <linux/component.h>
+#include <linux/iommu.h>
+#include <linux/version.h>
+
+#include <drm/drm_of.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_fb_helper.h>
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 5, 0)
+#include <drm/drm_drv.h>
+#include <drm/drm_file.h>
+#include <drm/drm_prime.h>
+#include <drm/drm_vblank.h>
+#include <drm/drm_ioctl.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_debugfs.h>
+#endif
+#include <linux/of_reserved_mem.h>
+#include <drm/drm_aperture.h>
+
+#include "vs_drv.h"
+#include "vs_fb.h"
+#include "vs_gem.h"
+#include "vs_plane.h"
+#include "vs_crtc.h"
+#include "vs_simple_enc.h"
+#include "vs_dc.h"
+#include "vs_virtual.h"
+#include "dw_mipi_dsi.h"
+
+extern struct platform_driver starfive_encoder_driver;
+
+#define DRV_NAME "vs-drm"
+#define DRV_DESC "VeriSilicon DRM driver"
+#define DRV_DATE "20191101"
+#define DRV_MAJOR 1
+#define DRV_MINOR 0
+
+static bool has_iommu = true;
+static struct platform_driver vs_drm_platform_driver;
+
+static const struct file_operations fops = {
+ .owner = THIS_MODULE,
+ .open = drm_open,
+ .release = drm_release,
+ .unlocked_ioctl = drm_ioctl,
+ .compat_ioctl = drm_compat_ioctl,
+ .poll = drm_poll,
+ .read = drm_read,
+ .mmap = vs_gem_mmap,
+};
+
+#ifdef CONFIG_DEBUG_FS
+static int vs_debugfs_planes_show(struct seq_file *s, void *data)
+{
+ struct drm_info_node *node = (struct drm_info_node *)s->private;
+ struct drm_device *dev = node->minor->dev;
+ struct drm_plane *plane;
+
+ list_for_each_entry(plane, &dev->mode_config.plane_list, head) {
+ struct drm_plane_state *state = plane->state;
+ struct vs_plane_state *plane_state = to_vs_plane_state(state);
+
+ seq_printf(s, "plane[%u]: %s\n", plane->base.id, plane->name);
+ seq_printf(s, "\tcrtc = %s\n", state->crtc ?
+ state->crtc->name : "(null)");
+ seq_printf(s, "\tcrtc id = %u\n", state->crtc ?
+ state->crtc->base.id : 0);
+ seq_printf(s, "\tcrtc-pos = " DRM_RECT_FMT "\n",
+ DRM_RECT_ARG(&plane_state->status.dest));
+ seq_printf(s, "\tsrc-pos = " DRM_RECT_FP_FMT "\n",
+ DRM_RECT_FP_ARG(&plane_state->status.src));
+ //seq_printf(s, "\tformat = %s\n", state->fb ?
+ // plane_state->status.format_name.str : "(null)");
+ seq_printf(s, "\trotation = 0x%x\n", state->rotation);
+ seq_printf(s, "\ttiling = %u\n",
+ plane_state->status.tile_mode);
+
+ seq_puts(s, "\n");
+ }
+
+ return 0;
+}
+
+static struct drm_info_list vs_debugfs_list[] = {
+ { "planes", vs_debugfs_planes_show, 0, NULL },
+};
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0)
+static void vs_debugfs_init(struct drm_minor *minor)
+{
+ drm_debugfs_create_files(vs_debugfs_list,
+ ARRAY_SIZE(vs_debugfs_list),
+ minor->debugfs_root, minor);
+}
+#else
+static int vs_debugfs_init(struct drm_minor *minor)
+{
+ struct drm_device *dev = minor->dev;
+ int ret;
+
+ ret = drm_debugfs_create_files(vs_debugfs_list,
+ ARRAY_SIZE(vs_debugfs_list),
+ minor->debugfs_root, minor);
+ if (ret)
+ dev_err(dev->dev, "could not install vs_debugfs_list.\n");
+
+ return ret;
+}
+#endif
+#endif
+
+static struct drm_driver vs_drm_driver = {
+ .driver_features = DRIVER_MODESET | DRIVER_ATOMIC | DRIVER_GEM,
+ .lastclose = drm_fb_helper_lastclose,
+ .prime_handle_to_fd = drm_gem_prime_handle_to_fd,
+ .prime_fd_to_handle = drm_gem_prime_fd_to_handle,
+ .gem_prime_import = vs_gem_prime_import,
+ .gem_prime_import_sg_table = vs_gem_prime_import_sg_table,
+ .gem_prime_mmap = vs_gem_prime_mmap,
+ .dumb_create = vs_gem_dumb_create,
+#ifdef CONFIG_DEBUG_FS
+ .debugfs_init = vs_debugfs_init,
+#endif
+ .fops = &fops,
+ .name = DRV_NAME,
+ .desc = DRV_DESC,
+ .date = DRV_DATE,
+ .major = DRV_MAJOR,
+ .minor = DRV_MINOR,
+};
+
+int vs_drm_iommu_attach_device(struct drm_device *drm_dev,
+ struct device *dev)
+{
+ struct vs_drm_private *priv = drm_dev->dev_private;
+ int ret;
+
+ if (!has_iommu)
+ return 0;
+
+ if (!priv->domain) {
+ priv->domain = iommu_get_domain_for_dev(dev);
+ if (IS_ERR(priv->domain))
+ return PTR_ERR(priv->domain);
+ priv->dma_dev = dev;
+ }
+
+ ret = iommu_attach_device(priv->domain, dev);
+ if (ret) {
+ DRM_DEV_ERROR(dev, "Failed to attach iommu device\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+void vs_drm_iommu_detach_device(struct drm_device *drm_dev,
+ struct device *dev)
+{
+ struct vs_drm_private *priv = drm_dev->dev_private;
+
+ if (!has_iommu)
+ return;
+
+ iommu_detach_device(priv->domain, dev);
+
+ if (priv->dma_dev == dev)
+ priv->dma_dev = drm_dev->dev;
+}
+
+void vs_drm_update_pitch_alignment(struct drm_device *drm_dev,
+ unsigned int alignment)
+{
+ struct vs_drm_private *priv = drm_dev->dev_private;
+
+ if (alignment > priv->pitch_alignment)
+ priv->pitch_alignment = alignment;
+}
+
+static int vs_drm_bind(struct device *dev)
+{
+ struct drm_device *drm_dev;
+ struct vs_drm_private *priv;
+ int ret;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 24)
+ static u64 dma_mask = DMA_BIT_MASK(40);
+#else
+ static u64 dma_mask = DMA_40BIT_MASK;
+#endif
+ printk("vs_drm_bind dma_mask = %08x\n",dma_mask);
+ printk("vs_drm_bind LINUX_VERSION_CODE = %08x\n",LINUX_VERSION_CODE);
+ printk("vs_drm_bind KERNEL_VERSION(2, 6, 24) = %08x\n",KERNEL_VERSION(2, 6, 24));
+
+ /* Remove existing drivers that may own the framebuffer memory. */
+ ret = drm_aperture_remove_framebuffers(false, &vs_drm_driver);
+ if (ret) {
+ DRM_DEV_ERROR(dev,
+ "Failed to remove existing framebuffers - %d.\n",
+ ret);
+ return ret;
+ }
+
+ drm_dev = drm_dev_alloc(&vs_drm_driver, dev);
+ if (IS_ERR(drm_dev))
+ return PTR_ERR(drm_dev);
+
+ dev_set_drvdata(dev, drm_dev);
+
+ priv = devm_kzalloc(drm_dev->dev, sizeof(struct vs_drm_private),
+ GFP_KERNEL);
+ if (!priv) {
+ ret = -ENOMEM;
+ goto err_put_dev;
+ }
+
+ priv->pitch_alignment = 64;
+ priv->dma_dev = drm_dev->dev;
+ priv->dma_dev->coherent_dma_mask = dma_mask;
+ drm_dev->dev_private = priv;
+
+ drm_mode_config_init(drm_dev);
+
+ /* Now try and bind all our sub-components */
+ ret = component_bind_all(dev, drm_dev);
+ if (ret)
+ goto err_mode;
+
+ vs_mode_config_init(drm_dev);
+
+ ret = drm_vblank_init(drm_dev, drm_dev->mode_config.num_crtc);
+ if (ret)
+ goto err_bind;
+
+ drm_mode_config_reset(drm_dev);
+
+ drm_dev->irq_enabled = true;
+
+ drm_kms_helper_poll_init(drm_dev);
+
+ ret = drm_dev_register(drm_dev, 0);
+ if (ret)
+ goto err_helper;
+
+ return 0;
+
+err_helper:
+ drm_kms_helper_poll_fini(drm_dev);
+err_bind:
+ component_unbind_all(drm_dev->dev, drm_dev);
+err_mode:
+ drm_mode_config_cleanup(drm_dev);
+ if (priv->domain)
+ iommu_domain_free(priv->domain);
+err_put_dev:
+ drm_dev->dev_private = NULL;
+ dev_set_drvdata(dev, NULL);
+ drm_dev_put(drm_dev);
+ return ret;
+}
+
+static void vs_drm_unbind(struct device *dev)
+{
+ struct drm_device *drm_dev = dev_get_drvdata(dev);
+ struct vs_drm_private *priv = drm_dev->dev_private;
+
+ drm_dev_unregister(drm_dev);
+
+ drm_kms_helper_poll_fini(drm_dev);
+
+ component_unbind_all(drm_dev->dev, drm_dev);
+
+ drm_mode_config_cleanup(drm_dev);
+
+ if (priv->domain) {
+ iommu_domain_free(priv->domain);
+ priv->domain = NULL;
+ }
+
+ drm_dev->dev_private = NULL;
+ dev_set_drvdata(dev, NULL);
+ drm_dev_put(drm_dev);
+}
+
+static const struct component_master_ops vs_drm_ops = {
+ .bind = vs_drm_bind,
+ .unbind = vs_drm_unbind,
+};
+
+static struct platform_driver *drm_sub_drivers[] = {
+ /* put display control driver at start */
+ &dc_platform_driver,
+
+ /* connector */
+
+ /* bridge */
+#ifdef CONFIG_VERISILICON_DW_MIPI_DSI
+ &dw_mipi_dsi_driver,
+#endif
+#ifdef CONFIG_STARFIVE_INNO_HDMI
+ &inno_hdmi_driver,
+#endif
+#ifdef CONFIG_STARFIVE_DSI
+ &starfive_dsi_platform_driver,
+#endif
+ /* encoder */
+ //&simple_encoder_driver,
+ &starfive_encoder_driver,
+
+#ifdef CONFIG_VERISILICON_VIRTUAL_DISPLAY
+ &virtual_display_platform_driver,
+#endif
+};
+#define NUM_DRM_DRIVERS \
+ (sizeof(drm_sub_drivers) / sizeof(struct platform_driver *))
+
+static int compare_dev(struct device *dev, void *data)
+{
+ return dev == (struct device *)data;
+}
+
+static struct component_match *vs_drm_match_add(struct device *dev)
+{
+ struct component_match *match = NULL;
+ int i;
+
+ for (i = 0; i < NUM_DRM_DRIVERS; ++i) {
+ struct platform_driver *drv = drm_sub_drivers[i];
+ struct device *p = NULL, *d;
+
+ while ((d = platform_find_device_by_driver(p, &drv->driver))) {
+ put_device(p);
+
+ component_match_add(dev, &match, compare_dev, d);
+ p = d;
+ }
+ put_device(p);
+ }
+
+ return match ?: ERR_PTR(-ENODEV);
+}
+
+static int vs_drm_platform_of_probe(struct device *dev)
+{
+ struct device_node *np = dev->of_node;
+ struct device_node *port;
+ bool found = false;
+ int i;
+
+ if (!np)
+ return -ENODEV;
+
+ for (i = 0;; i++) {
+ struct device_node *iommu;
+
+ port = of_parse_phandle(np, "ports", i);
+ if (!port)
+ break;
+
+ if (!of_device_is_available(port->parent)) {
+ of_node_put(port);
+ continue;
+ }
+
+ iommu = of_parse_phandle(port->parent, "iommus", 0);
+
+ /*
+ * if there is a crtc not support iommu, force set all
+ * crtc use non-iommu buffer.
+ */
+ if (!iommu || !of_device_is_available(iommu->parent))
+ has_iommu = false;
+
+ found = true;
+
+ of_node_put(iommu);
+ of_node_put(port);
+ }
+
+ if (i == 0) {
+ DRM_DEV_ERROR(dev, "missing 'ports' property\n");
+ return -ENODEV;
+ }
+
+ if (!found) {
+ DRM_DEV_ERROR(dev, "No available DC found.\n");
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static int vs_drm_platform_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct component_match *match;
+ int ret;
+
+ ret = vs_drm_platform_of_probe(dev);
+ if (ret)
+ return ret;
+
+ match = vs_drm_match_add(dev);
+ if (IS_ERR(match))
+ return PTR_ERR(match);
+
+ return component_master_add_with_match(dev, &vs_drm_ops, match);
+}
+
+static int vs_drm_platform_remove(struct platform_device *pdev)
+{
+ component_master_del(&pdev->dev, &vs_drm_ops);
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int vs_drm_suspend(struct device *dev)
+{
+ struct drm_device *drm = dev_get_drvdata(dev);
+
+ return drm_mode_config_helper_suspend(drm);
+}
+
+static int vs_drm_resume(struct device *dev)
+{
+ struct drm_device *drm = dev_get_drvdata(dev);
+
+ return drm_mode_config_helper_resume(drm);
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(vs_drm_pm_ops, vs_drm_suspend, vs_drm_resume);
+
+
+static const struct of_device_id vs_drm_dt_ids[] = {
+
+ { .compatible = "verisilicon,display-subsystem", },
+
+ { /* sentinel */ },
+
+};
+
+MODULE_DEVICE_TABLE(of, vs_drm_dt_ids);
+
+static struct platform_driver vs_drm_platform_driver = {
+ .probe = vs_drm_platform_probe,
+ .remove = vs_drm_platform_remove,
+
+ .driver = {
+ .name = DRV_NAME,
+ .of_match_table = vs_drm_dt_ids,
+ .pm = &vs_drm_pm_ops,
+ },
+};
+
+static int __init vs_drm_init(void)
+{
+ int ret;
+ printk("%s:%d\n",__FUNCTION__,__LINE__);
+ ret = platform_register_drivers(drm_sub_drivers, NUM_DRM_DRIVERS);
+ if (ret)
+ return ret;
+
+ ret = platform_driver_register(&vs_drm_platform_driver);
+ if (ret)
+ platform_unregister_drivers(drm_sub_drivers, NUM_DRM_DRIVERS);
+
+ return ret;
+}
+
+static void __exit vs_drm_fini(void)
+{
+ platform_driver_unregister(&vs_drm_platform_driver);
+ platform_unregister_drivers(drm_sub_drivers, NUM_DRM_DRIVERS);
+}
+
+module_init(vs_drm_init);
+module_exit(vs_drm_fini);
+
+MODULE_DESCRIPTION("VeriSilicon DRM Driver");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2020 VeriSilicon Holdings Co., Ltd.
+ */
+
+#ifndef __VS_DRV_H__
+#define __VS_DRV_H__
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/version.h>
+
+#include <drm/drm_gem.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 5, 0)
+#include <drm/drmP.h>
+#endif
+
+#include "vs_plane.h"
+#ifdef CONFIG_VERISILICON_MMU
+#include "vs_dc_mmu.h"
+#endif
+
+
+/*
+ *
+ * @dma_dev: device for DMA API.
+ * - use the first attached device if support iommu
+ else use drm device (only contiguous buffer support)
+ * @domain: iommu domain for DRM.
+ * - all DC IOMMU share same domain to reduce mapping
+ * @pitch_alignment: buffer pitch alignment required by sub-devices.
+ *
+ */
+struct vs_drm_private {
+ struct device *dma_dev;
+ struct iommu_domain *domain;
+#ifdef CONFIG_VERISILICON_MMU
+ dc_mmu *mmu;
+#endif
+
+ unsigned int pitch_alignment;
+};
+
+int vs_drm_iommu_attach_device(struct drm_device *drm_dev,
+ struct device *dev);
+
+void vs_drm_iommu_detach_device(struct drm_device *drm_dev,
+ struct device *dev);
+
+void vs_drm_update_pitch_alignment(struct drm_device *drm_dev,
+ unsigned int alignment);
+
+static inline struct device *to_dma_dev(struct drm_device *dev)
+{
+ struct vs_drm_private *priv = dev->dev_private;
+
+ return priv->dma_dev;
+}
+
+static inline bool is_iommu_enabled(struct drm_device *dev)
+{
+ struct vs_drm_private *priv = dev->dev_private;
+
+ return priv->domain != NULL ? true : false;
+}
+
+#ifdef CONFIG_STARFIVE_INNO_HDMI
+extern struct platform_driver inno_hdmi_driver;
+#endif
+
+#ifdef CONFIG_STARFIVE_DSI
+extern int init_seeed_panel(void);
+extern void exit_seeed_panel(void);
+extern struct platform_driver starfive_dsi_platform_driver;
+#endif
+
+#endif /* __VS_DRV_H__ */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020 VeriSilicon Holdings Co., Ltd.
+ */
+
+#include <linux/module.h>
+#include <linux/version.h>
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 5, 0)
+#include <drm/drm_fourcc.h>
+#else
+#include <drm/drmP.h>
+#endif
+#include <drm/drm_gem.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_damage_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+
+#include "vs_fb.h"
+#include "vs_gem.h"
+
+#define fourcc_mod_vs_get_type(val) \
+ (((val) & DRM_FORMAT_MOD_VS_TYPE_MASK) >> 54)
+
+static struct drm_framebuffer_funcs vs_fb_funcs = {
+ .create_handle = drm_gem_fb_create_handle,
+ .destroy = drm_gem_fb_destroy,
+ .dirty = drm_atomic_helper_dirtyfb,
+};
+
+static struct drm_framebuffer *
+vs_fb_alloc(struct drm_device *dev, const struct drm_mode_fb_cmd2 *mode_cmd,
+ struct vs_gem_object **obj, unsigned int num_planes)
+{
+ struct drm_framebuffer *fb;
+ int ret, i;
+
+ fb = kzalloc(sizeof(*fb), GFP_KERNEL);
+ if (!fb)
+ return ERR_PTR(-ENOMEM);
+
+ drm_helper_mode_fill_fb_struct(dev, fb, mode_cmd);
+
+ for (i = 0; i < num_planes; i++)
+ fb->obj[i] = &obj[i]->base;
+
+ ret = drm_framebuffer_init(dev, fb, &vs_fb_funcs);
+ if (ret) {
+ dev_err(dev->dev, "Failed to initialize framebuffer: %d\n",
+ ret);
+ kfree(fb);
+ return ERR_PTR(ret);
+ }
+
+ return fb;
+}
+
+static struct drm_framebuffer *vs_fb_create(struct drm_device *dev,
+ struct drm_file *file_priv,
+ const struct drm_mode_fb_cmd2 *mode_cmd)
+{
+ struct drm_framebuffer *fb;
+ const struct drm_format_info *info;
+ struct vs_gem_object *objs[MAX_NUM_PLANES];
+ struct drm_gem_object *obj;
+ unsigned int height, size;
+ unsigned char i, num_planes;
+ int ret = 0;
+
+ info = drm_get_format_info(dev, mode_cmd);
+ if (!info)
+ return ERR_PTR(-EINVAL);
+
+ num_planes = info->num_planes;
+ if (num_planes > MAX_NUM_PLANES)
+ return ERR_PTR(-EINVAL);
+
+ for (i = 0; i < num_planes; i++) {
+ obj = drm_gem_object_lookup(file_priv, mode_cmd->handles[i]);
+ if (!obj) {
+ dev_err(dev->dev, "Failed to lookup GEM object.\n");
+ ret = -ENXIO;
+ goto err;
+ }
+
+ height = drm_format_info_plane_height(info,
+ mode_cmd->height, i);
+
+ size = height * mode_cmd->pitches[i] + mode_cmd->offsets[i];
+
+ if (obj->size < size) {
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0)
+ drm_gem_object_put(obj);
+#else
+ drm_gem_object_put_unlocked(obj);
+#endif
+ ret = -EINVAL;
+ goto err;
+ }
+
+ objs[i] = to_vs_gem_object(obj);
+ }
+
+ fb = vs_fb_alloc(dev, mode_cmd, objs, i);
+ if (IS_ERR(fb)) {
+ ret = PTR_ERR(fb);
+ goto err;
+ }
+
+ return fb;
+
+err:
+ for (; i > 0; i--)
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0)
+ drm_gem_object_put(&objs[i-1]->base);
+#else
+ drm_gem_object_put_unlocked(&objs[i-1]->base);
+#endif
+
+ return ERR_PTR(ret);
+}
+
+struct vs_gem_object *vs_fb_get_gem_obj(struct drm_framebuffer *fb,
+ unsigned char plane)
+{
+ if (plane > MAX_NUM_PLANES)
+ return NULL;
+
+ return to_vs_gem_object(fb->obj[plane]);
+}
+
+static const struct drm_format_info vs_formats[] = {
+ {.format = DRM_FORMAT_NV12, .depth = 0, .num_planes = 2, .char_per_block = { 20, 40, 0 },
+ .block_w = { 4, 4, 0 }, .block_h = { 4, 4, 0 },.hsub = 2, .vsub = 2, .is_yuv = true},
+ {.format = DRM_FORMAT_YUV444, .depth = 0, .num_planes = 3, .char_per_block = { 20, 20, 20 },
+ .block_w = { 4, 4, 4 }, .block_h = { 4, 4, 4 },.hsub = 1, .vsub = 1, .is_yuv = true},
+};
+
+static const struct drm_format_info *
+vs_lookup_format_info(const struct drm_format_info formats[],
+ int num_formats, u32 format)
+{
+ int i;
+
+ for (i = 0; i < num_formats; i++) {
+ if (formats[i].format == format)
+ return &formats[i];
+ }
+
+ return NULL;
+}
+
+static const struct drm_format_info *
+vs_get_format_info(const struct drm_mode_fb_cmd2 *cmd)
+{
+ if (fourcc_mod_vs_get_type(cmd->modifier[0]) ==
+ DRM_FORMAT_MOD_VS_TYPE_CUSTOM_10BIT)
+ return vs_lookup_format_info(vs_formats, ARRAY_SIZE(vs_formats),
+ cmd->pixel_format);
+ else
+ return NULL;
+}
+
+static const struct drm_mode_config_funcs vs_mode_config_funcs = {
+ .fb_create = vs_fb_create,
+ .get_format_info = vs_get_format_info,
+ .output_poll_changed = drm_fb_helper_output_poll_changed,
+ .atomic_check = drm_atomic_helper_check,
+ .atomic_commit = drm_atomic_helper_commit,
+};
+
+static struct drm_mode_config_helper_funcs vs_mode_config_helpers = {
+ .atomic_commit_tail = drm_atomic_helper_commit_tail_rpm,
+};
+
+void vs_mode_config_init(struct drm_device *dev)
+{
+ dev->mode_config.allow_fb_modifiers = true;
+
+ if (dev->mode_config.max_width == 0 ||
+ dev->mode_config.max_height == 0) {
+ dev->mode_config.min_width = 0;
+ dev->mode_config.min_height = 0;
+ dev->mode_config.max_width = 4096;
+ dev->mode_config.max_height = 4096;
+ }
+ dev->mode_config.funcs = &vs_mode_config_funcs;
+ dev->mode_config.helper_private = &vs_mode_config_helpers;
+}
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2020 VeriSilicon Holdings Co., Ltd.
+ */
+
+#ifndef __VS_FB_H__
+#define __VS_FB_H__
+
+struct vs_gem_object *vs_fb_get_gem_obj(struct drm_framebuffer *fb,
+ unsigned char plane);
+
+void vs_mode_config_init(struct drm_device *dev);
+#endif /* __VS_FB_H__ */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020 VeriSilicon Holdings Co., Ltd.
+ */
+
+#include <linux/dma-buf.h>
+#include <linux/of_reserved_mem.h>
+#include <drm/drm_gem_cma_helper.h>
+
+#include "vs_drv.h"
+#include "vs_gem.h"
+
+static const struct drm_gem_object_funcs vs_gem_default_funcs;
+
+static void nonseq_free(struct page ** pages, unsigned int nr_page)
+{
+ u32 i;
+
+ if (!pages)
+ return;
+
+ for(i =0; i < nr_page; i++)
+ __free_page(pages[i]);
+
+}
+
+static int get_pages(unsigned int nr_page, struct vs_gem_object *vs_obj)
+{
+ struct page *pages;
+ u32 i, num_page, page_count = 0;
+ int order = 0;
+ gfp_t gfp = GFP_KERNEL;
+
+ if (!vs_obj->pages)
+ return -EINVAL;
+
+ gfp &= ~__GFP_HIGHMEM;
+ gfp |= __GFP_DMA32;
+
+ num_page = nr_page;
+
+ do {
+ pages = NULL;
+ order = get_order(num_page * PAGE_SIZE);
+ num_page = 1 << order;
+
+ if ((num_page + page_count > nr_page) || (order >= MAX_ORDER)) {
+ num_page = num_page >> 1;
+ continue;
+ }
+
+ pages = alloc_pages(gfp, order);
+ if (!pages) {
+ if (num_page == 1) {
+ nonseq_free(vs_obj->pages, page_count);
+ return -ENOMEM;
+ }
+
+ num_page = num_page >> 1;
+ }
+ else {
+ for(i = 0; i < num_page; i++) {
+ vs_obj->pages[page_count + i] = &pages[i];
+ SetPageReserved(vs_obj->pages[page_count + i]);
+ }
+
+ page_count += num_page;
+ num_page = nr_page - page_count;
+ }
+
+ } while (page_count < nr_page);
+
+ vs_obj->get_pages = true;
+
+ return 0;
+}
+
+static void put_pages(unsigned int nr_page, struct vs_gem_object *vs_obj)
+{
+ u32 i;
+
+ for(i = 0; i < nr_page; i++)
+ ClearPageReserved(vs_obj->pages[i]);
+
+ nonseq_free(vs_obj->pages, nr_page);
+
+ return;
+}
+
+static int vs_gem_alloc_buf(struct vs_gem_object *vs_obj)
+{
+ struct drm_device *dev = vs_obj->base.dev;
+ unsigned int nr_pages;
+ struct sg_table sgt;
+ int ret = -ENOMEM;
+#ifdef CONFIG_VERISILICON_MMU
+ struct vs_drm_private *priv = dev->dev_private;
+#endif
+
+ if (vs_obj->dma_addr) {
+ DRM_DEV_DEBUG_KMS(dev->dev, "already allocated.\n");
+ return 0;
+ }
+
+ vs_obj->dma_attrs = DMA_ATTR_WRITE_COMBINE
+ | DMA_ATTR_NO_KERNEL_MAPPING;
+
+ if (!is_iommu_enabled(dev))
+ vs_obj->dma_attrs |= DMA_ATTR_FORCE_CONTIGUOUS;
+
+ nr_pages = vs_obj->size >> PAGE_SHIFT;
+
+ vs_obj->pages = kvmalloc_array(nr_pages, sizeof(struct page *),
+ GFP_KERNEL | __GFP_ZERO);
+ if (!vs_obj->pages) {
+ DRM_DEV_ERROR(dev->dev, "failed to allocate pages.\n");
+ return -ENOMEM;
+ }
+
+ vs_obj->cookie = dma_alloc_attrs(to_dma_dev(dev), vs_obj->size,
+ &vs_obj->dma_addr, GFP_KERNEL,
+ vs_obj->dma_attrs);
+
+ printk("Allocated coherent memory, vaddr: 0x%0llX, paddr: 0x%0llX\n", (u64)vs_obj->cookie, vs_obj->dma_addr);
+ if (!vs_obj->cookie) {
+ printk("====> %s, %d.\n", __func__, __LINE__);
+#ifdef CONFIG_VERISILICON_MMU
+ ret = get_pages(nr_pages, vs_obj);
+ if (ret) {
+ DRM_DEV_ERROR(dev->dev, "fail to allocate buffer.\n");
+ goto err_free;
+ }
+#else
+ DRM_DEV_ERROR(dev->dev, "failed to allocate buffer.\n");
+ goto err_free;
+#endif
+ }
+
+#ifdef CONFIG_VERISILICON_MMU
+ /* MMU map*/
+ if (!priv->mmu) {
+ DRM_DEV_ERROR(dev->dev, "invalid mmu.\n");
+ ret = -EINVAL;
+ goto err_mem_free;
+ }
+
+ /* mmu for ree driver */
+ if (!vs_obj->get_pages)
+ ret = dc_mmu_map_memory(priv->mmu, (u64)vs_obj->dma_addr,
+ nr_pages, &vs_obj->iova, true, false);
+ else
+ ret = dc_mmu_map_memory(priv->mmu, (u64)vs_obj->pages,
+ nr_pages, &vs_obj->iova, false, false);
+
+ if (ret) {
+ DRM_DEV_ERROR(dev->dev, "failed to do mmu map.\n");
+ goto err_mem_free;
+ }
+#else
+ vs_obj->iova = vs_obj->dma_addr;
+#endif
+
+ printk("====> %s, %d.vs_obj->get_pages = %d\n", __func__, __LINE__,vs_obj->get_pages);
+
+ if (!vs_obj->get_pages) {
+
+ printk("====> %s, %d.\n", __func__, __LINE__);
+ ret = dma_get_sgtable_attrs(to_dma_dev(dev), &sgt,
+ vs_obj->cookie, vs_obj->dma_addr,
+ vs_obj->size, vs_obj->dma_attrs);
+ if (ret < 0) {
+ DRM_DEV_ERROR(dev->dev, "failed to get sgtable.\n");
+ goto err_mem_free;
+ }
+ printk("====> %s, %d.\n", __func__, __LINE__);
+
+
+
+ if (drm_prime_sg_to_page_array(&sgt, vs_obj->pages,nr_pages)) {
+ DRM_DEV_ERROR(dev->dev, "invalid sgtable.\n");
+ ret = -EINVAL;
+ goto err_sgt_free;
+ }
+ printk("====> %s, %d.\n", __func__, __LINE__);
+
+ sg_free_table(&sgt);
+ }
+
+ return 0;
+
+err_sgt_free:
+ sg_free_table(&sgt);
+err_mem_free:
+ if (!vs_obj->get_pages)
+ dma_free_attrs(to_dma_dev(dev), vs_obj->size, vs_obj->cookie,
+ vs_obj->dma_addr, vs_obj->dma_attrs);
+ else
+ put_pages(nr_pages, vs_obj);
+err_free:
+ kvfree(vs_obj->pages);
+
+ return ret;
+}
+
+static void vs_gem_free_buf(struct vs_gem_object *vs_obj)
+{
+ struct drm_device *dev = vs_obj->base.dev;
+#ifdef CONFIG_VERISILICON_MMU
+ struct vs_drm_private *priv = dev->dev_private;
+ unsigned int nr_pages;
+#endif
+
+ if ((!vs_obj->get_pages) && (!vs_obj->dma_addr)) {
+ DRM_DEV_DEBUG_KMS(dev->dev, "dma_addr is invalid.\n");
+ return;
+ }
+
+#ifdef CONFIG_VERISILICON_MMU
+ if (!priv->mmu) {
+ DRM_DEV_ERROR(dev->dev, "invalid mmu.\n");
+ return;
+ }
+
+ nr_pages = vs_obj->size >> PAGE_SHIFT;
+ dc_mmu_unmap_memory(priv->mmu, vs_obj->iova, nr_pages);
+#endif
+
+ if (!vs_obj->get_pages)
+ dma_free_attrs(to_dma_dev(dev), vs_obj->size, vs_obj->cookie,
+ (dma_addr_t)vs_obj->dma_addr,
+ vs_obj->dma_attrs);
+ else
+ put_pages(vs_obj->size >> PAGE_SHIFT, vs_obj);
+
+ kvfree(vs_obj->pages);
+}
+
+static void vs_gem_free_object(struct drm_gem_object *obj)
+{
+ struct vs_gem_object *vs_obj = to_vs_gem_object(obj);
+
+ if (obj->import_attach)
+ drm_prime_gem_destroy(obj, vs_obj->sgt);
+ else
+ vs_gem_free_buf(vs_obj);
+
+ drm_gem_object_release(obj);
+
+ kfree(vs_obj);
+}
+
+static struct vs_gem_object *vs_gem_alloc_object(struct drm_device *dev,
+ size_t size)
+{
+ struct vs_gem_object *vs_obj;
+ struct drm_gem_object *obj;
+ int ret;
+
+ vs_obj = kzalloc(sizeof(*vs_obj), GFP_KERNEL);
+ if (!vs_obj)
+ return ERR_PTR(-ENOMEM);
+
+ vs_obj->size = size;
+ obj = &vs_obj->base;
+
+ ret = drm_gem_object_init(dev, obj, size);
+ if (ret)
+ goto err_free;
+
+ vs_obj->base.funcs = &vs_gem_default_funcs;
+
+ ret = drm_gem_create_mmap_offset(obj);
+ if (ret) {
+ drm_gem_object_release(obj);
+ goto err_free;
+ }
+
+ return vs_obj;
+
+err_free:
+ kfree(vs_obj);
+ return ERR_PTR(ret);
+}
+
+struct vs_gem_object *vs_gem_create_object(struct drm_device *dev,
+ size_t size)
+{
+ struct vs_gem_object *vs_obj;
+ int ret;
+
+ size = PAGE_ALIGN(size);
+
+ vs_obj = vs_gem_alloc_object(dev, size);
+ if (IS_ERR(vs_obj))
+ return vs_obj;
+
+ ret = vs_gem_alloc_buf(vs_obj);
+ if (ret) {
+ drm_gem_object_release(&vs_obj->base);
+ kfree(vs_obj);
+ return ERR_PTR(ret);
+ }
+
+ return vs_obj;
+}
+
+static struct vs_gem_object *vs_gem_create_with_handle(struct drm_device *dev,
+ struct drm_file *file,
+ size_t size,
+ unsigned int *handle)
+{
+ struct vs_gem_object *vs_obj;
+ struct drm_gem_object *obj;
+ int ret;
+
+ vs_obj = vs_gem_create_object(dev, size);
+ if (IS_ERR(vs_obj))
+ return vs_obj;
+
+ obj = &vs_obj->base;
+
+ ret = drm_gem_handle_create(file, obj, handle);
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0)
+ drm_gem_object_put(obj);
+#else
+ drm_gem_object_put_unlocked(obj);
+#endif
+
+ if (ret)
+ return ERR_PTR(ret);
+
+ return vs_obj;
+}
+
+static int vs_gem_mmap_obj(struct drm_gem_object *obj,
+ struct vm_area_struct *vma)
+{
+ struct vs_gem_object *vs_obj = to_vs_gem_object(obj);
+ struct drm_device *drm_dev = vs_obj->base.dev;
+ unsigned long vm_size;
+ int ret = 0;
+
+ vm_size = vma->vm_end - vma->vm_start;
+ if (vm_size > vs_obj->size)
+ return -EINVAL;
+
+ vma->vm_pgoff = 0;
+
+ if (!vs_obj->get_pages) {
+ vma->vm_flags &= ~VM_PFNMAP;
+ DRM_INFO("vs_gem_mmap_obj 111111\n");
+
+ ret = dma_mmap_attrs(to_dma_dev(drm_dev), vma, vs_obj->cookie,
+ vs_obj->dma_addr, vs_obj->size,
+ vs_obj->dma_attrs);
+ }
+ else {
+ u32 i, nr_pages, pfn = 0U;
+ unsigned long start;
+ DRM_INFO("vs_gem_mmap_obj 222222\n");
+
+ vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
+ vma->vm_flags |= VM_IO | VM_DONTCOPY | VM_DONTEXPAND |
+ VM_DONTDUMP;
+ start = vma->vm_start;
+ vm_size = PAGE_ALIGN(vm_size);
+ nr_pages = vm_size >> PAGE_SHIFT;
+
+ for(i = 0; i < nr_pages; i++) {
+ pfn = page_to_pfn(vs_obj->pages[i]);
+
+ ret = remap_pfn_range(vma, start, pfn, PAGE_SIZE,
+ vma->vm_page_prot);
+ if (ret < 0)
+ break;
+
+ start += PAGE_SIZE;
+ }
+ }
+
+ if (ret)
+ drm_gem_vm_close(vma);
+
+ return ret;
+}
+
+struct sg_table *vs_gem_prime_get_sg_table(struct drm_gem_object *obj)
+{
+ struct vs_gem_object *vs_obj = to_vs_gem_object(obj);
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0)
+ return drm_prime_pages_to_sg(obj->dev, vs_obj->pages,
+ vs_obj->size >> PAGE_SHIFT);
+#else
+ return drm_prime_pages_to_sg(vs_obj->pages, vs_obj->size >> PAGE_SHIFT);
+#endif
+}
+
+int vs_gem_prime_vmap(struct drm_gem_object *obj, struct dma_buf_map *map)
+{
+ return 0;
+}
+
+void vs_gem_prime_vunmap(struct drm_gem_object *obj, struct dma_buf_map *map)
+{
+ /* Nothing to do */
+}
+
+static const struct vm_operations_struct vs_vm_ops = {
+ .open = drm_gem_vm_open,
+ .close = drm_gem_vm_close,
+};
+
+static const struct drm_gem_object_funcs vs_gem_default_funcs = {
+ .free = vs_gem_free_object,
+ .get_sg_table = vs_gem_prime_get_sg_table,
+ .vmap = vs_gem_prime_vmap,
+ .vunmap = vs_gem_prime_vunmap,
+ .vm_ops = &vs_vm_ops,
+};
+
+int vs_gem_dumb_create(struct drm_file *file,
+ struct drm_device *dev,
+ struct drm_mode_create_dumb *args)
+{
+ struct vs_drm_private *priv = dev->dev_private;
+ struct vs_gem_object *vs_obj;
+ unsigned int pitch = DIV_ROUND_UP(args->width * args->bpp, 8);
+
+ if (args->bpp % 10)
+ args->pitch = ALIGN(pitch, priv->pitch_alignment);
+ else
+ /* for costum 10bit format with no bit gaps */
+ args->pitch = pitch;
+ args->size = PAGE_ALIGN(args->pitch * args->height);
+ printk("vs_gem_dumb_create size = %08x\n",args->size);
+ vs_obj = vs_gem_create_with_handle(dev, file, args->size,
+ &args->handle);
+ return PTR_ERR_OR_ZERO(vs_obj);
+}
+
+struct drm_gem_object *vs_gem_prime_import(struct drm_device *dev,
+ struct dma_buf *dma_buf)
+{
+ return drm_gem_prime_import_dev(dev, dma_buf, to_dma_dev(dev));
+}
+
+struct drm_gem_object *
+vs_gem_prime_import_sg_table(struct drm_device *dev,
+ struct dma_buf_attachment *attach,
+ struct sg_table *sgt)
+{
+ struct vs_gem_object *vs_obj;
+ int npages;
+ int ret;
+ struct scatterlist *s;
+ u32 i;
+ dma_addr_t expected;
+ size_t size = attach->dmabuf->size;
+#ifdef CONFIG_VERISILICON_MMU
+ u32 iova;
+ struct vs_drm_private *priv = dev->dev_private;
+
+ if (!priv->mmu) {
+ DRM_ERROR("invalid mmu.\n");
+ ret = -EINVAL;
+ return ERR_PTR(ret);
+ }
+#endif
+
+ size = PAGE_ALIGN(size);
+
+ vs_obj = vs_gem_alloc_object(dev, size);
+ if (IS_ERR(vs_obj))
+ return ERR_CAST(vs_obj);
+
+ expected = sg_dma_address(sgt->sgl);
+ for_each_sg(sgt->sgl, s, sgt->nents, i) {
+ if (sg_dma_address(s) != expected) {
+#ifndef CONFIG_VERISILICON_MMU
+ DRM_ERROR("sg_table is not contiguous");
+ ret = -EINVAL;
+ goto err;
+#endif
+ }
+
+ if (sg_dma_len(s) & (PAGE_SIZE-1)) {
+ ret = -EINVAL;
+ goto err;
+ }
+
+#ifdef CONFIG_VERISILICON_MMU
+ iova = 0;
+ npages = sg_dma_len(s) >> PAGE_SHIFT;
+ ret = dc_mmu_map_memory(priv->mmu, (u64)sg_dma_address(s),
+ npages, &iova, true, false);
+ if (ret) {
+ DRM_ERROR("failed to do mmu map.\n");
+ goto err;
+ }
+
+ if (i == 0)
+ vs_obj->iova = iova;
+#else
+ if (i == 0)
+ vs_obj->iova = sg_dma_address(s);
+#endif
+
+ expected = sg_dma_address(s) + sg_dma_len(s);
+ }
+
+ vs_obj->dma_addr = sg_dma_address(sgt->sgl);
+
+ npages = vs_obj->size >> PAGE_SHIFT;
+ vs_obj->pages = kvmalloc_array(npages, sizeof(struct page *),
+ GFP_KERNEL);
+ if (!vs_obj->pages) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ printk("====> %s, %d.\n", __func__, __LINE__);
+
+ //ret = drm_prime_sg_to_dma_addr_array(sgt, vs_obj->dma_addr,npages);
+ ret = drm_prime_sg_to_page_array(sgt, vs_obj->pages, npages);
+ if (ret)
+ goto err_free_page;
+
+ vs_obj->sgt = sgt;
+
+ return &vs_obj->base;
+
+err_free_page:
+ kvfree(vs_obj->pages);
+err:
+ vs_gem_free_object(&vs_obj->base);
+
+ return ERR_PTR(ret);
+}
+
+int vs_gem_prime_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma)
+{
+ int ret = 0;
+
+ ret = drm_gem_mmap_obj(obj, obj->size, vma);
+ if (ret < 0)
+ return ret;
+
+ return vs_gem_mmap_obj(obj, vma);
+}
+
+int vs_gem_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+ struct drm_gem_object *obj;
+ int ret;
+
+ ret = drm_gem_mmap(filp, vma);
+ if (ret)
+ return ret;
+
+ obj = vma->vm_private_data;
+
+ if (obj->import_attach)
+ return dma_buf_mmap(obj->dma_buf, vma, 0);
+
+ return vs_gem_mmap_obj(obj, vma);
+}
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2020 VeriSilicon Holdings Co., Ltd.
+ */
+
+#ifndef __VS_GEM_H__
+#define __VS_GEM_H__
+
+#include <linux/dma-buf.h>
+
+#include <drm/drm_gem.h>
+
+#include "vs_drv.h"
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 5, 0)
+#include <drm/drm_prime.h>
+#endif
+
+/*
+ *
+ * @base: drm gem object.
+ * @size: size requested from user
+ * @cookie: cookie returned by dma_alloc_attrs
+ * - not kernel virtual address with DMA_ATTR_NO_KERNEL_MAPPING
+ * @dma_addr: bus address(accessed by dma) to allocated memory region.
+ * - this address could be physical address without IOMMU and
+ * device address with IOMMU.
+ * @dma_attrs: attribute for DMA API
+ * @get_pages: flag for manually applying for non-contiguous memory.
+ * @pages: Array of backing pages.
+ * @sgt: Imported sg_table.
+ *
+ */
+struct vs_gem_object {
+ struct drm_gem_object base;
+ size_t size;
+ void *cookie;
+ dma_addr_t dma_addr;
+ u32 iova;
+ unsigned long dma_attrs;
+ bool get_pages;
+ struct page **pages;
+ struct sg_table *sgt;
+};
+
+static inline
+struct vs_gem_object *to_vs_gem_object(struct drm_gem_object *obj)
+{
+ return container_of(obj, struct vs_gem_object, base);
+}
+
+struct vs_gem_object *vs_gem_create_object(struct drm_device *dev,
+ size_t size);
+
+int vs_gem_prime_vmap(struct drm_gem_object *obj, struct dma_buf_map *map);
+void vs_gem_prime_vunmap(struct drm_gem_object *obj, struct dma_buf_map *map);
+
+int vs_gem_prime_mmap(struct drm_gem_object *obj,
+ struct vm_area_struct *vma);
+
+int vs_gem_dumb_create(struct drm_file *file_priv,
+ struct drm_device *drm,
+ struct drm_mode_create_dumb *args);
+
+int vs_gem_mmap(struct file *filp, struct vm_area_struct *vma);
+
+
+struct sg_table *vs_gem_prime_get_sg_table(struct drm_gem_object *obj);
+
+struct drm_gem_object *vs_gem_prime_import(struct drm_device *dev,
+ struct dma_buf *dma_buf);
+struct drm_gem_object *
+vs_gem_prime_import_sg_table(struct drm_device *dev,
+ struct dma_buf_attachment *attach,
+ struct sg_table *sgt);
+
+#endif /* __VS_GEM_H__ */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020 VeriSilicon Holdings Co., Ltd.
+ */
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_plane_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/vs_drm.h>
+
+#include "vs_type.h"
+#include "vs_crtc.h"
+#include "vs_plane.h"
+#include "vs_gem.h"
+#include "vs_fb.h"
+
+void vs_plane_destory(struct drm_plane *plane)
+{
+ struct vs_plane *vs_plane = to_vs_plane(plane);
+
+ drm_plane_cleanup(plane);
+ kfree(vs_plane);
+}
+
+static void vs_plane_reset(struct drm_plane *plane)
+{
+ struct vs_plane_state *state;
+ struct vs_plane *vs_plane = to_vs_plane(plane);
+
+ if (plane->state) {
+ __drm_atomic_helper_plane_destroy_state(plane->state);
+
+ state = to_vs_plane_state(plane->state);
+ kfree(state);
+ plane->state = NULL;
+ }
+
+ state = kzalloc(sizeof(*state), GFP_KERNEL);
+ if (state == NULL)
+ return;
+
+ __drm_atomic_helper_plane_reset(plane, &state->base);
+
+ state->degamma = VS_DEGAMMA_DISABLE;
+ state->degamma_changed = false;
+ state->base.zpos = vs_plane->id;
+
+ memset(&state->status, 0, sizeof(state->status));
+}
+
+static void _vs_plane_duplicate_blob(struct vs_plane_state *state,
+ struct vs_plane_state *ori_state)
+{
+ state->watermark = ori_state->watermark;
+ state->color_mgmt = ori_state->color_mgmt;
+ state->roi = ori_state->roi;
+
+ if(state->watermark)
+ drm_property_blob_get(state->watermark);
+ if (state->color_mgmt)
+ drm_property_blob_get(state->color_mgmt);
+ if (state->roi)
+ drm_property_blob_get(state->roi);
+}
+
+static int
+_vs_plane_set_property_blob_from_id(struct drm_device *dev,
+ struct drm_property_blob **blob,
+ uint64_t blob_id,
+ size_t expected_size)
+{
+ struct drm_property_blob *new_blob = NULL;
+
+ if (blob_id) {
+ new_blob = drm_property_lookup_blob(dev, blob_id);
+ if (new_blob == NULL)
+ return -EINVAL;
+
+ if (new_blob->length != expected_size) {
+ drm_property_blob_put(new_blob);
+ return -EINVAL;
+ }
+ }
+
+ drm_property_replace_blob(blob, new_blob);
+ drm_property_blob_put(new_blob);
+
+ return 0;
+}
+
+static struct drm_plane_state *
+vs_plane_atomic_duplicate_state(struct drm_plane *plane)
+{
+ struct vs_plane_state *ori_state;
+ struct vs_plane_state *state;
+
+ if (WARN_ON(!plane->state))
+ return NULL;
+
+ ori_state = to_vs_plane_state(plane->state);
+ state = kzalloc(sizeof(*state), GFP_KERNEL);
+ if (!state)
+ return NULL;
+
+ __drm_atomic_helper_plane_duplicate_state(plane, &state->base);
+
+ state->degamma = ori_state->degamma;
+ state->degamma_changed = ori_state->degamma_changed;
+
+ _vs_plane_duplicate_blob(state, ori_state);
+ memcpy(&state->status, &ori_state->status, sizeof(ori_state->status));
+
+ return &state->base;
+}
+
+static void vs_plane_atomic_destroy_state(struct drm_plane *plane,
+ struct drm_plane_state *state)
+{
+ struct vs_plane_state *vs_plane_state = to_vs_plane_state(state);
+
+ __drm_atomic_helper_plane_destroy_state(state);
+
+ drm_property_blob_put(vs_plane_state->watermark);
+ drm_property_blob_put(vs_plane_state->color_mgmt);
+ drm_property_blob_put(vs_plane_state->roi);
+ kfree(vs_plane_state);
+}
+
+static int vs_plane_atomic_set_property(struct drm_plane *plane,
+ struct drm_plane_state *state,
+ struct drm_property *property,
+ uint64_t val)
+{
+ struct drm_device *dev = plane->dev;
+ struct vs_plane *vs_plane = to_vs_plane(plane);
+ struct vs_plane_state *vs_plane_state = to_vs_plane_state(state);
+ int ret = 0;
+
+ if (property == vs_plane->degamma_mode) {
+ if (vs_plane_state->degamma != val) {
+ vs_plane_state->degamma = val;
+ vs_plane_state->degamma_changed = true;
+ } else {
+ vs_plane_state->degamma_changed = false;
+ }
+ } else if (property == vs_plane->watermark_prop) {
+ ret = _vs_plane_set_property_blob_from_id(dev,
+ &vs_plane_state->watermark,
+ val, sizeof(struct drm_vs_watermark));
+ return ret;
+ } else if (property == vs_plane->color_mgmt_prop) {
+ ret = _vs_plane_set_property_blob_from_id(dev,
+ &vs_plane_state->color_mgmt,
+ val, sizeof(struct drm_vs_color_mgmt));
+ return ret;
+ } else if (property == vs_plane->roi_prop) {
+ ret = _vs_plane_set_property_blob_from_id(dev,
+ &vs_plane_state->roi,
+ val, sizeof(struct drm_vs_roi));
+ return ret;
+ } else {
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int vs_plane_atomic_get_property(struct drm_plane *plane,
+ const struct drm_plane_state *state,
+ struct drm_property *property,
+ uint64_t *val)
+{
+ struct vs_plane *vs_plane = to_vs_plane(plane);
+ const struct vs_plane_state *vs_plane_state =
+ container_of(state, const struct vs_plane_state, base);
+
+ if (property == vs_plane->degamma_mode)
+ *val = vs_plane_state->degamma;
+ else if (property == vs_plane->watermark_prop)
+ *val = (vs_plane_state->watermark) ?
+ vs_plane_state->watermark->base.id : 0;
+ else if (property == vs_plane->color_mgmt_prop)
+ *val = (vs_plane_state->color_mgmt) ?
+ vs_plane_state->color_mgmt->base.id : 0;
+ else if (property == vs_plane->roi_prop)
+ *val = (vs_plane_state->roi) ?
+ vs_plane_state->roi->base.id : 0;
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+const struct drm_plane_funcs vs_plane_funcs = {
+ .update_plane = drm_atomic_helper_update_plane,
+ .disable_plane = drm_atomic_helper_disable_plane,
+ .destroy = vs_plane_destory,
+ .reset = vs_plane_reset,
+ .atomic_duplicate_state = vs_plane_atomic_duplicate_state,
+ .atomic_destroy_state = vs_plane_atomic_destroy_state,
+ .atomic_set_property = vs_plane_atomic_set_property,
+ .atomic_get_property = vs_plane_atomic_get_property,
+};
+
+static unsigned char vs_get_plane_number(struct drm_framebuffer *fb)
+{
+ const struct drm_format_info *info;
+
+ if (!fb)
+ return 0;
+
+ info = drm_format_info(fb->format->format);
+ if (!info || info->num_planes > MAX_NUM_PLANES)
+ return 0;
+
+ return info->num_planes;
+}
+
+
+static int vs_plane_atomic_check(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 vs_plane *vs_plane = to_vs_plane(plane);
+ struct drm_framebuffer *fb = new_plane_state->fb;
+ struct drm_crtc *crtc = new_plane_state->crtc;
+ struct vs_crtc *vs_crtc = to_vs_crtc(crtc);
+
+ if (!crtc || !fb)
+ return 0;
+
+ //return vs_plane->funcs->check(vs_crtc->dev, vs_plane, new_plane_state);
+ return vs_plane->funcs->check(vs_crtc->dev, plane, state);
+}
+
+static void vs_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);
+ unsigned char i, num_planes;
+ struct drm_framebuffer *fb;
+ struct vs_plane *vs_plane = to_vs_plane(plane);
+ //struct drm_plane_state *state = plane->state;
+ struct vs_crtc *vs_crtc = to_vs_crtc(new_state->crtc);
+ struct vs_plane_state *plane_state = to_vs_plane_state(new_state);
+ //struct drm_format_name_buf *name = &plane_state->status.format_name;
+
+ if (!new_state->fb || !new_state->crtc)
+ return;
+
+ fb = new_state->fb;
+
+ num_planes = vs_get_plane_number(fb);
+
+ for (i = 0; i < num_planes; i++) {
+ struct vs_gem_object *vs_obj;
+
+ vs_obj = vs_fb_get_gem_obj(fb, i);
+ vs_plane->dma_addr[i] = vs_obj->iova + fb->offsets[i];
+ }
+
+ plane_state->status.src = drm_plane_state_src(new_state);
+ plane_state->status.dest = drm_plane_state_dest(new_state);
+ //drm_get_format_name(fb->format->format, name);
+
+ vs_plane->funcs->update(vs_crtc->dev, vs_plane, plane, state);
+}
+
+static void vs_plane_atomic_disable(struct drm_plane *plane,
+ struct drm_atomic_state *state)
+{
+ struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state,
+ plane);
+ struct vs_plane *vs_plane = to_vs_plane(plane);
+ struct vs_crtc *vs_crtc = to_vs_crtc(old_state->crtc);
+
+ vs_plane->funcs->disable(vs_crtc->dev, vs_plane, old_state);
+}
+
+const struct drm_plane_helper_funcs vs_plane_helper_funcs = {
+ .atomic_check = vs_plane_atomic_check,
+ .atomic_update = vs_plane_atomic_update,
+ .atomic_disable = vs_plane_atomic_disable,
+};
+
+static const struct drm_prop_enum_list vs_degamma_mode_enum_list[] = {
+ { VS_DEGAMMA_DISABLE, "disabled" },
+ { VS_DEGAMMA_BT709, "preset degamma for BT709" },
+ { VS_DEGAMMA_BT2020, "preset degamma for BT2020" },
+};
+
+struct vs_plane *vs_plane_create(struct drm_device *drm_dev,
+ struct vs_plane_info *info,
+ unsigned int layer_num,
+ unsigned int possible_crtcs)
+{
+ struct vs_plane *plane;
+ int ret;
+
+ if (!info)
+ return NULL;
+ printk("====> %s, %d.\n", __func__, __LINE__);
+
+ plane = kzalloc(sizeof(struct vs_plane), GFP_KERNEL);
+ if (!plane)
+ return NULL;
+ printk("====> %s, %d.\n", __func__, __LINE__);
+
+ ret = drm_universal_plane_init(drm_dev, &plane->base, possible_crtcs,
+ &vs_plane_funcs, info->formats,
+ info->num_formats, info->modifiers, info->type,
+ info->name ? info->name : NULL);
+ if (ret)
+ goto err_free_plane;
+ printk("====> %s, %d.\n", __func__, __LINE__);
+
+ drm_plane_helper_add(&plane->base, &vs_plane_helper_funcs);
+ printk("====> %s, %d.\n", __func__, __LINE__);
+
+ /* Set up the plane properties */
+ if (info->degamma_size) {
+ plane->degamma_mode = drm_property_create_enum(drm_dev, 0,
+ "DEGAMMA_MODE",
+ vs_degamma_mode_enum_list,
+ ARRAY_SIZE(vs_degamma_mode_enum_list));
+
+ if (!plane->degamma_mode)
+ goto error_cleanup_plane;
+
+ drm_object_attach_property(&plane->base.base,
+ plane->degamma_mode,
+ VS_DEGAMMA_DISABLE);
+ }
+
+ if (info->rotation) {
+ ret = drm_plane_create_rotation_property(&plane->base,
+ DRM_MODE_ROTATE_0,
+ info->rotation);
+ if (ret)
+ goto error_cleanup_plane;
+ }
+
+ if (info->blend_mode) {
+ ret = drm_plane_create_blend_mode_property(&plane->base,
+ info->blend_mode);
+ if (ret)
+ goto error_cleanup_plane;
+ ret = drm_plane_create_alpha_property(&plane->base);
+ if (ret)
+ goto error_cleanup_plane;
+ }
+
+ if (info->color_encoding) {
+ ret = drm_plane_create_color_properties(&plane->base,
+ info->color_encoding,
+ BIT(DRM_COLOR_YCBCR_LIMITED_RANGE),
+ DRM_COLOR_YCBCR_BT709,
+ DRM_COLOR_YCBCR_LIMITED_RANGE);
+ if (ret)
+ goto error_cleanup_plane;
+ }
+
+ if (info->zpos != 255) {
+ ret = drm_plane_create_zpos_property(&plane->base, info->zpos, 0, layer_num - 1);
+ if (ret)
+ goto error_cleanup_plane;
+ }
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0)
+ else {
+ ret = drm_plane_create_zpos_immutable_property(&plane->base,
+ info->zpos);
+ if (ret)
+ goto error_cleanup_plane;
+ }
+#endif
+
+ if (info->watermark) {
+ plane->watermark_prop = drm_property_create(drm_dev, DRM_MODE_PROP_BLOB,
+ "WATERMARK", 0);
+ if (!plane->watermark_prop)
+ goto error_cleanup_plane;
+
+ drm_object_attach_property(&plane->base.base, plane->watermark_prop, 0);
+ }
+
+ if (info->color_mgmt) {
+ plane->color_mgmt_prop = drm_property_create(drm_dev, DRM_MODE_PROP_BLOB,
+ "COLOR_CONFIG", 0);
+ if (!plane->color_mgmt_prop)
+ goto error_cleanup_plane;
+
+ drm_object_attach_property(&plane->base.base, plane->color_mgmt_prop, 0);
+ }
+
+ if (info->roi) {
+ plane->roi_prop = drm_property_create(drm_dev, DRM_MODE_PROP_BLOB,
+ "ROI", 0);
+ if (!plane->roi_prop)
+ goto error_cleanup_plane;
+
+ drm_object_attach_property(&plane->base.base, plane->roi_prop, 0);
+ }
+ printk("====> %s, %d.\n", __func__, __LINE__);
+
+ return plane;
+
+error_cleanup_plane:
+ drm_plane_cleanup(&plane->base);
+err_free_plane:
+ kfree(plane);
+ return NULL;
+}
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2020 VeriSilicon Holdings Co., Ltd.
+ */
+
+#ifndef __VS_PLANE_H__
+#define __VS_PLANE_H__
+
+#include <drm/drm_plane_helper.h>
+#include <drm/drm_fourcc.h>
+
+#include "vs_type.h"
+#include "vs_fb.h"
+
+#define MAX_NUM_PLANES 3 /* colour format plane */
+
+struct vs_plane;
+
+struct vs_plane_funcs {
+ void (*update)(struct device *dev, struct vs_plane *plane, struct drm_plane *drm_plane,
+ struct drm_atomic_state *state);
+ void (*disable)(struct device *dev, struct vs_plane *plane,
+ struct drm_plane_state *old_state);
+ int (*check)(struct device *dev, struct drm_plane *plane,
+ struct drm_atomic_state *state);
+};
+
+struct vs_plane_status {
+ u32 tile_mode;
+ struct drm_rect src;
+ struct drm_rect dest;
+ //struct drm_format_name_buf format_name;
+};
+
+struct vs_plane_state {
+ struct drm_plane_state base;
+ struct vs_plane_status status; /* for debugfs */
+
+ struct drm_property_blob *watermark;
+ struct drm_property_blob *color_mgmt;
+ struct drm_property_blob *roi;
+
+ u32 degamma;
+ bool degamma_changed;
+};
+
+struct vs_plane {
+ struct drm_plane base;
+ u8 id;
+ dma_addr_t dma_addr[MAX_NUM_PLANES];
+
+ struct drm_property *degamma_mode;
+ struct drm_property *watermark_prop;
+ struct drm_property *color_mgmt_prop;
+ struct drm_property *roi_prop;
+
+ const struct vs_plane_funcs *funcs;
+};
+
+void vs_plane_destory(struct drm_plane *plane);
+
+struct vs_plane *vs_plane_create(struct drm_device *drm_dev,
+ struct vs_plane_info *info,
+ unsigned int layer_num,
+ unsigned int possible_crtcs);
+
+static inline struct vs_plane *to_vs_plane(struct drm_plane *plane)
+{
+ return container_of(plane, struct vs_plane, base);
+}
+
+static inline struct vs_plane_state *
+to_vs_plane_state(struct drm_plane_state *state)
+{
+ return container_of(state, struct vs_plane_state, base);
+}
+#endif /* __VS_PLANE_H__ */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020 VeriSilicon Holdings Co., Ltd.
+ */
+#include <linux/version.h>
+#include <linux/component.h>
+#include <linux/of_device.h>
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 5, 0)
+#include <linux/module.h>
+
+#include <drm/drm_bridge.h>
+#else
+#include <drm/drmP.h>
+#endif
+
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_of.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+
+#include "vs_crtc.h"
+#include "vs_simple_enc.h"
+
+static const struct simple_encoder_priv hdmi_priv = {
+ .encoder_type = DRM_MODE_ENCODER_TMDS
+};
+
+static const struct simple_encoder_priv dsi_priv = {
+ .encoder_type = DRM_MODE_ENCODER_DSI
+};
+
+static const struct drm_encoder_funcs encoder_funcs = {
+ .destroy = drm_encoder_cleanup
+};
+
+static inline struct simple_encoder *to_simple_encoder(struct drm_encoder *enc)
+{
+ return container_of(enc, struct simple_encoder, encoder);
+}
+
+static int encoder_parse_dt(struct device *dev)
+{
+ struct simple_encoder *simple = dev_get_drvdata(dev);
+ int ret = 0;
+ int cnt, i;
+ u32 *vals;
+ u32 *masks;
+
+ simple->dss_regmap = syscon_regmap_lookup_by_phandle(dev->of_node,
+ "verisilicon,dss-syscon");
+
+ if (IS_ERR(simple->dss_regmap)) {
+ if (PTR_ERR(simple->dss_regmap) != -ENODEV) {
+ dev_err(dev, "failed to get dss-syscon\n");
+ ret = PTR_ERR(simple->dss_regmap);
+ goto err;
+ }
+ simple->dss_regmap = NULL;
+ goto err;
+ }
+
+ cnt = of_property_count_elems_of_size(dev->of_node,
+ "verisilicon,mux-mask", 4);
+ if (!cnt) {
+ ret = cnt;
+ goto err;
+ }
+
+ simple->dss_regdatas = devm_kzalloc(dev,
+ sizeof(*simple->dss_regdatas) * cnt, GFP_KERNEL);
+
+ masks = kcalloc(cnt, sizeof(*masks), GFP_KERNEL);
+ if (!masks) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ vals = kcalloc(cnt, sizeof(*vals), GFP_KERNEL);
+ if (!vals) {
+ ret = -ENOMEM;
+ goto err_free_masks;
+ }
+
+ ret = of_property_read_u32_array(
+ dev->of_node, "verisilicon,mux-mask", masks, cnt);
+ if (ret)
+ goto err_free_vals;
+
+ ret = of_property_read_u32_array(
+ dev->of_node, "verisilicon,mux-val", vals, cnt);
+ if (ret)
+ goto err_free_vals;
+
+ for (i = 0; i < cnt; i++) {
+ simple->dss_regdatas[i].mask = masks[i];
+ simple->dss_regdatas[i].value = vals[i];
+ }
+
+err_free_vals:
+ kfree(vals);
+err_free_masks:
+ kfree(masks);
+err:
+ return ret;
+}
+
+void encoder_atomic_enable(struct drm_encoder *encoder,
+ struct drm_atomic_state *state)
+{
+ struct simple_encoder *simple = to_simple_encoder(encoder);
+ struct dss_data *data = simple->dss_regdatas;
+ int crtc_id;
+
+ if (!simple->dss_regmap)
+ return;
+
+ crtc_id = drm_of_encoder_active_endpoint_id(
+ simple->dev->of_node, encoder);
+
+ regmap_update_bits(simple->dss_regmap, 0, data[crtc_id].mask,
+ data[crtc_id].value);
+}
+
+int encoder_atomic_check(struct drm_encoder *encoder,
+ struct drm_crtc_state *crtc_state,
+ struct drm_connector_state *conn_state)
+{
+ struct vs_crtc_state *vs_crtc_state = to_vs_crtc_state(crtc_state);
+ struct drm_connector *connector = conn_state->connector;
+ int ret = 0;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0)
+ struct drm_bridge *first_bridge = drm_bridge_chain_get_first_bridge(encoder);
+ struct drm_bridge_state *bridge_state = ERR_PTR(-EINVAL);
+#endif
+
+ vs_crtc_state->encoder_type = encoder->encoder_type;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0)
+ if (first_bridge && first_bridge->funcs->atomic_duplicate_state)
+ bridge_state = drm_atomic_get_bridge_state(
+ crtc_state->state, first_bridge);
+
+ if (IS_ERR(bridge_state)) {
+ if (connector->display_info.num_bus_formats)
+ vs_crtc_state->output_fmt = connector->display_info.bus_formats[0];
+ else
+ vs_crtc_state->output_fmt = MEDIA_BUS_FMT_FIXED;
+ } else {
+ vs_crtc_state->output_fmt = bridge_state->input_bus_cfg.format;
+ }
+#else
+ if (connector->display_info.num_bus_formats)
+ vs_crtc_state->output_fmt = connector->display_info.bus_formats[0];
+ else
+ vs_crtc_state->output_fmt = MEDIA_BUS_FMT_RGB888_1X24;
+#endif
+
+ switch (vs_crtc_state->output_fmt) {
+ case MEDIA_BUS_FMT_FIXED:
+ case MEDIA_BUS_FMT_RGB565_1X16:
+ case MEDIA_BUS_FMT_RGB666_1X18:
+ case MEDIA_BUS_FMT_RGB888_1X24:
+ case MEDIA_BUS_FMT_RGB666_1X24_CPADHI:
+ case MEDIA_BUS_FMT_RGB101010_1X30:
+ case MEDIA_BUS_FMT_UYYVYY8_0_5X24:
+ case MEDIA_BUS_FMT_UYVY8_1X16:
+ case MEDIA_BUS_FMT_YUV8_1X24:
+ case MEDIA_BUS_FMT_UYYVYY10_0_5X30:
+ case MEDIA_BUS_FMT_UYVY10_1X20:
+ case MEDIA_BUS_FMT_YUV10_1X30:
+ ret = 0;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ /* If MEDIA_BUS_FMT_FIXED, set it to default value */
+ if (vs_crtc_state->output_fmt == MEDIA_BUS_FMT_FIXED)
+ vs_crtc_state->output_fmt = MEDIA_BUS_FMT_RGB888_1X24;
+
+ return ret;
+}
+
+static const struct drm_encoder_helper_funcs encoder_helper_funcs = {
+ .atomic_enable = encoder_atomic_enable,
+ .atomic_check = encoder_atomic_check,
+};
+
+static int encoder_bind(struct device *dev, struct device *master, void *data)
+{
+ struct drm_device *drm_dev = data;
+ struct simple_encoder *simple = dev_get_drvdata(dev);
+ struct drm_encoder *encoder;
+ struct drm_bridge *bridge;
+ int ret;
+
+ encoder = &simple->encoder;
+
+ /* Encoder. */
+ ret = drm_encoder_init(drm_dev, encoder, &encoder_funcs,
+ simple->priv->encoder_type, NULL);
+ if (ret)
+ return ret;
+
+ drm_encoder_helper_add(encoder, &encoder_helper_funcs);
+
+ encoder->possible_crtcs =
+ drm_of_find_possible_crtcs(drm_dev, dev->of_node);
+
+ /* output port is port1*/
+ ret = drm_of_find_panel_or_bridge(dev->of_node, 1, -1, NULL, &bridge);
+ if (ret)
+ goto err;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 7, 0)
+ ret = drm_bridge_attach(encoder, bridge, NULL, 0);
+#else
+ ret = drm_bridge_attach(encoder, bridge, NULL);
+#endif
+ if (ret)
+ goto err;
+
+ return 0;
+err:
+ drm_encoder_cleanup(encoder);
+
+ return ret;
+}
+
+static void encoder_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct simple_encoder *simple = dev_get_drvdata(dev);
+
+ drm_encoder_cleanup(&simple->encoder);
+}
+
+static const struct component_ops encoder_component_ops = {
+ .bind = encoder_bind,
+ .unbind = encoder_unbind,
+};
+
+static const struct of_device_id simple_encoder_dt_match[] = {
+ { .compatible = "verisilicon,hdmi-encoder", .data = &hdmi_priv},
+ { .compatible = "verisilicon,dp-encoder", .data = &hdmi_priv},
+ { .compatible = "verisilicon,dsi-encoder", .data = &dsi_priv},
+ {},
+};
+MODULE_DEVICE_TABLE(of, simple_encoder_dt_match);
+
+static int encoder_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct simple_encoder *simple;
+ int ret;
+
+ simple = devm_kzalloc(dev, sizeof(*simple), GFP_KERNEL);
+ if (!simple)
+ return -ENOMEM;
+
+ simple->priv = of_device_get_match_data(dev);
+
+ simple->dev = dev;
+
+ dev_set_drvdata(dev, simple);
+
+ ret = encoder_parse_dt(dev);
+ if (ret)
+ return ret;
+
+ return component_add(dev, &encoder_component_ops);
+}
+
+static int encoder_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+
+ component_del(dev, &encoder_component_ops);
+
+ dev_set_drvdata(dev, NULL);
+
+ return 0;
+}
+
+struct platform_driver simple_encoder_driver = {
+ .probe = encoder_probe,
+ .remove = encoder_remove,
+ .driver = {
+ .name = "vs-simple-encoder",
+ .of_match_table = of_match_ptr(simple_encoder_dt_match),
+ },
+};
+
+MODULE_DESCRIPTION("Simple Encoder Driver");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2020 VeriSilicon Holdings Co., Ltd.
+ */
+
+#ifndef __VS_SIMPLE_ENC_H_
+#define __VS_SIMPLE_ENC_H_
+
+struct simple_encoder_priv {
+ unsigned char encoder_type;
+};
+
+struct dss_data {
+ u32 mask;
+ u32 value;
+};
+
+struct simple_encoder {
+ struct drm_encoder encoder;
+ struct device *dev;
+ const struct simple_encoder_priv *priv;
+ struct regmap *dss_regmap;
+ struct dss_data *dss_regdatas;
+};
+
+extern struct platform_driver simple_encoder_driver;
+#endif /* __VS_SIMPLE_ENC_H_ */
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2020 VeriSilicon Holdings Co., Ltd.
+ */
+
+#ifndef __VS_TYPE_H__
+#define __VS_TYPE_H__
+
+#include <linux/version.h>
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 5, 0)
+#include <drm/drmP.h>
+#endif
+#include <drm/drm_plane.h>
+#include <drm/drm_plane_helper.h>
+
+struct vs_plane_info {
+ const char *name;
+ u8 id;
+ enum drm_plane_type type;
+ unsigned int num_formats;
+ const u32 *formats;
+ u8 num_modifiers;
+ const u64 *modifiers;
+ unsigned int min_width;
+ unsigned int min_height;
+ unsigned int max_width;
+ unsigned int max_height;
+ unsigned int rotation;
+ unsigned int blend_mode;
+ unsigned int color_encoding;
+
+ /* 0 means no de-gamma LUT */
+ unsigned int degamma_size;
+
+ int min_scale; /* 16.16 fixed point */
+ int max_scale; /* 16.16 fixed point */
+
+ u8 zpos; /* default zorder value,
+ * and 255 means unsupported zorder capability */
+
+ bool watermark;
+ bool color_mgmt;
+ bool roi;
+};
+
+struct vs_dc_info {
+ const char *name;
+
+ u8 panel_num;
+
+ /* planes */
+ u8 plane_num;
+ const struct vs_plane_info *planes;
+
+ u8 layer_num;
+ unsigned int max_bpc;
+ unsigned int color_formats;
+
+ /* 0 means no gamma LUT */
+ u16 gamma_size;
+ u8 gamma_bits;
+
+ u16 pitch_alignment;
+
+ bool pipe_sync;
+ bool mmu_prefetch;
+ bool background;
+ bool panel_sync;
+ bool cap_dec;
+};
+
+#endif /* __VS_TYPE_H__ */
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020 VeriSilicon Holdings Co., Ltd.
+ */
+
+#include <linux/component.h>
+#include <linux/of_platform.h>
+#include <linux/media-bus-format.h>
+
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_of.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_plane_helper.h>
+#include <drm/drm_atomic_helper.h>
+
+#include "vs_virtual.h"
+#include "vs_dc.h"
+#include "vs_gem.h"
+
+static unsigned char __get_bpp(struct vs_virtual_display *vd)
+{
+ if (vd->bus_format == MEDIA_BUS_FMT_RGB101010_1X30)
+ return 10;
+ return 8;
+}
+
+static void vd_dump_destroy(struct vs_virtual_display *vd)
+{
+ if (vd->dump_blob.data) {
+ vunmap(vd->dump_blob.data);
+ vd->dump_blob.data = NULL;
+ }
+ vd->dump_blob.size = 0;
+
+ debugfs_remove(vd->dump_debugfs);
+ vd->dump_debugfs = NULL;
+
+ if (vd->dump_obj) {
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 9, 0)
+ drm_gem_object_put(&vd->dump_obj->base);
+#else
+ drm_gem_object_put_unlocked(&vd->dump_obj->base);
+#endif
+ vd->dump_obj = NULL;
+ }
+}
+
+static void vd_dump_create(struct vs_virtual_display *vd,
+ struct drm_display_mode *mode)
+{
+ struct drm_device *drm_dev = vd->encoder.dev;
+ struct vs_dc *dc = dev_get_drvdata(vd->dc);
+ struct vs_gem_object *obj;
+ unsigned int pitch, size;
+ void *kvaddr;
+ char *name;
+
+ if (!dc->funcs)
+ return;
+
+ vd_dump_destroy(vd);
+
+ /* dump in 4bytes XRGB format */
+ pitch = mode->hdisplay * 4;
+ pitch = ALIGN(pitch, dc->hw.info->pitch_alignment);
+ size = PAGE_ALIGN(pitch * mode->vdisplay);
+
+ obj = vs_gem_create_object(drm_dev, size);
+ if (IS_ERR(obj))
+ return;
+
+ vd->dump_obj = obj;
+ vd->pitch = pitch;
+
+ kvaddr = vmap(obj->pages, obj->size >> PAGE_SHIFT, VM_MAP,
+ pgprot_writecombine(PAGE_KERNEL));
+ if (!kvaddr)
+ goto err;
+
+ vd->dump_blob.data = kvaddr;
+ vd->dump_blob.size = obj->size;
+
+ name = kasprintf(GFP_KERNEL, "%dx%d-XRGB-%d.raw",
+ mode->hdisplay, mode->vdisplay,
+ __get_bpp(vd));
+ if (!name)
+ goto err;
+
+ vd->dump_debugfs = debugfs_create_blob(name, 0444,
+ vd->connector.debugfs_entry,
+ &vd->dump_blob);
+ kfree(name);
+
+ return;
+
+err:
+ vd_dump_destroy(vd);
+
+}
+
+static void vd_encoder_destroy(struct drm_encoder *encoder)
+{
+ struct vs_virtual_display *vd;
+
+ drm_encoder_cleanup(encoder);
+ vd = to_virtual_display_with_encoder(encoder);
+ vd_dump_destroy(vd);
+}
+
+static const struct drm_encoder_funcs vd_encoder_funcs = {
+ .destroy = vd_encoder_destroy
+};
+
+static void vd_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ struct vs_virtual_display *vd;
+
+ vd = to_virtual_display_with_encoder(encoder);
+ vd_dump_create(vd, adjusted_mode);
+}
+
+static void vd_encoder_disable(struct drm_encoder *encoder)
+{
+ struct vs_virtual_display *vd;
+ struct vs_dc *dc;
+
+ vd = to_virtual_display_with_encoder(encoder);
+ dc = dev_get_drvdata(vd->dc);
+ if (dc->funcs && dc->funcs->dump_disable)
+ dc->funcs->dump_disable(vd->dc);
+}
+
+static void vd_encoder_enable(struct drm_encoder *encoder)
+{
+ struct vs_virtual_display *vd;
+ struct vs_dc *dc;
+
+ vd = to_virtual_display_with_encoder(encoder);
+ dc = dev_get_drvdata(vd->dc);
+ if (dc->funcs && dc->funcs->dump_enable && vd->dump_obj)
+ dc->funcs->dump_enable(vd->dc, vd->dump_obj->iova,
+ vd->pitch);
+}
+
+static const struct drm_encoder_helper_funcs vd_encoder_helper_funcs = {
+ .mode_set = vd_mode_set,
+ .enable = vd_encoder_enable,
+ .disable = vd_encoder_disable,
+};
+
+static int vd_get_modes(struct drm_connector *connector)
+{
+ struct drm_device *dev = connector->dev;
+ struct drm_display_mode *mode = NULL;
+ unsigned int i;
+ static const struct display_mode {
+ int w, h;
+ } cvt_mode[] = {
+ {640, 480},
+ {720, 480},
+ {800, 600},
+ {1024, 768},
+ {1280, 720},
+ {1280, 1024},
+ {1400, 1050},
+ {1680, 1050},
+ {1600, 1200},
+ {1920, 1080},
+ {1920, 1200},
+ {3840, 2160}
+ };
+
+ for (i = 0; i < ARRAY_SIZE(cvt_mode); i++) {
+ mode = drm_cvt_mode(dev, cvt_mode[i].w, cvt_mode[i].h,
+ 60, false, false, false);
+ drm_mode_probed_add(connector, mode);
+ }
+ return 0;
+}
+
+static struct drm_encoder *vd_best_encoder(struct drm_connector *connector)
+{
+ struct vs_virtual_display *vd;
+
+ vd = to_virtual_display_with_connector(connector);
+ return &vd->encoder;
+}
+
+static enum drm_mode_status vd_mode_valid(struct drm_connector *connector,
+ struct drm_display_mode *mode)
+{
+ return MODE_OK;
+}
+
+static const struct drm_connector_helper_funcs vd_connector_helper_funcs = {
+ .get_modes = vd_get_modes,
+ .mode_valid = vd_mode_valid,
+ .best_encoder = vd_best_encoder,
+};
+
+static void vd_connector_destroy(struct drm_connector *connector)
+{
+ drm_connector_unregister(connector);
+ drm_connector_cleanup(connector);
+}
+
+static enum drm_connector_status
+vd_connector_detect(struct drm_connector *connector, bool force)
+{
+ return connector_status_connected;
+}
+
+static const struct drm_connector_funcs vd_connector_funcs = {
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = vd_connector_destroy,
+ .detect = vd_connector_detect,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+ .reset = drm_atomic_helper_connector_reset,
+};
+
+static int vd_bind(struct device *dev, struct device *master, void *data)
+{
+ struct drm_device *drm_dev = data;
+ struct vs_virtual_display *vd = dev_get_drvdata(dev);
+ struct drm_encoder *encoder;
+ struct drm_connector *connector;
+ struct device_node *ep, *np;
+ struct platform_device *pdev;
+ int ret;
+
+ /* Encoder */
+ encoder = &vd->encoder;
+ ret = drm_encoder_init(drm_dev, encoder, &vd_encoder_funcs,
+ DRM_MODE_ENCODER_VIRTUAL, NULL);
+ if (ret)
+ return ret;
+ drm_encoder_helper_add(encoder, &vd_encoder_helper_funcs);
+
+ encoder->possible_crtcs =
+ drm_of_find_possible_crtcs(drm_dev, dev->of_node);
+
+ /* Connector */
+ connector = &vd->connector;
+ ret = drm_connector_init(drm_dev, connector, &vd_connector_funcs,
+ DRM_MODE_CONNECTOR_VIRTUAL);
+ if (ret)
+ goto connector_init_err;
+ drm_connector_helper_add(connector, &vd_connector_helper_funcs);
+ connector->interlace_allowed = false;
+ connector->doublescan_allowed = false;
+ connector->dpms = DRM_MODE_DPMS_OFF;
+ connector->polled = DRM_CONNECTOR_POLL_CONNECT |
+ DRM_CONNECTOR_POLL_DISCONNECT;
+ ret = drm_connector_register(connector);
+ if (ret)
+ goto connector_reg_err;
+
+ drm_display_info_set_bus_formats(&connector->display_info,
+ &vd->bus_format, 1);
+
+ /* attach */
+ ret = drm_connector_attach_encoder(connector, encoder);
+ if (ret)
+ goto attach_err;
+
+ ep = of_graph_get_endpoint_by_regs(dev->of_node, 0, -1);
+ if (!ep) {
+ ret = -EINVAL;
+ goto attach_err;
+ }
+
+ np = of_graph_get_remote_port_parent(ep);
+ of_node_put(ep);
+ if (!np) {
+ ret = -EINVAL;
+ goto attach_err;
+ }
+
+ pdev = of_find_device_by_node(np);
+ of_node_put(np);
+ if (!pdev) {
+ ret = -EPROBE_DEFER;
+ goto attach_err;
+ }
+ get_device(&pdev->dev);
+ vd->dc = &pdev->dev;
+
+ return 0;
+
+attach_err:
+ drm_connector_unregister(connector);
+connector_reg_err:
+ drm_connector_cleanup(connector);
+connector_init_err:
+ drm_encoder_cleanup(encoder);
+ return ret;
+}
+
+static void vd_unbind(struct device *dev, struct device *master, void *data)
+{
+ struct vs_virtual_display *vd = dev_get_drvdata(dev);
+
+ drm_connector_unregister(&vd->connector);
+ drm_connector_cleanup(&vd->connector);
+ drm_encoder_cleanup(&vd->encoder);
+}
+
+const struct component_ops vd_component_ops = {
+ .bind = vd_bind,
+ .unbind = vd_unbind,
+};
+
+static const struct of_device_id vd_driver_dt_match[] = {
+ { .compatible = "verisilicon,virtual_display", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, vd_driver_dt_match);
+
+static int vd_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct vs_virtual_display *vd;
+ unsigned char bpp;
+
+ vd = devm_kzalloc(dev, sizeof(*vd), GFP_KERNEL);
+ if (!vd)
+ return -ENOMEM;
+
+ vd->bus_format = MEDIA_BUS_FMT_RGB101010_1X30;
+ of_property_read_u8(dev->of_node, "bpp", &bpp);
+ if (bpp == 8)
+ vd->bus_format = MEDIA_BUS_FMT_RBG888_1X24;
+
+ dev_set_drvdata(dev, vd);
+
+ return component_add(dev, &vd_component_ops);
+}
+
+static int vd_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+
+ component_del(dev, &vd_component_ops);
+
+ dev_set_drvdata(dev, NULL);
+
+ return 0;
+}
+
+struct platform_driver virtual_display_platform_driver = {
+ .probe = vd_probe,
+ .remove = vd_remove,
+ .driver = {
+ .name = "vs-virtual-display",
+ .of_match_table = of_match_ptr(vd_driver_dt_match),
+ },
+};
+
+MODULE_DESCRIPTION("VeriSilicon Virtual Display Driver");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2020 VeriSilicon Holdings Co., Ltd.
+ */
+
+
+#ifndef __VS_VIRTUAL_H_
+#define __VS_VIRTUAL_H_
+
+#include <linux/debugfs.h>
+
+struct vs_virtual_display {
+ struct drm_encoder encoder;
+ struct drm_connector connector;
+ struct device *dc;
+ u32 bus_format;
+
+ struct dentry *dump_debugfs;
+ struct debugfs_blob_wrapper dump_blob;
+ struct vs_gem_object *dump_obj;
+ unsigned int pitch;
+};
+
+static inline struct vs_virtual_display *
+to_virtual_display_with_connector(struct drm_connector *connector)
+{
+ return container_of(connector, struct vs_virtual_display, connector);
+}
+
+static inline struct vs_virtual_display *
+to_virtual_display_with_encoder(struct drm_encoder *encoder)
+{
+ return container_of(encoder, struct vs_virtual_display, encoder);
+}
+
+extern struct platform_driver virtual_display_platform_driver;
+#endif /* __VS_VIRTUAL_H_ */
source "drivers/phy/ti/Kconfig"
source "drivers/phy/intel/Kconfig"
source "drivers/phy/xilinx/Kconfig"
+source "drivers/phy/m31/Kconfig"
endmenu
st/ \
tegra/ \
ti/ \
- xilinx/
+ xilinx/ \
+ m31/
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Phy drivers for Starfive platforms
+#
+
+config PHY_M31_DPHY_RX0
+ tristate "Starfive M31 MIPI DPHY TX Driver"
+ select GENERIC_PHY_MIPI_DPHY
+ select GENERIC_PHY
+ help
+ Enable this to support the starfive MIPI DPHY TX0
+
+ To compile this driver as a module, choose M here: the module
+ will be called phy-starfive-dphy-tx0.
+
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_PHY_M31_DPHY_RX0) += phy-m31-dphy-tx0.o
+
--- /dev/null
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+/*
+ * Rockchip MIPI Synopsys DPHY RX0 driver
+ *
+ * Copyright (C) 2019 Collabora, Ltd.
+ *
+ * Based on:
+ *
+ * drivers/media/platform/rockchip/isp1/mipi_dphy_sy.c
+ * in https://chromium.googlesource.com/chromiumos/third_party/kernel,
+ * chromeos-4.4 branch.
+ *
+ * Copyright (C) 2017 Fuzhou Rockchip Electronics Co., Ltd.
+ * Jacob Chen <jacob2.chen@rock-chips.com>
+ * Shunqian Zheng <zhengsq@rock-chips.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/phy/phy.h>
+#include <linux/phy/phy-mipi-dphy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+//syscfg registers
+#define SCFG_DSI_CSI_SEL 0x2c
+#define SCFG_PHY_RESETB 0x30
+#define SCFG_REFCLK_SEL 0x34
+#define SCFG_DBUS_PW_PLL_SSC_LD0 0x38
+#define SCFG_GRS_CDTX_PLL 0x3c
+
+#define SCFG_RG_CDTX_PLL_FBK_PRE 0x44
+#define SCFG_RG_CLANE_DLANE_TIME 0x58
+#define SCFG_RG_CLANE_HS_TIME 0x58
+
+#define SCFG_RG_EXTD_CYCLE_SEL 0x5c
+
+#define SCFG_L0N_L0P_HSTX 0x60
+#define SCFG_L1N_L1P_HSTX 0x64
+#define SCFG_L2N_L2P_HSTX 0x68
+#define SCFG_L3N_L3P_HSTX 0x6c
+#define SCFG_L4N_L4P_HSTX 0x70
+#define SCFG_LX_SWAP_SEL 0x78
+
+#define SCFG_HS_PRE_ZERO_T_D 0xc4
+#define SCFG_TXREADY_SRC_SEL_D 0xc8
+#define SCFG_HS_PRE_ZERO_T_C 0xd4
+#define SCFG_TXREADY_SRC_SEL_C 0xd8
+
+//reg SCFG_LX_SWAP_SEL
+#define OFFSET_CFG_L0_SWAP_SEL 0
+#define OFFSET_CFG_L1_SWAP_SEL 3
+#define OFFSET_CFG_L2_SWAP_SEL 6
+#define OFFSET_CFG_L3_SWAP_SEL 9
+#define OFFSET_CFG_L4_SWAP_SEL 12
+
+//reg SCFG_DBUS_PW_PLL_SSC_LD0
+#define OFFSET_SCFG_CFG_DATABUD16_SEL 0
+#define OFFSET_SCFG_PWRON_READY_N 1
+#define OFFSET_RG_CDTX_PLL_FM_EN 2
+#define OFFSET_SCFG_PLLSSC_EN 3
+#define OFFSET_RG_CDTX_PLL_LDO_STB_X2_EN 4
+
+//reg SCFG_RG_CLANE_DLANE_TIME
+#define OFFSET_DHS_PRE_TIME 8
+#define OFFSET_DHS_TRIAL_TIME 16
+#define OFFSET_DHS_ZERO_TIME 24
+
+//reg SCFG_RG_CLANE_HS_TIME
+#define OFFSET_CHS_PRE_TIME 8
+#define OFFSET_CHS_TRIAL_TIME 16
+#define OFFSET_CHS_ZERO_TIME 24
+
+//dsitx registers
+#define VID_MCTL_MAIN_DATA_CTL 0x04
+#define VID_MCTL_MAIN_PHY_CTL 0x08
+#define VID_MCTL_MAIN_EN 0x0c
+#define VID_MAIN_CTRL_ADDR 0xb0
+#define VID_VSIZE1_ADDR 0xb4
+#define VID_VSIZE2_ADDR 0xb8
+#define VID_HSIZE1_ADDR 0xc0
+#define VID_HSIZE2_ADDR 0xc4
+#define VID_BLKSIZE1_ADDR 0xCC
+#define VID_BLKSIZE2_ADDR 0xd0
+#define VID_PCK_TIME_ADDR 0xd8
+#define VID_DPHY_TIME_ADDR 0xdc
+#define VID_ERR_COLOR1_ADDR 0xe0
+#define VID_ERR_COLOR2_ADDR 0xe4
+#define VID_VPOS_ADDR 0xe8
+#define VID_HPOS_ADDR 0xec
+#define VID_MODE_STAT_ADDR 0xf0
+#define VID_VCA_SET1_ADDR 0xf4
+#define VID_VCA_SET2_ADDR 0xf8
+
+
+#define VID_MODE_STAT_CLR_ADDR 0x160
+#define VID_MODE_STAT_FLAG_ADDR 0x180
+
+#define TVG_CTRL_ADDR 0x0fc
+#define TVG_IMG_SIZE_ADDR 0x100
+#define TVG_COLOR1_ADDR 0x104
+#define TVG_COLOR1BIT_ADDR 0x108
+#define TVG_COLOR2_ADDR 0x10c
+#define TVG_COLOR2BIT_ADDR 0x110
+#define TVG_STAT_ADDR 0x114
+#define TVG_STAT_CTRL_ADDR 0x144
+#define TVG_STAT_CLR_ADDR 0x164
+#define TVG_STAT_FLAG_ADDR 0x184
+
+#define DPI_IRQ_EN_ADDR 0x1a0
+#define DPI_IRQ_CLR_ADDR 0x1a4
+#define DPI_IRQ_STAT_ADDR 0x1a4
+#define DPI_CFG_ADDR 0x1ac
+
+
+//sysrst registers
+#define SRST_ASSERT0 0x00
+#define SRST_STATUS0 0x04
+/* Definition controller bit for syd rst registers */
+#define BIT_RST_DSI_DPI_PIX 17
+
+struct sf_dphy {
+ struct device *dev;
+ void __iomem *topsys;
+
+ struct clk_bulk_data *clks;
+
+ struct phy_configure_opts_mipi_dphy config;
+
+ u8 hsfreq;
+
+ struct phy *phy;
+};
+
+static u32 top_sys_read32(struct sf_dphy *priv, u32 reg)
+{
+ return ioread32(priv->topsys + reg);
+}
+
+
+static inline void top_sys_write32(struct sf_dphy *priv, u32 reg, u32 val)
+{
+ iowrite32(val, priv->topsys + reg);
+}
+
+static void dsi_csi2tx_sel(struct sf_dphy *priv, int sel)
+{
+ u32 temp = 0;
+ temp = top_sys_read32(priv, SCFG_DSI_CSI_SEL);
+ temp &= ~(0x1);
+ temp |= (sel & 0x1);
+ top_sys_write32(priv, SCFG_DSI_CSI_SEL, temp);
+}
+
+static void dphy_clane_hs_txready_sel(struct sf_dphy *priv, u32 ready_sel)
+{
+ top_sys_write32(priv, SCFG_TXREADY_SRC_SEL_D, ready_sel);
+ top_sys_write32(priv, SCFG_TXREADY_SRC_SEL_C, ready_sel);
+ top_sys_write32(priv, SCFG_HS_PRE_ZERO_T_D, 0x30);
+ top_sys_write32(priv, SCFG_HS_PRE_ZERO_T_C, 0x30);
+}
+
+static void mipi_tx_lxn_set(struct sf_dphy *priv, u32 reg, u32 n_hstx, u32 p_hstx)
+{
+ u32 temp = 0;
+
+ temp = n_hstx;
+ temp |= p_hstx << 5;
+ top_sys_write32(priv, reg, temp);
+}
+
+static void dphy_config(struct sf_dphy *priv, int bit_rate)
+{
+ int pre_div, fbk_int, extd_cycle_sel;
+ int dhs_pre_time, dhs_zero_time, dhs_trial_time;
+ int chs_pre_time, chs_zero_time, chs_trial_time;
+ int chs_clk_pre_time, chs_clk_post_time;
+ u32 set_val = 0;
+
+ mipi_tx_lxn_set(priv, SCFG_L0N_L0P_HSTX, 0x10, 0x10);
+ mipi_tx_lxn_set(priv, SCFG_L1N_L1P_HSTX, 0x10, 0x10);
+ mipi_tx_lxn_set(priv, SCFG_L2N_L2P_HSTX, 0x10, 0x10);
+ mipi_tx_lxn_set(priv, SCFG_L3N_L3P_HSTX, 0x10, 0x10);
+ mipi_tx_lxn_set(priv, SCFG_L4N_L4P_HSTX, 0x10, 0x10);
+
+ if(bit_rate == 80) {
+ pre_div=0x1, fbk_int=2*0x33, extd_cycle_sel=0x4,
+ dhs_pre_time=0xe, dhs_zero_time=0x1d, dhs_trial_time=0x15,
+ chs_pre_time=0x5, chs_zero_time=0x2b, chs_trial_time=0xd,
+ chs_clk_pre_time=0xf,
+ chs_clk_post_time=0x71;
+ } else if (bit_rate == 100) {
+ pre_div=0x1, fbk_int=2*0x40, extd_cycle_sel=0x4,
+ dhs_pre_time=0x10, dhs_zero_time=0x21, dhs_trial_time=0x17,
+ chs_pre_time=0x7, chs_zero_time=0x35, chs_trial_time=0xf,
+ chs_clk_pre_time=0xf,
+ chs_clk_post_time=0x73;
+ } else if (bit_rate == 200) {
+ pre_div=0x1, fbk_int=2*0x40, extd_cycle_sel=0x3;
+ dhs_pre_time=0xc, dhs_zero_time=0x1b, dhs_trial_time=0x13;
+ chs_pre_time=0x7, chs_zero_time=0x35, chs_trial_time=0xf,
+ chs_clk_pre_time=0x7,
+ chs_clk_post_time=0x3f;
+ } else if(bit_rate == 300) {
+ pre_div=0x1, fbk_int=2*0x60, extd_cycle_sel=0x3,
+ dhs_pre_time=0x11, dhs_zero_time=0x25, dhs_trial_time=0x19,
+ chs_pre_time=0xa, chs_zero_time=0x50, chs_trial_time=0x15,
+ chs_clk_pre_time=0x7,
+ chs_clk_post_time=0x45;
+ } else if(bit_rate == 400) {
+ pre_div=0x1, fbk_int=2*0x40, extd_cycle_sel=0x2,
+ dhs_pre_time=0xa, dhs_zero_time=0x18, dhs_trial_time=0x11,
+ chs_pre_time=0x7, chs_zero_time=0x35, chs_trial_time=0xf,
+ chs_clk_pre_time=0x3,
+ chs_clk_post_time=0x25;
+ } else if(bit_rate == 500 ) {
+ pre_div=0x1, fbk_int=2*0x50, extd_cycle_sel=0x2,
+ dhs_pre_time=0xc, dhs_zero_time=0x1d, dhs_trial_time=0x14,
+ chs_pre_time=0x9, chs_zero_time=0x42, chs_trial_time=0x12,
+ chs_clk_pre_time=0x3,
+ chs_clk_post_time=0x28;
+ } else if(bit_rate == 600 ) {
+ pre_div=0x1, fbk_int=2*0x60, extd_cycle_sel=0x2,
+ dhs_pre_time=0xe, dhs_zero_time=0x23, dhs_trial_time=0x17,
+ chs_pre_time=0xa, chs_zero_time=0x50, chs_trial_time=0x15,
+ chs_clk_pre_time=0x3,
+ chs_clk_post_time=0x2b;
+ } else if(bit_rate == 700) {
+ pre_div=0x1, fbk_int=2*0x38, extd_cycle_sel=0x1,
+ dhs_pre_time=0x8, dhs_zero_time=0x14, dhs_trial_time=0xf,
+ chs_pre_time=0x6, chs_zero_time=0x2f, chs_trial_time=0xe,
+ chs_clk_pre_time=0x1,
+ chs_clk_post_time=0x16;
+ } else if(bit_rate == 800 ) {
+ pre_div=0x1, fbk_int=2*0x40, extd_cycle_sel=0x1,
+ dhs_pre_time=0x9, dhs_zero_time=0x17, dhs_trial_time=0x10,
+ chs_pre_time=0x7, chs_zero_time=0x35, chs_trial_time=0xf,
+ chs_clk_pre_time=0x1,
+ chs_clk_post_time=0x18;
+ } else if(bit_rate == 900 ) {
+ pre_div=0x1, fbk_int=2*0x48, extd_cycle_sel=0x1,
+ dhs_pre_time=0xa, dhs_zero_time=0x19, dhs_trial_time=0x12,
+ chs_pre_time=0x8, chs_zero_time=0x3c, chs_trial_time=0x10,
+ chs_clk_pre_time=0x1,
+ chs_clk_post_time=0x19;
+ } else if(bit_rate == 1000) {
+ pre_div=0x1, fbk_int=2*0x50, extd_cycle_sel=0x1,
+ dhs_pre_time=0xb, dhs_zero_time=0x1c, dhs_trial_time=0x13,
+ chs_pre_time=0x9, chs_zero_time=0x42, chs_trial_time=0x12,
+ chs_clk_pre_time=0x1,
+ chs_clk_post_time=0x1b;
+ } else if(bit_rate == 1100) {
+ pre_div=0x1, fbk_int=2*0x58, extd_cycle_sel=0x1,
+ dhs_pre_time=0xc, dhs_zero_time=0x1e, dhs_trial_time=0x15,
+ chs_pre_time=0x9, chs_zero_time=0x4a, chs_trial_time=0x14,
+ chs_clk_pre_time=0x1,
+ chs_clk_post_time=0x1d;
+ } else if(bit_rate == 1200) {
+ pre_div=0x1, fbk_int=2*0x60, extd_cycle_sel=0x1,
+ dhs_pre_time=0xe, dhs_zero_time=0x20, dhs_trial_time=0x16,
+ chs_pre_time=0xa, chs_zero_time=0x50, chs_trial_time=0x15,
+ chs_clk_pre_time=0x1,
+ chs_clk_post_time=0x1e;
+ } else if(bit_rate == 1300) {
+ pre_div=0x1, fbk_int=2*0x34, extd_cycle_sel=0x0,
+ dhs_pre_time=0x7, dhs_zero_time=0x12, dhs_trial_time=0xd,
+ chs_pre_time=0x5, chs_zero_time=0x2c, chs_trial_time=0xd,
+ chs_clk_pre_time=0x0,
+ chs_clk_post_time=0xf;
+ } else if(bit_rate == 1400) {
+ pre_div=0x1, fbk_int=2*0x38, extd_cycle_sel=0x0,
+ dhs_pre_time=0x7, dhs_zero_time=0x14, dhs_trial_time=0xe,
+ chs_pre_time=0x6, chs_zero_time=0x2f, chs_trial_time=0xe,
+ chs_clk_pre_time=0x0,
+ chs_clk_post_time=0x10;
+ } else if(bit_rate == 1500) {
+ pre_div=0x1, fbk_int=2*0x3c, extd_cycle_sel=0x0,
+ dhs_pre_time=0x8, dhs_zero_time=0x14, dhs_trial_time=0xf,
+ chs_pre_time=0x6, chs_zero_time=0x32, chs_trial_time=0xe,
+ chs_clk_pre_time=0x0,
+ chs_clk_post_time=0x11;
+ } else if(bit_rate == 1600) {
+ pre_div=0x1, fbk_int=2*0x40, extd_cycle_sel=0x0,
+ dhs_pre_time=0x9, dhs_zero_time=0x15, dhs_trial_time=0x10,
+ chs_pre_time=0x7, chs_zero_time=0x35, chs_trial_time=0xf,
+ chs_clk_pre_time=0x0,
+ chs_clk_post_time=0x12;
+ } else if(bit_rate == 1700) {
+ pre_div=0x1, fbk_int=2*0x44, extd_cycle_sel=0x0,
+ dhs_pre_time=0x9, dhs_zero_time=0x17, dhs_trial_time=0x10,
+ chs_pre_time=0x7, chs_zero_time=0x39, chs_trial_time=0x10,
+ chs_clk_pre_time=0x0,
+ chs_clk_post_time=0x12;
+ } else if(bit_rate == 1800) {
+ pre_div=0x1, fbk_int=2*0x48, extd_cycle_sel=0x0,
+ dhs_pre_time=0xa, dhs_zero_time=0x18, dhs_trial_time=0x11,
+ chs_pre_time=0x8, chs_zero_time=0x3c, chs_trial_time=0x10,
+ chs_clk_pre_time=0x0,
+ chs_clk_post_time=0x13;
+ } else if(bit_rate == 1900) {
+ pre_div=0x1, fbk_int=2*0x4c, extd_cycle_sel=0x0,
+ dhs_pre_time=0xa, dhs_zero_time=0x1a, dhs_trial_time=0x12,
+ chs_pre_time=0x8, chs_zero_time=0x3f, chs_trial_time=0x11,
+ chs_clk_pre_time=0x0,
+ chs_clk_post_time=0x14;
+ } else if(bit_rate == 2000) {
+ pre_div=0x1, fbk_int=2*0x50, extd_cycle_sel=0x0,
+ dhs_pre_time=0xb, dhs_zero_time=0x1b, dhs_trial_time=0x13,
+ chs_pre_time=0x9, chs_zero_time=0x42, chs_trial_time=0x12,
+ chs_clk_pre_time=0x0,
+ chs_clk_post_time=0x15;
+ } else if(bit_rate == 2100) {
+ pre_div=0x1, fbk_int=2*0x54, extd_cycle_sel=0x0,
+ dhs_pre_time=0xb, dhs_zero_time=0x1c, dhs_trial_time=0x13,
+ chs_pre_time=0x9, chs_zero_time=0x46, chs_trial_time=0x13,
+ chs_clk_pre_time=0x0,
+ chs_clk_post_time=0x15;
+ } else if(bit_rate == 2200) {
+ pre_div=0x1, fbk_int=2*0x5b, extd_cycle_sel=0x0,
+ dhs_pre_time=0xc, dhs_zero_time=0x1d, dhs_trial_time=0x14,
+ chs_pre_time=0x9, chs_zero_time=0x4a, chs_trial_time=0x14,
+ chs_clk_pre_time=0x0,
+ chs_clk_post_time=0x16;
+ } else if(bit_rate == 2300) {
+ pre_div=0x1, fbk_int=2*0x5c, extd_cycle_sel=0x0,
+ dhs_pre_time=0xc, dhs_zero_time=0x1f, dhs_trial_time=0x15,
+ chs_pre_time=0xa, chs_zero_time=0x4c, chs_trial_time=0x14,
+ chs_clk_pre_time=0x0,
+ chs_clk_post_time=0x17;
+ } else if(bit_rate == 2400) {
+ pre_div=0x1, fbk_int=2*0x60, extd_cycle_sel=0x0,
+ dhs_pre_time=0xd, dhs_zero_time=0x20, dhs_trial_time=0x16,
+ chs_pre_time=0xa, chs_zero_time=0x50, chs_trial_time=0x15,
+ chs_clk_pre_time=0x0,
+ chs_clk_post_time=0x18;
+ } else if(bit_rate == 2500) {
+ pre_div=0x1, fbk_int=2*0x64, extd_cycle_sel=0x0,
+ dhs_pre_time=0xe, dhs_zero_time=0x21, dhs_trial_time=0x16,
+ chs_pre_time=0xb, chs_zero_time=0x53, chs_trial_time=0x16,
+ chs_clk_pre_time=0x0,
+ chs_clk_post_time=0x18;
+ } else {
+ //default bit_rate == 700
+ pre_div=0x1, fbk_int=2*0x38, extd_cycle_sel=0x1,
+ dhs_pre_time=0x8, dhs_zero_time=0x14, dhs_trial_time=0xf,
+ chs_pre_time=0x6, chs_zero_time=0x2f, chs_trial_time=0xe,
+ chs_clk_pre_time=0x1,
+ chs_clk_post_time=0x16;
+ }
+ top_sys_write32(priv, SCFG_REFCLK_SEL, 0x3);
+
+ set_val = 0
+ | (1 << OFFSET_CFG_L1_SWAP_SEL)
+ | (4 << OFFSET_CFG_L2_SWAP_SEL)
+ | (2 << OFFSET_CFG_L3_SWAP_SEL)
+ | (3 << OFFSET_CFG_L4_SWAP_SEL);
+ top_sys_write32(priv, SCFG_LX_SWAP_SEL, set_val);
+
+ set_val = 0
+ | (0 << OFFSET_SCFG_PWRON_READY_N)
+ | (1 << OFFSET_RG_CDTX_PLL_FM_EN)
+ | (0 << OFFSET_SCFG_PLLSSC_EN)
+ | (1 << OFFSET_RG_CDTX_PLL_LDO_STB_X2_EN);
+ top_sys_write32(priv, SCFG_DBUS_PW_PLL_SSC_LD0, set_val);
+
+ set_val = fbk_int
+ | (pre_div << 9);
+ top_sys_write32(priv, SCFG_RG_CDTX_PLL_FBK_PRE, set_val);
+
+ top_sys_write32(priv, SCFG_RG_EXTD_CYCLE_SEL, extd_cycle_sel);
+
+ set_val = chs_zero_time
+ | (dhs_pre_time << OFFSET_DHS_PRE_TIME)
+ | (dhs_trial_time << OFFSET_DHS_TRIAL_TIME)
+ | (dhs_zero_time << OFFSET_DHS_ZERO_TIME);
+ top_sys_write32(priv, SCFG_RG_CLANE_DLANE_TIME, set_val);
+
+ set_val = chs_clk_post_time
+ | (chs_clk_pre_time << OFFSET_CHS_PRE_TIME)
+ | (chs_pre_time << OFFSET_CHS_TRIAL_TIME)
+ | (chs_trial_time << OFFSET_CHS_ZERO_TIME);
+ top_sys_write32(priv, SCFG_RG_CLANE_HS_TIME, set_val);
+
+}
+
+static void reset_dphy(struct sf_dphy *priv, int resetb)
+{
+ u32 cfg_dsc_enable = 0x01;//bit0
+
+ u32 precfg = top_sys_read32(priv, SCFG_PHY_RESETB);
+ precfg &= ~(cfg_dsc_enable);
+ precfg |= (resetb&cfg_dsc_enable);
+ top_sys_write32(priv, SCFG_PHY_RESETB, precfg);
+}
+
+static void polling_dphy_lock(struct sf_dphy *priv)
+{
+ int pll_unlock;
+
+ udelay(10);
+
+ do {
+ pll_unlock = top_sys_read32(priv, SCFG_GRS_CDTX_PLL) >> 3;
+ pll_unlock &= 0x1;
+ } while(pll_unlock == 0x1);
+}
+
+static int sf_dphy_configure(struct phy *phy, union phy_configure_opts *opts)
+{
+ struct sf_dphy *dphy = phy_get_drvdata(phy);
+ uint32_t bit_rate = 800000000/1000000UL;//new mipi panel clock setting
+
+
+ dphy_config(dphy, bit_rate);
+ reset_dphy(dphy, 1);
+ mdelay(10);
+ polling_dphy_lock(dphy);
+
+ return 0;
+}
+
+static int sf_dphy_power_on(struct phy *phy)
+{
+ return 0;
+}
+
+static int sf_dphy_power_off(struct phy *phy)
+{
+ return 0;
+}
+
+static int sf_dphy_init(struct phy *phy)
+{
+ struct sf_dphy *dphy = phy_get_drvdata(phy);
+
+ dsi_csi2tx_sel(dphy, 0);
+ dphy_clane_hs_txready_sel(dphy, 0x1);
+
+ return 0;
+}
+
+static int sf_dphy_validate(struct phy *phy, enum phy_mode mode, int submode,
+ union phy_configure_opts *opts)
+{
+ return 0;
+}
+
+static int sf_dphy_set_mode(struct phy *phy, enum phy_mode mode, int submode)
+{
+ return 0;
+}
+
+
+static int sf_dphy_exit(struct phy *phy)
+{
+ return 0;
+}
+
+static const struct phy_ops sf_dphy_ops = {
+ .power_on = sf_dphy_power_on,
+ .power_off = sf_dphy_power_off,
+ .init = sf_dphy_init,
+ .exit = sf_dphy_exit,
+ .configure = sf_dphy_configure,
+ .validate = sf_dphy_validate,
+ .set_mode = sf_dphy_set_mode,
+ .owner = THIS_MODULE,
+};
+
+static const struct of_device_id sf_dphy_dt_ids[] = {
+ {
+ .compatible = "starfive,jh7100-mipi-dphy-tx",
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(of, sf_dphy_dt_ids);
+
+static int sf_dphy_probe(struct platform_device *pdev)
+{
+ struct phy_provider *phy_provider;
+ struct sf_dphy *dphy;
+ struct resource *res;
+ int ret;
+ dphy = devm_kzalloc(&pdev->dev, sizeof(*dphy), GFP_KERNEL);
+ if (!dphy)
+ return -ENOMEM;
+ dev_set_drvdata(&pdev->dev, dphy);
+
+ dev_info(&pdev->dev,"===> %s enter, %d \n", __func__, __LINE__);
+
+ dphy->topsys = ioremap(0x12260000, 0x10000);
+
+ dphy->phy = devm_phy_create(&pdev->dev, NULL, &sf_dphy_ops);
+ if (IS_ERR(dphy->phy)) {
+ dev_err(&pdev->dev, "failed to create phy\n");
+ return PTR_ERR(dphy->phy);
+ }
+ phy_set_drvdata(dphy->phy, dphy);
+
+ phy_provider = devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate);
+
+ return PTR_ERR_OR_ZERO(phy_provider);
+}
+
+static struct platform_driver sf_dphy_driver = {
+ .probe = sf_dphy_probe,
+ .driver = {
+ .name = "sf-mipi-dphy-tx",
+ .of_match_table = sf_dphy_dt_ids,
+ },
+};
+module_platform_driver(sf_dphy_driver);
+
+MODULE_AUTHOR("Ezequiel Garcia <ezequiel@collabora.com>");
+MODULE_DESCRIPTION("sf MIPI DPHY TX0 driver");
+MODULE_LICENSE("Dual MIT/GPL");
*/
/* Vendor Ids: */
+#define DRM_FORMAT_MOD_NONE 0
#define DRM_FORMAT_MOD_VENDOR_NONE 0
#define DRM_FORMAT_MOD_VENDOR_INTEL 0x01
#define DRM_FORMAT_MOD_VENDOR_AMD 0x02
#define DRM_FORMAT_MOD_VENDOR_ARM 0x08
#define DRM_FORMAT_MOD_VENDOR_ALLWINNER 0x09
#define DRM_FORMAT_MOD_VENDOR_AMLOGIC 0x0a
+#define DRM_FORMAT_MOD_VENDOR_VS 0x0b
/* add more to the end as needed */
#define AMD_FMT_MOD_CLEAR(field) \
(~((uint64_t)AMD_FMT_MOD_##field##_MASK << AMD_FMT_MOD_##field##_SHIFT))
+
+#define DRM_FORMAT_MOD_VS_TYPE_NORMAL 0x00
+#define DRM_FORMAT_MOD_VS_TYPE_COMPRESSED 0x01
+#define DRM_FORMAT_MOD_VS_TYPE_CUSTOM_10BIT 0x02
+#define DRM_FORMAT_MOD_VS_TYPE_MASK ((__u64)0x3 << 54)
+
+#define fourcc_mod_vs_code(type, val) \
+ fourcc_mod_code(VS, ((((__u64)type) << 54) | (val)))
+
+#define DRM_FORMAT_MOD_VS_DEC_TILE_MODE_MASK 0x3F
+#define DRM_FORMAT_MOD_VS_DEC_TILE_8X8_XMAJOR 0x00
+#define DRM_FORMAT_MOD_VS_DEC_TILE_8X8_YMAJOR 0x01
+#define DRM_FORMAT_MOD_VS_DEC_TILE_16X4 0x02
+#define DRM_FORMAT_MOD_VS_DEC_TILE_8X4 0x03
+#define DRM_FORMAT_MOD_VS_DEC_TILE_4X8 0x04
+#define DRM_FORMAT_MOD_VS_DEC_RASTER_16X4 0x06
+#define DRM_FORMAT_MOD_VS_DEC_TILE_64X4 0x07
+#define DRM_FORMAT_MOD_VS_DEC_TILE_32X4 0x08
+#define DRM_FORMAT_MOD_VS_DEC_RASTER_256X1 0x09
+#define DRM_FORMAT_MOD_VS_DEC_RASTER_128X1 0x0A
+#define DRM_FORMAT_MOD_VS_DEC_RASTER_64X4 0x0B
+#define DRM_FORMAT_MOD_VS_DEC_RASTER_256X2 0x0C
+#define DRM_FORMAT_MOD_VS_DEC_RASTER_128X2 0x0D
+#define DRM_FORMAT_MOD_VS_DEC_RASTER_128X4 0x0E
+#define DRM_FORMAT_MOD_VS_DEC_RASTER_64X1 0x0F
+#define DRM_FORMAT_MOD_VS_DEC_TILE_16X8 0x10
+#define DRM_FORMAT_MOD_VS_DEC_TILE_8X16 0x11
+#define DRM_FORMAT_MOD_VS_DEC_RASTER_512X1 0x12
+#define DRM_FORMAT_MOD_VS_DEC_RASTER_32X4 0x13
+#define DRM_FORMAT_MOD_VS_DEC_RASTER_64X2 0x14
+#define DRM_FORMAT_MOD_VS_DEC_RASTER_32X2 0x15
+#define DRM_FORMAT_MOD_VS_DEC_RASTER_32X1 0x16
+#define DRM_FORMAT_MOD_VS_DEC_RASTER_16X1 0x17
+#define DRM_FORMAT_MOD_VS_DEC_TILE_128X4 0x18
+#define DRM_FORMAT_MOD_VS_DEC_TILE_256X4 0x19
+#define DRM_FORMAT_MOD_VS_DEC_TILE_512X4 0x1A
+#define DRM_FORMAT_MOD_VS_DEC_TILE_16X16 0x1B
+#define DRM_FORMAT_MOD_VS_DEC_TILE_32X16 0x1C
+#define DRM_FORMAT_MOD_VS_DEC_TILE_64X16 0x1D
+#define DRM_FORMAT_MOD_VS_DEC_TILE_128X8 0x1E
+#define DRM_FORMAT_MOD_VS_DEC_TILE_8X4_S 0x1F
+#define DRM_FORMAT_MOD_VS_DEC_TILE_16X4_S 0x20
+#define DRM_FORMAT_MOD_VS_DEC_TILE_32X4_S 0x21
+#define DRM_FORMAT_MOD_VS_DEC_TILE_16X4_LSB 0x22
+#define DRM_FORMAT_MOD_VS_DEC_TILE_32X4_LSB 0x23
+#define DRM_FORMAT_MOD_VS_DEC_TILE_32X8 0x24
+
+#define DRM_FORMAT_MOD_VS_DEC_ALIGN_32 (0x01 << 6)
+#define DRM_FORMAT_MOD_VS_DEC_ALIGN_64 (0x01 << 7)
+
+#define fourcc_mod_vs_dec_code(tile, align) \
+ fourcc_mod_vs_code(DRM_FORMAT_MOD_VS_TYPE_COMPRESSED, \
+ ((tile) | (align)))
+
+#define DRM_FORMAT_MOD_VS_NORM_MODE_MASK 0x1F
+#define DRM_FORMAT_MOD_VS_LINEAR 0x00
+#define DRM_FORMAT_MOD_VS_TILED4x4 0x01
+#define DRM_FORMAT_MOD_VS_SUPER_TILED_XMAJOR 0x02
+#define DRM_FORMAT_MOD_VS_SUPER_TILED_YMAJOR 0x03
+#define DRM_FORMAT_MOD_VS_TILE_8X8 0x04
+#define DRM_FORMAT_MOD_VS_TILE_MODE1 0x05
+#define DRM_FORMAT_MOD_VS_TILE_MODE2 0x06
+#define DRM_FORMAT_MOD_VS_TILE_8X4 0x07
+#define DRM_FORMAT_MOD_VS_TILE_MODE4 0x08
+#define DRM_FORMAT_MOD_VS_TILE_MODE5 0x09
+#define DRM_FORMAT_MOD_VS_TILE_MODE6 0x0A
+#define DRM_FORMAT_MOD_VS_SUPER_TILED_XMAJOR_8X4 0x0B
+#define DRM_FORMAT_MOD_VS_SUPER_TILED_YMAJOR_4X8 0x0C
+#define DRM_FORMAT_MOD_VS_TILE_Y 0x0D
+#define DRM_FORMAT_MOD_VS_TILE_128X1 0x0F
+#define DRM_FORMAT_MOD_VS_TILE_256X1 0x10
+#define DRM_FORMAT_MOD_VS_TILE_32X1 0x11
+#define DRM_FORMAT_MOD_VS_TILE_64X1 0x12
+#define DRM_FORMAT_MOD_VS_TILE_MODE4X4 0x15
+
+#define fourcc_mod_vs_norm_code(tile) \
+ fourcc_mod_vs_code(DRM_FORMAT_MOD_VS_TYPE_NORMAL, \
+ (tile))
+
+#define fourcc_mod_vs_custom_code(tile) \
+ fourcc_mod_vs_code(DRM_FORMAT_MOD_VS_TYPE_CUSTOM_10BIT, \
+ (tile))
#if defined(__cplusplus)
}
#endif