diff options
134 files changed, 9896 insertions, 4205 deletions
diff --git a/Documentation/devicetree/bindings/display/bridge/analogix_dp.txt b/Documentation/devicetree/bindings/display/bridge/analogix_dp.txt new file mode 100644 index 000000000000..4f2ba8c13d92 --- /dev/null +++ b/Documentation/devicetree/bindings/display/bridge/analogix_dp.txt @@ -0,0 +1,52 @@ +Analogix Display Port bridge bindings + +Required properties for dp-controller: + -compatible: + platform specific such as: + * "samsung,exynos5-dp" + * "rockchip,rk3288-dp" + -reg: + physical base address of the controller and length + of memory mapped region. + -interrupts: + interrupt combiner values. + -clocks: + from common clock binding: handle to dp clock. + -clock-names: + from common clock binding: Shall be "dp". + -interrupt-parent: + phandle to Interrupt combiner node. + -phys: + from general PHY binding: the phandle for the PHY device. + -phy-names: + from general PHY binding: Should be "dp". + +Optional properties for dp-controller: + -force-hpd: + Indicate driver need force hpd when hpd detect failed, this + is used for some eDP screen which don't have hpd signal. + -hpd-gpios: + Hotplug detect GPIO. + Indicates which GPIO should be used for hotplug detection + -port@[X]: SoC specific port nodes with endpoint definitions as defined + in Documentation/devicetree/bindings/media/video-interfaces.txt, + please refer to the SoC specific binding document: + * Documentation/devicetree/bindings/display/exynos/exynos_dp.txt + * Documentation/devicetree/bindings/video/analogix_dp-rockchip.txt + +[1]: Documentation/devicetree/bindings/media/video-interfaces.txt +------------------------------------------------------------------------------- + +Example: + + dp-controller { + compatible = "samsung,exynos5-dp"; + reg = <0x145b0000 0x10000>; + interrupts = <10 3>; + interrupt-parent = <&combiner>; + clocks = <&clock 342>; + clock-names = "dp"; + + phys = <&dp_phy>; + phy-names = "dp"; + }; diff --git a/Documentation/devicetree/bindings/display/exynos/exynos_dp.txt b/Documentation/devicetree/bindings/display/exynos/exynos_dp.txt index fe4a7a2dea9c..ade5d8eebf85 100644 --- a/Documentation/devicetree/bindings/display/exynos/exynos_dp.txt +++ b/Documentation/devicetree/bindings/display/exynos/exynos_dp.txt @@ -1,20 +1,3 @@ -Device-Tree bindings for Samsung Exynos Embedded DisplayPort Transmitter(eDP) - -DisplayPort is industry standard to accommodate the growing board adoption -of digital display technology within the PC and CE industries. -It consolidates the internal and external connection methods to reduce device -complexity and cost. It also supports necessary features for important cross -industry applications and provides performance scalability to enable the next -generation of displays that feature higher color depths, refresh rates, and -display resolutions. - -eDP (embedded display port) device is compliant with Embedded DisplayPort -standard as follows, -- DisplayPort standard 1.1a for Exynos5250 and Exynos5260. -- DisplayPort standard 1.3 for Exynos5422s and Exynos5800. - -eDP resides between FIMD and panel or FIMD and bridge such as LVDS. - The Exynos display port interface should be configured based on the type of panel connected to it. @@ -48,26 +31,6 @@ Required properties for dp-controller: from general PHY binding: the phandle for the PHY device. -phy-names: from general PHY binding: Should be "dp". - -samsung,color-space: - input video data format. - COLOR_RGB = 0, COLOR_YCBCR422 = 1, COLOR_YCBCR444 = 2 - -samsung,dynamic-range: - dynamic range for input video data. - VESA = 0, CEA = 1 - -samsung,ycbcr-coeff: - YCbCr co-efficients for input video. - COLOR_YCBCR601 = 0, COLOR_YCBCR709 = 1 - -samsung,color-depth: - number of bits per colour component. - COLOR_6 = 0, COLOR_8 = 1, COLOR_10 = 2, COLOR_12 = 3 - -samsung,link-rate: - link rate supported by the panel. - LINK_RATE_1_62GBPS = 0x6, LINK_RATE_2_70GBPS = 0x0A - -samsung,lane-count: - number of lanes supported by the panel. - LANE_COUNT1 = 1, LANE_COUNT2 = 2, LANE_COUNT4 = 4 - - display-timings: timings for the connected panel as described by - Documentation/devicetree/bindings/display/display-timing.txt Optional properties for dp-controller: -interlaced: @@ -83,17 +46,31 @@ Optional properties for dp-controller: Hotplug detect GPIO. Indicates which GPIO should be used for hotplug detection -Video interfaces: - Device node can contain video interface port nodes according to [1]. - The following are properties specific to those nodes: - - endpoint node connected to bridge or panel node: - - remote-endpoint: specifies the endpoint in panel or bridge node. - This node is required in all kinds of exynos dp - to represent the connection between dp and bridge - or dp and panel. - -[1]: Documentation/devicetree/bindings/media/video-interfaces.txt + -video interfaces: Device node can contain video interface port + nodes according to [1]. + - display-timings: timings for the connected panel as described by + Documentation/devicetree/bindings/display/panel/display-timing.txt + +For the below properties, please refer to Analogix DP binding document: + * Documentation/devicetree/bindings/display/bridge/analogix_dp.txt + -phys (required) + -phy-names (required) + -hpd-gpios (optional) + force-hpd (optional) + +Deprecated properties for DisplayPort: +-interlaced: deprecated prop that can parsed from drm_display_mode. +-vsync-active-high: deprecated prop that can parsed from drm_display_mode. +-hsync-active-high: deprecated prop that can parsed from drm_display_mode. +-samsung,ycbcr-coeff: deprecated prop that can parsed from drm_display_mode. +-samsung,dynamic-range: deprecated prop that can parsed from drm_display_mode. +-samsung,color-space: deprecated prop that can parsed from drm_display_info. +-samsung,color-depth: deprecated prop that can parsed from drm_display_info. +-samsung,link-rate: deprecated prop that can reading from monitor by dpcd method. +-samsung,lane-count: deprecated prop that can reading from monitor by dpcd method. +-samsung,hpd-gpio: deprecated name for hpd-gpios. + +------------------------------------------------------------------------------- Example: @@ -112,13 +89,6 @@ SOC specific portion: Board Specific portion: dp-controller { - samsung,color-space = <0>; - samsung,dynamic-range = <0>; - samsung,ycbcr-coeff = <0>; - samsung,color-depth = <1>; - samsung,link-rate = <0x0a>; - samsung,lane-count = <4>; - display-timings { native-mode = <&lcd_timing>; lcd_timing: 1366x768 { @@ -135,18 +105,9 @@ Board Specific portion: }; ports { - port { + port@0 { dp_out: endpoint { - remote-endpoint = <&dp_in>; - }; - }; - }; - - panel { - ... - port { - dp_in: endpoint { - remote-endpoint = <&dp_out>; + remote-endpoint = <&bridge_in>; }; }; }; diff --git a/Documentation/devicetree/bindings/display/fsl,dcu.txt b/Documentation/devicetree/bindings/display/fsl,dcu.txt index ebf1be9ae393..ae55cde1b69e 100644 --- a/Documentation/devicetree/bindings/display/fsl,dcu.txt +++ b/Documentation/devicetree/bindings/display/fsl,dcu.txt @@ -6,17 +6,24 @@ Required properties: * "fsl,vf610-dcu". - reg: Address and length of the register set for dcu. -- clocks: From common clock binding: handle to dcu clock. -- clock-names: From common clock binding: Shall be "dcu". +- clocks: Handle to "dcu" and "pix" clock (in the order below) + This can be the same clock (e.g. LS1021a) + See ../clocks/clock-bindings.txt for details. +- clock-names: Should be "dcu" and "pix" + See ../clocks/clock-bindings.txt for details. - big-endian Boolean property, LS1021A DCU registers are big-endian. - fsl,panel: The phandle to panel node. +Optional properties: +- fsl,tcon: The phandle to the timing controller node. + Examples: dcu: dcu@2ce0000 { compatible = "fsl,ls1021a-dcu"; reg = <0x0 0x2ce0000 0x0 0x10000>; - clocks = <&platform_clk 0>; - clock-names = "dcu"; + clocks = <&platform_clk 0>, <&platform_clk 0>; + clock-names = "dcu", "pix"; big-endian; fsl,panel = <&panel>; + fsl,tcon = <&tcon>; }; diff --git a/Documentation/devicetree/bindings/display/fsl,tcon.txt b/Documentation/devicetree/bindings/display/fsl,tcon.txt new file mode 100644 index 000000000000..6fa4ab668db5 --- /dev/null +++ b/Documentation/devicetree/bindings/display/fsl,tcon.txt @@ -0,0 +1,18 @@ +Device Tree bindings for Freescale TCON Driver + +Required properties: +- compatible: Should be one of + * "fsl,vf610-tcon". + +- reg: Address and length of the register set for tcon. +- clocks: From common clock binding: handle to tcon ipg clock. +- clock-names: From common clock binding: Shall be "ipg". + +Examples: +timing-controller@4003d000 { + compatible = "fsl,vf610-tcon"; + reg = <0x4003d000 0x1000>; + clocks = <&clks VF610_CLK_TCON0>; + clock-names = "ipg"; + status = "okay"; +}; diff --git a/Documentation/devicetree/bindings/display/rockchip/analogix_dp-rockchip.txt b/Documentation/devicetree/bindings/display/rockchip/analogix_dp-rockchip.txt new file mode 100644 index 000000000000..e832ff98fd61 --- /dev/null +++ b/Documentation/devicetree/bindings/display/rockchip/analogix_dp-rockchip.txt @@ -0,0 +1,92 @@ +Rockchip RK3288 specific extensions to the Analogix Display Port +================================ + +Required properties: +- compatible: "rockchip,rk3288-edp"; + +- reg: physical base address of the controller and length + +- clocks: from common clock binding: handle to dp clock. + of memory mapped region. + +- clock-names: from common clock binding: + Required elements: "dp" "pclk" + +- resets: Must contain an entry for each entry in reset-names. + See ../reset/reset.txt for details. + +- pinctrl-names: Names corresponding to the chip hotplug pinctrl states. +- pinctrl-0: pin-control mode. should be <&edp_hpd> + +- reset-names: Must include the name "dp" + +- rockchip,grf: this soc should set GRF regs, so need get grf here. + +- ports: there are 2 port nodes with endpoint definitions as defined in + Documentation/devicetree/bindings/media/video-interfaces.txt. + Port 0: contained 2 endpoints, connecting to the output of vop. + Port 1: contained 1 endpoint, connecting to the input of panel. + +For the below properties, please refer to Analogix DP binding document: + * Documentation/devicetree/bindings/drm/bridge/analogix_dp.txt +- phys (required) +- phy-names (required) +- hpd-gpios (optional) +- force-hpd (optional) +------------------------------------------------------------------------------- + +Example: + dp-controller: dp@ff970000 { + compatible = "rockchip,rk3288-dp"; + reg = <0xff970000 0x4000>; + interrupts = <GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&cru SCLK_EDP>, <&cru PCLK_EDP_CTRL>; + clock-names = "dp", "pclk"; + phys = <&dp_phy>; + phy-names = "dp"; + + rockchip,grf = <&grf>; + resets = <&cru 111>; + reset-names = "dp"; + + pinctrl-names = "default"; + pinctrl-0 = <&edp_hpd>; + + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + edp_in: port@0 { + reg = <0>; + #address-cells = <1>; + #size-cells = <0>; + edp_in_vopb: endpoint@0 { + reg = <0>; + remote-endpoint = <&vopb_out_edp>; + }; + edp_in_vopl: endpoint@1 { + reg = <1>; + remote-endpoint = <&vopl_out_edp>; + }; + }; + + edp_out: port@1 { + reg = <1>; + #address-cells = <1>; + #size-cells = <0>; + edp_out_panel: endpoint { + reg = <0>; + remote-endpoint = <&panel_in_edp> + }; + }; + }; + }; + + pinctrl { + edp { + edp_hpd: edp-hpd { + rockchip,pins = <7 11 RK_FUNC_2 &pcfg_pull_none>; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/display/snps,arcpgu.txt b/Documentation/devicetree/bindings/display/snps,arcpgu.txt new file mode 100644 index 000000000000..c5c7dfd37df2 --- /dev/null +++ b/Documentation/devicetree/bindings/display/snps,arcpgu.txt @@ -0,0 +1,35 @@ +ARC PGU + +This is a display controller found on several development boards produced +by Synopsys. The ARC PGU is an RGB streamer that reads the data from a +framebuffer and sends it to a single digital encoder (usually HDMI). + +Required properties: + - compatible: "snps,arcpgu" + - reg: Physical base address and length of the controller's registers. + - clocks: A list of phandle + clock-specifier pairs, one for each + entry in 'clock-names'. + - clock-names: A list of clock names. For ARC PGU it should contain: + - "pxlclk" for the clock feeding the output PLL of the controller. + +Required sub-nodes: + - port: The PGU connection to an encoder chip. + +Example: + +/ { + ... + + pgu@XXXXXXXX { + compatible = "snps,arcpgu"; + reg = <0xXXXXXXXX 0x400>; + clocks = <&clock_node>; + clock-names = "pxlclk"; + + port { + pgu_output: endpoint { + remote-endpoint = <&hdmi_enc_input>; + }; + }; + }; +}; diff --git a/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt new file mode 100644 index 000000000000..df8f4aeefe4c --- /dev/null +++ b/Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt @@ -0,0 +1,258 @@ +Allwinner A10 Display Pipeline +============================== + +The Allwinner A10 Display pipeline is composed of several components +that are going to be documented below: + +TV Encoder +---------- + +The TV Encoder supports the composite and VGA output. It is one end of +the pipeline. + +Required properties: + - compatible: value should be "allwinner,sun4i-a10-tv-encoder". + - reg: base address and size of memory-mapped region + - clocks: the clocks driving the TV encoder + - resets: phandle to the reset controller driving the encoder + +- ports: A ports node with endpoint definitions as defined in + Documentation/devicetree/bindings/media/video-interfaces.txt. The + first port should be the input endpoint. + +TCON +---- + +The TCON acts as a timing controller for RGB, LVDS and TV interfaces. + +Required properties: + - compatible: value should be "allwinner,sun5i-a13-tcon". + - reg: base address and size of memory-mapped region + - interrupts: interrupt associated to this IP + - clocks: phandles to the clocks feeding the TCON. Three are needed: + - 'ahb': the interface clocks + - 'tcon-ch0': The clock driving the TCON channel 0 + - 'tcon-ch1': The clock driving the TCON channel 1 + - resets: phandles to the reset controllers driving the encoder + - "lcd": the reset line for the TCON channel 0 + + - clock-names: the clock names mentioned above + - reset-names: the reset names mentioned above + - clock-output-names: Name of the pixel clock created + +- ports: A ports node with endpoint definitions as defined in + Documentation/devicetree/bindings/media/video-interfaces.txt. The + first port should be the input endpoint, the second one the output + + The output should have two endpoints. The first is the block + connected to the TCON channel 0 (usually a panel or a bridge), the + second the block connected to the TCON channel 1 (usually the TV + encoder) + + +Display Engine Backend +---------------------- + +The display engine backend exposes layers and sprites to the +system. + +Required properties: + - compatible: value must be one of: + * allwinner,sun5i-a13-display-backend + - reg: base address and size of the memory-mapped region. + - clocks: phandles to the clocks feeding the frontend and backend + * ahb: the backend interface clock + * mod: the backend module clock + * ram: the backend DRAM clock + - clock-names: the clock names mentioned above + - resets: phandles to the reset controllers driving the backend + +- ports: A ports node with endpoint definitions as defined in + Documentation/devicetree/bindings/media/video-interfaces.txt. The + first port should be the input endpoints, the second one the output + +Display Engine Frontend +----------------------- + +The display engine frontend does formats conversion, scaling, +deinterlacing and color space conversion. + +Required properties: + - compatible: value must be one of: + * allwinner,sun5i-a13-display-frontend + - reg: base address and size of the memory-mapped region. + - interrupts: interrupt associated to this IP + - clocks: phandles to the clocks feeding the frontend and backend + * ahb: the backend interface clock + * mod: the backend module clock + * ram: the backend DRAM clock + - clock-names: the clock names mentioned above + - resets: phandles to the reset controllers driving the backend + +- ports: A ports node with endpoint definitions as defined in + Documentation/devicetree/bindings/media/video-interfaces.txt. The + first port should be the input endpoints, the second one the outputs + + +Display Engine Pipeline +----------------------- + +The display engine pipeline (and its entry point, since it can be +either directly the backend or the frontend) is represented as an +extra node. + +Required properties: + - compatible: value must be one of: + * allwinner,sun5i-a13-display-engine + + - allwinner,pipelines: list of phandle to the display engine + frontends available. + +Example: + +panel: panel { + compatible = "olimex,lcd-olinuxino-43-ts"; + #address-cells = <1>; + #size-cells = <0>; + + port { + #address-cells = <1>; + #size-cells = <0>; + + panel_input: endpoint { + remote-endpoint = <&tcon0_out_panel>; + }; + }; +}; + +tve0: tv-encoder@01c0a000 { + compatible = "allwinner,sun4i-a10-tv-encoder"; + reg = <0x01c0a000 0x1000>; + clocks = <&ahb_gates 34>; + resets = <&tcon_ch0_clk 0>; + + port { + #address-cells = <1>; + #size-cells = <0>; + + tve0_in_tcon0: endpoint@0 { + reg = <0>; + remote-endpoint = <&tcon0_out_tve0>; + }; + }; +}; + +tcon0: lcd-controller@1c0c000 { + compatible = "allwinner,sun5i-a13-tcon"; + reg = <0x01c0c000 0x1000>; + interrupts = <44>; + resets = <&tcon_ch0_clk 1>; + reset-names = "lcd"; + clocks = <&ahb_gates 36>, + <&tcon_ch0_clk>, + <&tcon_ch1_clk>; + clock-names = "ahb", + "tcon-ch0", + "tcon-ch1"; + clock-output-names = "tcon-pixel-clock"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + tcon0_in: port@0 { + #address-cells = <1>; + #size-cells = <0>; + reg = <0>; + + tcon0_in_be0: endpoint@0 { + reg = <0>; + remote-endpoint = <&be0_out_tcon0>; + }; + }; + + tcon0_out: port@1 { + #address-cells = <1>; + #size-cells = <0>; + reg = <1>; + + tcon0_out_panel: endpoint@0 { + reg = <0>; + remote-endpoint = <&panel_input>; + }; + + tcon0_out_tve0: endpoint@1 { + reg = <1>; + remote-endpoint = <&tve0_in_tcon0>; + }; + }; + }; +}; + +fe0: display-frontend@1e00000 { + compatible = "allwinner,sun5i-a13-display-frontend"; + reg = <0x01e00000 0x20000>; + interrupts = <47>; + clocks = <&ahb_gates 46>, <&de_fe_clk>, + <&dram_gates 25>; + clock-names = "ahb", "mod", + "ram"; + resets = <&de_fe_clk>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + fe0_out: port@1 { + #address-cells = <1>; + #size-cells = <0>; + reg = <1>; + + fe0_out_be0: endpoint { + remote-endpoint = <&be0_in_fe0>; + }; + }; + }; +}; + +be0: display-backend@1e60000 { + compatible = "allwinner,sun5i-a13-display-backend"; + reg = <0x01e60000 0x10000>; + clocks = <&ahb_gates 44>, <&de_be_clk>, + <&dram_gates 26>; + clock-names = "ahb", "mod", + "ram"; + resets = <&de_be_clk>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + be0_in: port@0 { + #address-cells = <1>; + #size-cells = <0>; + reg = <0>; + + be0_in_fe0: endpoint@0 { + reg = <0>; + remote-endpoint = <&fe0_out_be0>; + }; + }; + + be0_out: port@1 { + #address-cells = <1>; + #size-cells = <0>; + reg = <1>; + + be0_out_tcon0: endpoint@0 { + reg = <0>; + remote-endpoint = <&tcon0_in_be0>; + }; + }; + }; +}; + +display-engine { + compatible = "allwinner,sun5i-a13-display-engine"; + allwinner,pipelines = <&fe0>; +}; diff --git a/MAINTAINERS b/MAINTAINERS index 61a323a6b2cf..9ef7b408beb3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -847,6 +847,12 @@ S: Maintained F: drivers/net/arcnet/ F: include/uapi/linux/if_arcnet.h +ARC PGU DRM DRIVER +M: Alexey Brodkin <abrodkin@synopsys.com> +S: Supported +F: drivers/gpu/drm/arc/ +F: Documentation/devicetree/bindings/display/snps,arcpgu.txt + ARM HDLCD DRM DRIVER M: Liviu Dudau <liviu.dudau@arm.com> S: Supported @@ -3803,6 +3809,13 @@ S: Supported F: drivers/gpu/drm/atmel-hlcdc/ F: Documentation/devicetree/bindings/drm/atmel/ +DRM DRIVERS FOR ALLWINNER A10 +M: Maxime Ripard <maxime.ripard@free-electrons.com> +L: dri-devel@lists.freedesktop.org +S: Supported +F: drivers/gpu/drm/sun4i/ +F: Documentation/devicetree/bindings/display/sunxi/sun4i-drm.txt + DRM DRIVERS FOR EXYNOS M: Inki Dae <inki.dae@samsung.com> M: Joonyoung Shim <jy0922.shim@samsung.com> @@ -3822,6 +3835,7 @@ L: dri-devel@lists.freedesktop.org S: Supported F: drivers/gpu/drm/fsl-dcu/ F: Documentation/devicetree/bindings/display/fsl,dcu.txt +F: Documentation/devicetree/bindings/display/fsl,tcon.txt F: Documentation/devicetree/bindings/display/panel/nec,nl4827hc19_05b.txt DRM DRIVERS FOR FREESCALE IMX diff --git a/arch/arc/boot/dts/axs10x_mb.dtsi b/arch/arc/boot/dts/axs10x_mb.dtsi index ab5d5701e11d..823f15ca68df 100644 --- a/arch/arc/boot/dts/axs10x_mb.dtsi +++ b/arch/arc/boot/dts/axs10x_mb.dtsi @@ -34,6 +34,12 @@ clock-frequency = <50000000>; #clock-cells = <0>; }; + + pguclk: pguclk { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <74440000>; + }; }; ethernet@0x18000 { @@ -155,6 +161,37 @@ clocks = <&i2cclk>; interrupts = <16>; + adv7511:adv7511@39{ + compatible="adi,adv7511"; + reg = <0x39>; + interrupts = <23>; + adi,input-depth = <8>; + adi,input-colorspace = "rgb"; + adi,input-clock = "1x"; + adi,clock-delay = <0x03>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + /* RGB/YUV input */ + port@0 { + reg = <0>; + adv7511_input:endpoint { + remote-endpoint = <&pgu_output>; + }; + }; + + /* HDMI output */ + port@1 { + reg = <1>; + adv7511_output: endpoint { + remote-endpoint = <&hdmi_connector_in>; + }; + }; + }; + }; + eeprom@0x54{ compatible = "24c01"; reg = <0x54>; @@ -168,6 +205,16 @@ }; }; + hdmi0: connector { + compatible = "hdmi-connector"; + type = "a"; + port { + hdmi_connector_in: endpoint { + remote-endpoint = <&adv7511_output>; + }; + }; + }; + gpio0:gpio@13000 { compatible = "snps,dw-apb-gpio"; reg = <0x13000 0x1000>; @@ -229,5 +276,19 @@ reg = <2>; }; }; + + pgu@17000 { + compatible = "snps,arcpgu"; + reg = <0x17000 0x400>; + encoder-slave = <&adv7511>; + clocks = <&pguclk>; + clock-names = "pxlclk"; + + port { + pgu_output: endpoint { + remote-endpoint = <&adv7511_input>; + }; + }; + }; }; }; diff --git a/arch/arm/boot/dts/exynos5250-arndale.dts b/arch/arm/boot/dts/exynos5250-arndale.dts index 8b2acc74aa76..85d819217d17 100644 --- a/arch/arm/boot/dts/exynos5250-arndale.dts +++ b/arch/arm/boot/dts/exynos5250-arndale.dts @@ -124,8 +124,6 @@ &dp { status = "okay"; samsung,color-space = <0>; - samsung,dynamic-range = <0>; - samsung,ycbcr-coeff = <0>; samsung,color-depth = <1>; samsung,link-rate = <0x0a>; samsung,lane-count = <4>; diff --git a/arch/arm/boot/dts/exynos5250-smdk5250.dts b/arch/arm/boot/dts/exynos5250-smdk5250.dts index 0f5dcd418af8..f30c2dbba4f5 100644 --- a/arch/arm/boot/dts/exynos5250-smdk5250.dts +++ b/arch/arm/boot/dts/exynos5250-smdk5250.dts @@ -80,8 +80,6 @@ &dp { samsung,color-space = <0>; - samsung,dynamic-range = <0>; - samsung,ycbcr-coeff = <0>; samsung,color-depth = <1>; samsung,link-rate = <0x0a>; samsung,lane-count = <4>; diff --git a/arch/arm/boot/dts/exynos5250-snow-common.dtsi b/arch/arm/boot/dts/exynos5250-snow-common.dtsi index 95210ef6a6b5..746808f401e5 100644 --- a/arch/arm/boot/dts/exynos5250-snow-common.dtsi +++ b/arch/arm/boot/dts/exynos5250-snow-common.dtsi @@ -236,12 +236,10 @@ pinctrl-names = "default"; pinctrl-0 = <&dp_hpd>; samsung,color-space = <0>; - samsung,dynamic-range = <0>; - samsung,ycbcr-coeff = <0>; samsung,color-depth = <1>; samsung,link-rate = <0x0a>; samsung,lane-count = <2>; - samsung,hpd-gpio = <&gpx0 7 GPIO_ACTIVE_HIGH>; + hpd-gpios = <&gpx0 7 GPIO_ACTIVE_HIGH>; ports { port@0 { diff --git a/arch/arm/boot/dts/exynos5250-spring.dts b/arch/arm/boot/dts/exynos5250-spring.dts index 0f500cb1eb2d..c607bed575d9 100644 --- a/arch/arm/boot/dts/exynos5250-spring.dts +++ b/arch/arm/boot/dts/exynos5250-spring.dts @@ -74,12 +74,10 @@ pinctrl-names = "default"; pinctrl-0 = <&dp_hpd_gpio>; samsung,color-space = <0>; - samsung,dynamic-range = <0>; - samsung,ycbcr-coeff = <0>; samsung,color-depth = <1>; samsung,link-rate = <0x0a>; samsung,lane-count = <1>; - samsung,hpd-gpio = <&gpc3 0 GPIO_ACTIVE_HIGH>; + hpd-gpios = <&gpc3 0 GPIO_ACTIVE_HIGH>; }; &ehci { diff --git a/arch/arm/boot/dts/exynos5420-peach-pit.dts b/arch/arm/boot/dts/exynos5420-peach-pit.dts index 3981ddb25036..7ddb6a066b28 100644 --- a/arch/arm/boot/dts/exynos5420-peach-pit.dts +++ b/arch/arm/boot/dts/exynos5420-peach-pit.dts @@ -157,12 +157,10 @@ pinctrl-names = "default"; pinctrl-0 = <&dp_hpd_gpio>; samsung,color-space = <0>; - samsung,dynamic-range = <0>; - samsung,ycbcr-coeff = <0>; samsung,color-depth = <1>; samsung,link-rate = <0x06>; samsung,lane-count = <2>; - samsung,hpd-gpio = <&gpx2 6 GPIO_ACTIVE_HIGH>; + hpd-gpios = <&gpx2 6 GPIO_ACTIVE_HIGH>; ports { port@0 { diff --git a/arch/arm/boot/dts/exynos5420-smdk5420.dts b/arch/arm/boot/dts/exynos5420-smdk5420.dts index 0785fedf441e..288817daa16c 100644 --- a/arch/arm/boot/dts/exynos5420-smdk5420.dts +++ b/arch/arm/boot/dts/exynos5420-smdk5420.dts @@ -102,8 +102,6 @@ pinctrl-names = "default"; pinctrl-0 = <&dp_hpd>; samsung,color-space = <0>; - samsung,dynamic-range = <0>; - samsung,ycbcr-coeff = <0>; samsung,color-depth = <1>; samsung,link-rate = <0x0a>; samsung,lane-count = <4>; diff --git a/arch/arm/boot/dts/exynos5800-peach-pi.dts b/arch/arm/boot/dts/exynos5800-peach-pi.dts index 6e9edc1610c4..6ba9aec15485 100644 --- a/arch/arm/boot/dts/exynos5800-peach-pi.dts +++ b/arch/arm/boot/dts/exynos5800-peach-pi.dts @@ -157,8 +157,6 @@ pinctrl-names = "default"; pinctrl-0 = <&dp_hpd_gpio>; samsung,color-space = <0>; - samsung,dynamic-range = <0>; - samsung,ycbcr-coeff = <0>; samsung,color-depth = <1>; samsung,link-rate = <0x0a>; samsung,lane-count = <2>; diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index f2a74d0b68ae..cd515029bb49 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -252,6 +252,8 @@ source "drivers/gpu/drm/rcar-du/Kconfig" source "drivers/gpu/drm/shmobile/Kconfig" +source "drivers/gpu/drm/sun4i/Kconfig" + source "drivers/gpu/drm/omapdrm/Kconfig" source "drivers/gpu/drm/tilcdc/Kconfig" @@ -281,3 +283,5 @@ source "drivers/gpu/drm/imx/Kconfig" source "drivers/gpu/drm/vc4/Kconfig" source "drivers/gpu/drm/etnaviv/Kconfig" + +source "drivers/gpu/drm/arc/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 6eb94fc561dc..1a26b4eb1ce0 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -1,4 +1,4 @@ -# + # Makefile for the drm device driver. This driver provides support for the # Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher. @@ -65,6 +65,7 @@ obj-$(CONFIG_DRM_ATMEL_HLCDC) += atmel-hlcdc/ obj-$(CONFIG_DRM_RCAR_DU) += rcar-du/ obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/ obj-y += omapdrm/ +obj-$(CONFIG_DRM_SUN4I) += sun4i/ obj-y += tilcdc/ obj-$(CONFIG_DRM_QXL) += qxl/ obj-$(CONFIG_DRM_BOCHS) += bochs/ @@ -78,3 +79,4 @@ obj-y += panel/ obj-y += bridge/ obj-$(CONFIG_DRM_FSL_DCU) += fsl-dcu/ obj-$(CONFIG_DRM_ETNAVIV) += etnaviv/ +obj-$(CONFIG_DRM_ARCPGU)+= arc/ diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c index f1e17d60055a..93462aea9faa 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c @@ -556,12 +556,10 @@ static struct pci_driver amdgpu_kms_pci_driver = { static int __init amdgpu_init(void) { amdgpu_sync_init(); -#ifdef CONFIG_VGA_CONSOLE if (vgacon_text_force()) { DRM_ERROR("VGACON disables amdgpu kernel modesetting.\n"); return -EINVAL; } -#endif DRM_INFO("amdgpu kernel modesetting enabled.\n"); driver = &kms_driver; pdriver = &amdgpu_kms_pci_driver; diff --git a/drivers/gpu/drm/arc/Kconfig b/drivers/gpu/drm/arc/Kconfig new file mode 100644 index 000000000000..f9a13b658fea --- /dev/null +++ b/drivers/gpu/drm/arc/Kconfig @@ -0,0 +1,10 @@ +config DRM_ARCPGU + tristate "ARC PGU" + depends on DRM && OF + select DRM_KMS_CMA_HELPER + select DRM_KMS_FB_HELPER + select DRM_KMS_HELPER + help + Choose this option if you have an ARC PGU controller. + + If M is selected the module will be called arcpgu. diff --git a/drivers/gpu/drm/arc/Makefile b/drivers/gpu/drm/arc/Makefile new file mode 100644 index 000000000000..d48fda70f857 --- /dev/null +++ b/drivers/gpu/drm/arc/Makefile @@ -0,0 +1,2 @@ +arcpgu-y := arcpgu_crtc.o arcpgu_hdmi.o arcpgu_drv.o +obj-$(CONFIG_DRM_ARCPGU) += arcpgu.o diff --git a/drivers/gpu/drm/arc/arcpgu.h b/drivers/gpu/drm/arc/arcpgu.h new file mode 100644 index 000000000000..86574b698a78 --- /dev/null +++ b/drivers/gpu/drm/arc/arcpgu.h @@ -0,0 +1,50 @@ +/* + * ARC PGU DRM driver. + * + * Copyright (C) 2016 Synopsys, Inc. (www.synopsys.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _ARCPGU_H_ +#define _ARCPGU_H_ + +struct arcpgu_drm_private { + void __iomem *regs; + struct clk *clk; + struct drm_fbdev_cma *fbdev; + struct drm_framebuffer *fb; + struct list_head event_list; + struct drm_crtc crtc; + struct drm_plane *plane; +}; + +#define crtc_to_arcpgu_priv(x) container_of(x, struct arcpgu_drm_private, crtc) + +static inline void arc_pgu_write(struct arcpgu_drm_private *arcpgu, + unsigned int reg, u32 value) +{ + iowrite32(value, arcpgu->regs + reg); +} + +static inline u32 arc_pgu_read(struct arcpgu_drm_private *arcpgu, + unsigned int reg) +{ + return ioread32(arcpgu->regs + reg); +} + +int arc_pgu_setup_crtc(struct drm_device *dev); +int arcpgu_drm_hdmi_init(struct drm_device *drm, struct device_node *np); +struct drm_fbdev_cma *arcpgu_fbdev_cma_init(struct drm_device *dev, + unsigned int preferred_bpp, unsigned int num_crtc, + unsigned int max_conn_count); + +#endif diff --git a/drivers/gpu/drm/arc/arcpgu_crtc.c b/drivers/gpu/drm/arc/arcpgu_crtc.c new file mode 100644 index 000000000000..92f8beff8e60 --- /dev/null +++ b/drivers/gpu/drm/arc/arcpgu_crtc.c @@ -0,0 +1,257 @@ +/* + * ARC PGU DRM driver. + * + * Copyright (C) 2016 Synopsys, Inc. (www.synopsys.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_plane_helper.h> +#include <linux/clk.h> +#include <linux/platform_data/simplefb.h> + +#include "arcpgu.h" +#include "arcpgu_regs.h" + +#define ENCODE_PGU_XY(x, y) ((((x) - 1) << 16) | ((y) - 1)) + +static struct simplefb_format supported_formats[] = { + { "r5g6b5", 16, {11, 5}, {5, 6}, {0, 5}, {0, 0}, DRM_FORMAT_RGB565 }, + { "r8g8b8", 24, {16, 8}, {8, 8}, {0, 8}, {0, 0}, DRM_FORMAT_RGB888 }, +}; + +static void arc_pgu_set_pxl_fmt(struct drm_crtc *crtc) +{ + struct arcpgu_drm_private *arcpgu = crtc_to_arcpgu_priv(crtc); + uint32_t pixel_format = crtc->primary->state->fb->pixel_format; + struct simplefb_format *format = NULL; + int i; + + for (i = 0; i < ARRAY_SIZE(supported_formats); i++) { + if (supported_formats[i].fourcc == pixel_format) + format = &supported_formats[i]; + } + + if (WARN_ON(!format)) + return; + + if (format->fourcc == DRM_FORMAT_RGB888) + arc_pgu_write(arcpgu, ARCPGU_REG_CTRL, + arc_pgu_read(arcpgu, ARCPGU_REG_CTRL) | + ARCPGU_MODE_RGB888_MASK); + +} + +static const struct drm_crtc_funcs arc_pgu_crtc_funcs = { + .destroy = drm_crtc_cleanup, + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .reset = drm_atomic_helper_crtc_reset, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, +}; + +static void arc_pgu_crtc_mode_set_nofb(struct drm_crtc *crtc) +{ + struct arcpgu_drm_private *arcpgu = crtc_to_arcpgu_priv(crtc); + struct drm_display_mode *m = &crtc->state->adjusted_mode; + u32 val; + + arc_pgu_write(arcpgu, ARCPGU_REG_FMT, + ENCODE_PGU_XY(m->crtc_htotal, m->crtc_vtotal)); + + arc_pgu_write(arcpgu, ARCPGU_REG_HSYNC, + ENCODE_PGU_XY(m->crtc_hsync_start - m->crtc_hdisplay, + m->crtc_hsync_end - m->crtc_hdisplay)); + + arc_pgu_write(arcpgu, ARCPGU_REG_VSYNC, + ENCODE_PGU_XY(m->crtc_vsync_start - m->crtc_vdisplay, + m->crtc_vsync_end - m->crtc_vdisplay)); + + arc_pgu_write(arcpgu, ARCPGU_REG_ACTIVE, + ENCODE_PGU_XY(m->crtc_hblank_end - m->crtc_hblank_start, + m->crtc_vblank_end - m->crtc_vblank_start)); + + val = arc_pgu_read(arcpgu, ARCPGU_REG_CTRL); + + if (m->flags & DRM_MODE_FLAG_PVSYNC) + val |= ARCPGU_CTRL_VS_POL_MASK << ARCPGU_CTRL_VS_POL_OFST; + else + val &= ~(ARCPGU_CTRL_VS_POL_MASK << ARCPGU_CTRL_VS_POL_OFST); + + if (m->flags & DRM_MODE_FLAG_PHSYNC) + val |= ARCPGU_CTRL_HS_POL_MASK << ARCPGU_CTRL_HS_POL_OFST; + else + val &= ~(ARCPGU_CTRL_HS_POL_MASK << ARCPGU_CTRL_HS_POL_OFST); + + arc_pgu_write(arcpgu, ARCPGU_REG_CTRL, val); + arc_pgu_write(arcpgu, ARCPGU_REG_STRIDE, 0); + arc_pgu_write(arcpgu, ARCPGU_REG_START_SET, 1); + + arc_pgu_set_pxl_fmt(crtc); + + clk_set_rate(arcpgu->clk, m->crtc_clock * 1000); +} + +static void arc_pgu_crtc_enable(struct drm_crtc *crtc) +{ + struct arcpgu_drm_private *arcpgu = crtc_to_arcpgu_priv(crtc); + + clk_prepare_enable(arcpgu->clk); + arc_pgu_write(arcpgu, ARCPGU_REG_CTRL, + arc_pgu_read(arcpgu, ARCPGU_REG_CTRL) | + ARCPGU_CTRL_ENABLE_MASK); +} + +static void arc_pgu_crtc_disable(struct drm_crtc *crtc) +{ + struct arcpgu_drm_private *arcpgu = crtc_to_arcpgu_priv(crtc); + + if (!crtc->primary->fb) + return; + + clk_disable_unprepare(arcpgu->clk); + arc_pgu_write(arcpgu, ARCPGU_REG_CTRL, + arc_pgu_read(arcpgu, ARCPGU_REG_CTRL) & + ~ARCPGU_CTRL_ENABLE_MASK); +} + +static int arc_pgu_crtc_atomic_check(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + struct arcpgu_drm_private *arcpgu = crtc_to_arcpgu_priv(crtc); + struct drm_display_mode *mode = &state->adjusted_mode; + long rate, clk_rate = mode->clock * 1000; + + rate = clk_round_rate(arcpgu->clk, clk_rate); + if (rate != clk_rate) + return -EINVAL; + + return 0; +} + +static void arc_pgu_crtc_atomic_begin(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + struct arcpgu_drm_private *arcpgu = crtc_to_arcpgu_priv(crtc); + unsigned long flags; + + if (crtc->state->event) { + struct drm_pending_vblank_event *event = crtc->state->event; + + crtc->state->event = NULL; + event->pipe = drm_crtc_index(crtc); + + WARN_ON(drm_crtc_vblank_get(crtc) != 0); + + spin_lock_irqsave(&crtc->dev->event_lock, flags); + list_add_tail(&event->base.link, &arcpgu->event_list); + spin_unlock_irqrestore(&crtc->dev->event_lock, flags); + } +} + +static const struct drm_crtc_helper_funcs arc_pgu_crtc_helper_funcs = { + .mode_set = drm_helper_crtc_mode_set, + .mode_set_base = drm_helper_crtc_mode_set_base, + .mode_set_nofb = arc_pgu_crtc_mode_set_nofb, + .enable = arc_pgu_crtc_enable, + .disable = arc_pgu_crtc_disable, + .prepare = arc_pgu_crtc_disable, + .commit = arc_pgu_crtc_enable, + .atomic_check = arc_pgu_crtc_atomic_check, + .atomic_begin = arc_pgu_crtc_atomic_begin, +}; + +static void arc_pgu_plane_atomic_update(struct drm_plane *plane, + struct drm_plane_state *state) +{ + struct arcpgu_drm_private *arcpgu; + struct drm_gem_cma_object *gem; + + if (!plane->state->crtc || !plane->state->fb) + return; + + arcpgu = crtc_to_arcpgu_priv(plane->state->crtc); + gem = drm_fb_cma_get_gem_obj(plane->state->fb, 0); + arc_pgu_write(arcpgu, ARCPGU_REG_BUF0_ADDR, gem->paddr); +} + +static const struct drm_plane_helper_funcs arc_pgu_plane_helper_funcs = { + .prepare_fb = NULL, + .cleanup_fb = NULL, + .atomic_update = arc_pgu_plane_atomic_update, +}; + +static void arc_pgu_plane_destroy(struct drm_plane *plane) +{ + drm_plane_helper_disable(plane); + drm_plane_cleanup(plane); +} + +static const struct drm_plane_funcs arc_pgu_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = arc_pgu_plane_destroy, + .reset = drm_atomic_helper_plane_reset, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, +}; + +static struct drm_plane *arc_pgu_plane_init(struct drm_device *drm) +{ + struct arcpgu_drm_private *arcpgu = drm->dev_private; + struct drm_plane *plane = NULL; + u32 formats[ARRAY_SIZE(supported_formats)], i; + int ret; + + plane = devm_kzalloc(drm->dev, sizeof(*plane), GFP_KERNEL); + if (!plane) + return ERR_PTR(-ENOMEM); + + for (i = 0; i < ARRAY_SIZE(supported_formats); i++) + formats[i] = supported_formats[i].fourcc; + + ret = drm_universal_plane_init(drm, plane, 0xff, &arc_pgu_plane_funcs, + formats, ARRAY_SIZE(formats), + DRM_PLANE_TYPE_PRIMARY, NULL); + if (ret) + return ERR_PTR(ret); + + drm_plane_helper_add(plane, &arc_pgu_plane_helper_funcs); + arcpgu->plane = plane; + + return plane; +} + +int arc_pgu_setup_crtc(struct drm_device *drm) +{ + struct arcpgu_drm_private *arcpgu = drm->dev_private; + struct drm_plane *primary; + int ret; + + primary = arc_pgu_plane_init(drm); + if (IS_ERR(primary)) + return PTR_ERR(primary); + + ret = drm_crtc_init_with_planes(drm, &arcpgu->crtc, primary, NULL, + &arc_pgu_crtc_funcs, NULL); + if (ret) { + arc_pgu_plane_destroy(primary); + return ret; + } + + drm_crtc_helper_add(&arcpgu->crtc, &arc_pgu_crtc_helper_funcs); + return 0; +} diff --git a/drivers/gpu/drm/arc/arcpgu_drv.c b/drivers/gpu/drm/arc/arcpgu_drv.c new file mode 100644 index 000000000000..5b35e5dbae38 --- /dev/null +++ b/drivers/gpu/drm/arc/arcpgu_drv.c @@ -0,0 +1,282 @@ +/* + * ARC PGU DRM driver. + * + * Copyright (C) 2016 Synopsys, Inc. (www.synopsys.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/clk.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_atomic_helper.h> + +#include "arcpgu.h" +#include "arcpgu_regs.h" + +static void arcpgu_fb_output_poll_changed(struct drm_device *dev) +{ + struct arcpgu_drm_private *arcpgu = dev->dev_private; + + if (arcpgu->fbdev) + drm_fbdev_cma_hotplug_event(arcpgu->fbdev); +} + +static int arcpgu_atomic_commit(struct drm_device *dev, + struct drm_atomic_state *state, bool async) +{ + return drm_atomic_helper_commit(dev, state, false); +} + +static struct drm_mode_config_funcs arcpgu_drm_modecfg_funcs = { + .fb_create = drm_fb_cma_create, + .output_poll_changed = arcpgu_fb_output_poll_changed, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = arcpgu_atomic_commit, +}; + +static void arcpgu_setup_mode_config(struct drm_device *drm) +{ + drm_mode_config_init(drm); + drm->mode_config.min_width = 0; + drm->mode_config.min_height = 0; + drm->mode_config.max_width = 1920; + drm->mode_config.max_height = 1080; + drm->mode_config.funcs = &arcpgu_drm_modecfg_funcs; +} + +int arcpgu_gem_mmap(struct file *filp, struct vm_area_struct *vma) +{ + int ret; + + ret = drm_gem_mmap(filp, vma); + if (ret) + return ret; + + vma->vm_page_prot = pgprot_noncached(vm_get_page_prot(vma->vm_flags)); + return 0; +} + +static const struct file_operations arcpgu_drm_ops = { + .owner = THIS_MODULE, + .open = drm_open, + .release = drm_release, + .unlocked_ioctl = drm_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = drm_compat_ioctl, +#endif + .poll = drm_poll, + .read = drm_read, + .llseek = no_llseek, + .mmap = arcpgu_gem_mmap, +}; + +static void arcpgu_preclose(struct drm_device *drm, struct drm_file *file) +{ + struct arcpgu_drm_private *arcpgu = drm->dev_private; + struct drm_pending_vblank_event *e, *t; + unsigned long flags; + + spin_lock_irqsave(&drm->event_lock, flags); + list_for_each_entry_safe(e, t, &arcpgu->event_list, base.link) { + if (e->base.file_priv != file) + continue; + list_del(&e->base.link); + e->base.destroy(&e->base); + } + spin_unlock_irqrestore(&drm->event_lock, flags); +} + +static void arcpgu_lastclose(struct drm_device *drm) +{ + struct arcpgu_drm_private *arcpgu = drm->dev_private; + + drm_fbdev_cma_restore_mode(arcpgu->fbdev); +} + +static int arcpgu_load(struct drm_device *drm) +{ + struct platform_device *pdev = to_platform_device(drm->dev); + struct arcpgu_drm_private *arcpgu; + struct device_node *encoder_node; + struct resource *res; + int ret; + + arcpgu = devm_kzalloc(&pdev->dev, sizeof(*arcpgu), GFP_KERNEL); + if (arcpgu == NULL) + return -ENOMEM; + + drm->dev_private = arcpgu; + + arcpgu->clk = devm_clk_get(drm->dev, "pxlclk"); + if (IS_ERR(arcpgu->clk)) + return PTR_ERR(arcpgu->clk); + + INIT_LIST_HEAD(&arcpgu->event_list); + + arcpgu_setup_mode_config(drm); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + arcpgu->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(arcpgu->regs)) { + dev_err(drm->dev, "Could not remap IO mem\n"); + return PTR_ERR(arcpgu->regs); + } + + dev_info(drm->dev, "arc_pgu ID: 0x%x\n", + arc_pgu_read(arcpgu, ARCPGU_REG_ID)); + + if (dma_set_mask_and_coherent(drm->dev, DMA_BIT_MASK(32))) + return -ENODEV; + + if (arc_pgu_setup_crtc(drm) < 0) + return -ENODEV; + + /* find the encoder node and initialize it */ + encoder_node = of_parse_phandle(drm->dev->of_node, "encoder-slave", 0); + if (!encoder_node) { + dev_err(drm->dev, "failed to get an encoder slave node\n"); + return -ENODEV; + } + + ret = arcpgu_drm_hdmi_init(drm, encoder_node); + if (ret < 0) + return ret; + + drm_mode_config_reset(drm); + drm_kms_helper_poll_init(drm); + + arcpgu->fbdev = drm_fbdev_cma_init(drm, 16, + drm->mode_config.num_crtc, + drm->mode_config.num_connector); + if (IS_ERR(arcpgu->fbdev)) { + ret = PTR_ERR(arcpgu->fbdev); + arcpgu->fbdev = NULL; + return -ENODEV; + } + + platform_set_drvdata(pdev, arcpgu); + return 0; +} + +int arcpgu_unload(struct drm_device *drm) +{ + struct arcpgu_drm_private *arcpgu = drm->dev_private; + + if (arcpgu->fbdev) { + drm_fbdev_cma_fini(arcpgu->fbdev); + arcpgu->fbdev = NULL; + } + drm_kms_helper_poll_fini(drm); + drm_vblank_cleanup(drm); + drm_mode_config_cleanup(drm); + + return 0; +} + +static struct drm_driver arcpgu_drm_driver = { + .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME | + DRIVER_ATOMIC, + .preclose = arcpgu_preclose, + .lastclose = arcpgu_lastclose, + .name = "drm-arcpgu", + .desc = "ARC PGU Controller", + .date = "20160219", + .major = 1, + .minor = 0, + .patchlevel = 0, + .fops = &arcpgu_drm_ops, + .dumb_create = drm_gem_cma_dumb_create, + .dumb_map_offset = drm_gem_cma_dumb_map_offset, + .dumb_destroy = drm_gem_dumb_destroy, + .get_vblank_counter = drm_vblank_no_hw_counter, + .prime_handle_to_fd = drm_gem_prime_handle_to_fd, + .prime_fd_to_handle = drm_gem_prime_fd_to_handle, + .gem_free_object = drm_gem_cma_free_object, + .gem_vm_ops = &drm_gem_cma_vm_ops, + .gem_prime_export = drm_gem_prime_export, + .gem_prime_import = drm_gem_prime_import, + .gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table, + .gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table, + .gem_prime_vmap = drm_gem_cma_prime_vmap, + .gem_prime_vunmap = drm_gem_cma_prime_vunmap, + .gem_prime_mmap = drm_gem_cma_prime_mmap, +}; + +static int arcpgu_probe(struct platform_device *pdev) +{ + struct drm_device *drm; + int ret; + + drm = drm_dev_alloc(&arcpgu_drm_driver, &pdev->dev); + if (!drm) + return -ENOMEM; + + ret = arcpgu_load(drm); + if (ret) + goto err_unref; + + ret = drm_dev_register(drm, 0); + if (ret) + goto err_unload; + + ret = drm_connector_register_all(drm); + if (ret) + goto err_unregister; + + return 0; + +err_unregister: + drm_dev_unregister(drm); + +err_unload: + arcpgu_unload(drm); + +err_unref: + drm_dev_unref(drm); + + return ret; +} + +static int arcpgu_remove(struct platform_device *pdev) +{ + struct drm_device *drm = platform_get_drvdata(pdev); + + drm_connector_unregister_all(drm); + drm_dev_unregister(drm); + arcpgu_unload(drm); + drm_dev_unref(drm); + + return 0; +} + +static const struct of_device_id arcpgu_of_table[] = { + {.compatible = "snps,arcpgu"}, + {} +}; + +MODULE_DEVICE_TABLE(of, arcpgu_of_table); + +static struct platform_driver arcpgu_platform_driver = { + .probe = arcpgu_probe, + .remove = arcpgu_remove, + .driver = { + .name = "arcpgu", + .of_match_table = arcpgu_of_table, + }, +}; + +module_platform_driver(arcpgu_platform_driver); + +MODULE_AUTHOR("Carlos Palminha <palminha@synopsys.com>"); +MODULE_DESCRIPTION("ARC PGU DRM driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/arc/arcpgu_hdmi.c b/drivers/gpu/drm/arc/arcpgu_hdmi.c new file mode 100644 index 000000000000..08b6baeb320d --- /dev/null +++ b/drivers/gpu/drm/arc/arcpgu_hdmi.c @@ -0,0 +1,201 @@ +/* + * ARC PGU DRM driver. + * + * Copyright (C) 2016 Synopsys, Inc. (www.synopsys.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <drm/drm_crtc_helper.h> +#include <drm/drm_encoder_slave.h> +#include <drm/drm_atomic_helper.h> + +#include "arcpgu.h" + +struct arcpgu_drm_connector { + struct drm_connector connector; + struct drm_encoder_slave *encoder_slave; +}; + +static int arcpgu_drm_connector_get_modes(struct drm_connector *connector) +{ + const struct drm_encoder_slave_funcs *sfuncs; + struct drm_encoder_slave *slave; + struct arcpgu_drm_connector *con = + container_of(connector, struct arcpgu_drm_connector, connector); + + slave = con->encoder_slave; + if (slave == NULL) { + dev_err(connector->dev->dev, + "connector_get_modes: cannot find slave encoder for connector\n"); + return 0; + } + + sfuncs = slave->slave_funcs; + if (sfuncs->get_modes == NULL) + return 0; + + return sfuncs->get_modes(&slave->base, connector); +} + +struct drm_encoder * +arcpgu_drm_connector_best_encoder(struct drm_connector *connector) +{ + struct drm_encoder_slave *slave; + struct arcpgu_drm_connector *con = + container_of(connector, struct arcpgu_drm_connector, connector); + + slave = con->encoder_slave; + if (slave == NULL) { + dev_err(connector->dev->dev, + "connector_best_encoder: cannot find slave encoder for connector\n"); + return NULL; + } + + return &slave->base; +} + +static enum drm_connector_status +arcpgu_drm_connector_detect(struct drm_connector *connector, bool force) +{ + enum drm_connector_status status = connector_status_unknown; + const struct drm_encoder_slave_funcs *sfuncs; + struct drm_encoder_slave *slave; + + struct arcpgu_drm_connector *con = + container_of(connector, struct arcpgu_drm_connector, connector); + + slave = con->encoder_slave; + if (slave == NULL) { + dev_err(connector->dev->dev, + "connector_detect: cannot find slave encoder for connector\n"); + return status; + } + + sfuncs = slave->slave_funcs; + if (sfuncs && sfuncs->detect) + return sfuncs->detect(&slave->base, connector); + + dev_err(connector->dev->dev, "connector_detect: could not detect slave funcs\n"); + return status; +} + +static void arcpgu_drm_connector_destroy(struct drm_connector *connector) +{ + drm_connector_unregister(connector); + drm_connector_cleanup(connector); +} + +static const struct drm_connector_helper_funcs +arcpgu_drm_connector_helper_funcs = { + .get_modes = arcpgu_drm_connector_get_modes, + .best_encoder = arcpgu_drm_connector_best_encoder, +}; + +static const struct drm_connector_funcs arcpgu_drm_connector_funcs = { + .dpms = drm_helper_connector_dpms, + .reset = drm_atomic_helper_connector_reset, + .detect = arcpgu_drm_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = arcpgu_drm_connector_destroy, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static struct drm_encoder_helper_funcs arcpgu_drm_encoder_helper_funcs = { + .dpms = drm_i2c_encoder_dpms, + .mode_fixup = drm_i2c_encoder_mode_fixup, + .mode_set = drm_i2c_encoder_mode_set, + .prepare = drm_i2c_encoder_prepare, + .commit = drm_i2c_encoder_commit, + .detect = drm_i2c_encoder_detect, +}; + +static struct drm_encoder_funcs arcpgu_drm_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + +int arcpgu_drm_hdmi_init(struct drm_device *drm, struct device_node *np) +{ + struct arcpgu_drm_connector *arcpgu_connector; + struct drm_i2c_encoder_driver *driver; + struct drm_encoder_slave *encoder; + struct drm_connector *connector; + struct i2c_client *i2c_slave; + int ret; + + encoder = devm_kzalloc(drm->dev, sizeof(*encoder), GFP_KERNEL); + if (encoder == NULL) + return -ENOMEM; + + i2c_slave = of_find_i2c_device_by_node(np); + if (!i2c_slave || !i2c_get_clientdata(i2c_slave)) { + dev_err(drm->dev, "failed to find i2c slave encoder\n"); + return -EPROBE_DEFER; + } + + if (i2c_slave->dev.driver == NULL) { + dev_err(drm->dev, "failed to find i2c slave driver\n"); + return -EPROBE_DEFER; + } + + driver = + to_drm_i2c_encoder_driver(to_i2c_driver(i2c_slave->dev.driver)); + ret = driver->encoder_init(i2c_slave, drm, encoder); + if (ret) { + dev_err(drm->dev, "failed to initialize i2c encoder slave\n"); + return ret; + } + + encoder->base.possible_crtcs = 1; + encoder->base.possible_clones = 0; + ret = drm_encoder_init(drm, &encoder->base, &arcpgu_drm_encoder_funcs, + DRM_MODE_ENCODER_TMDS, NULL); + if (ret) + return ret; + + drm_encoder_helper_add(&encoder->base, + &arcpgu_drm_encoder_helper_funcs); + + arcpgu_connector = devm_kzalloc(drm->dev, sizeof(*arcpgu_connector), + GFP_KERNEL); + if (!arcpgu_connector) { + ret = -ENOMEM; + goto error_encoder_cleanup; + } + + connector = &arcpgu_connector->connector; + drm_connector_helper_add(connector, &arcpgu_drm_connector_helper_funcs); + ret = drm_connector_init(drm, connector, &arcpgu_drm_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA); + if (ret < 0) { + dev_err(drm->dev, "failed to initialize drm connector\n"); + goto error_encoder_cleanup; + } + + ret = drm_mode_connector_attach_encoder(connector, &encoder->base); + if (ret < 0) { + dev_err(drm->dev, "could not attach connector to encoder\n"); + drm_connector_unregister(connector); + goto error_connector_cleanup; + } + + arcpgu_connector->encoder_slave = encoder; + + return 0; + +error_connector_cleanup: + drm_connector_cleanup(connector); + +error_encoder_cleanup: + drm_encoder_cleanup(&encoder->base); + return ret; +} diff --git a/drivers/gpu/drm/arc/arcpgu_regs.h b/drivers/gpu/drm/arc/arcpgu_regs.h new file mode 100644 index 000000000000..95a13a84c373 --- /dev/null +++ b/drivers/gpu/drm/arc/arcpgu_regs.h @@ -0,0 +1,40 @@ +/* + * ARC PGU DRM driver. + * + * Copyright (C) 2016 Synopsys, Inc. (www.synopsys.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _ARC_PGU_REGS_H_ +#define _ARC_PGU_REGS_H_ + +#define ARCPGU_REG_CTRL 0x00 +#define ARCPGU_REG_STAT 0x04 +#define ARCPGU_REG_FMT 0x10 +#define ARCPGU_REG_HSYNC 0x14 +#define ARCPGU_REG_VSYNC 0x18 +#define ARCPGU_REG_ACTIVE 0x1c +#define ARCPGU_REG_BUF0_ADDR 0x40 +#define ARCPGU_REG_STRIDE 0x50 +#define ARCPGU_REG_START_SET 0x84 + +#define ARCPGU_REG_ID 0x3FC + +#define ARCPGU_CTRL_ENABLE_MASK 0x02 +#define ARCPGU_CTRL_VS_POL_MASK 0x1 +#define ARCPGU_CTRL_VS_POL_OFST 0x3 +#define ARCPGU_CTRL_HS_POL_MASK 0x1 +#define ARCPGU_CTRL_HS_POL_OFST 0x4 +#define ARCPGU_MODE_RGB888_MASK 0x04 +#define ARCPGU_STAT_BUSY_MASK 0x02 + +#endif diff --git a/drivers/gpu/drm/ast/ast_drv.c b/drivers/gpu/drm/ast/ast_drv.c index 9a32d9dfdd26..fcd9c0714836 100644 --- a/drivers/gpu/drm/ast/ast_drv.c +++ b/drivers/gpu/drm/ast/ast_drv.c @@ -218,10 +218,8 @@ static struct drm_driver driver = { static int __init ast_init(void) { -#ifdef CONFIG_VGA_CONSOLE if (vgacon_text_force() && ast_modeset == -1) return -EINVAL; -#endif if (ast_modeset == 0) return -EINVAL; diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c index 58c4f785cf84..8df0aaf98725 100644 --- a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c @@ -32,6 +32,23 @@ #include "atmel_hlcdc_dc.h" /** + * Atmel HLCDC CRTC state structure + * + * @base: base CRTC state + * @output_mode: RGBXXX output mode + */ +struct atmel_hlcdc_crtc_state { + struct drm_crtc_state base; + unsigned int output_mode; +}; + +static inline struct atmel_hlcdc_crtc_state * +drm_crtc_state_to_atmel_hlcdc_crtc_state(struct drm_crtc_state *state) +{ + return container_of(state, struct atmel_hlcdc_crtc_state, base); +} + +/** * Atmel HLCDC CRTC structure * * @base: base DRM CRTC structure @@ -59,6 +76,7 @@ static void atmel_hlcdc_crtc_mode_set_nofb(struct drm_crtc *c) struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c); struct regmap *regmap = crtc->dc->hlcdc->regmap; struct drm_display_mode *adj = &c->state->adjusted_mode; + struct atmel_hlcdc_crtc_state *state; unsigned long mode_rate; struct videomode vm; unsigned long prate; @@ -112,15 +130,27 @@ static void atmel_hlcdc_crtc_mode_set_nofb(struct drm_crtc *c) if (adj->flags & DRM_MODE_FLAG_NHSYNC) cfg |= ATMEL_HLCDC_HSPOL; + state = drm_crtc_state_to_atmel_hlcdc_crtc_state(c->state); + cfg |= state->output_mode << 8; + regmap_update_bits(regmap, ATMEL_HLCDC_CFG(5), ATMEL_HLCDC_HSPOL | ATMEL_HLCDC_VSPOL | ATMEL_HLCDC_VSPDLYS | ATMEL_HLCDC_VSPDLYE | ATMEL_HLCDC_DISPPOL | ATMEL_HLCDC_DISPDLY | ATMEL_HLCDC_VSPSU | ATMEL_HLCDC_VSPHO | - ATMEL_HLCDC_GUARDTIME_MASK, + ATMEL_HLCDC_GUARDTIME_MASK | ATMEL_HLCDC_MODE_MASK, cfg); } +static bool atmel_hlcdc_crtc_mode_fixup(struct drm_crtc *c, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c); + + return atmel_hlcdc_dc_mode_valid(crtc->dc, adjusted_mode) == MODE_OK; +} + static void atmel_hlcdc_crtc_disable(struct drm_crtc *c) { struct drm_device *dev = c->dev; @@ -221,15 +251,79 @@ void atmel_hlcdc_crtc_resume(struct drm_crtc *c) } } +#define ATMEL_HLCDC_RGB444_OUTPUT BIT(0) +#define ATMEL_HLCDC_RGB565_OUTPUT BIT(1) +#define ATMEL_HLCDC_RGB666_OUTPUT BIT(2) +#define ATMEL_HLCDC_RGB888_OUTPUT BIT(3) +#define ATMEL_HLCDC_OUTPUT_MODE_MASK GENMASK(3, 0) + +static int atmel_hlcdc_crtc_select_output_mode(struct drm_crtc_state *state) +{ + unsigned int output_fmts = ATMEL_HLCDC_OUTPUT_MODE_MASK; + struct atmel_hlcdc_crtc_state *hstate; + struct drm_connector_state *cstate; + struct drm_connector *connector; + struct atmel_hlcdc_crtc *crtc; + int i; + + crtc = drm_crtc_to_atmel_hlcdc_crtc(state->crtc); + + for_each_connector_in_state(state->state, connector, cstate, i) { + struct drm_display_info *info = &connector->display_info; + unsigned int supported_fmts = 0; + int j; + + if (!cstate->crtc) + continue; + + for (j = 0; j < info->num_bus_formats; j++) { + switch (info->bus_formats[j]) { + case MEDIA_BUS_FMT_RGB444_1X12: + supported_fmts |= ATMEL_HLCDC_RGB444_OUTPUT; + break; + case MEDIA_BUS_FMT_RGB565_1X16: + supported_fmts |= ATMEL_HLCDC_RGB565_OUTPUT; + break; + case MEDIA_BUS_FMT_RGB666_1X18: + supported_fmts |= ATMEL_HLCDC_RGB666_OUTPUT; + break; + case MEDIA_BUS_FMT_RGB888_1X24: + supported_fmts |= ATMEL_HLCDC_RGB888_OUTPUT; + break; + default: + break; + } + } + + if (crtc->dc->desc->conflicting_output_formats) + output_fmts &= supported_fmts; + else + output_fmts |= supported_fmts; + } + + if (!output_fmts) + return -EINVAL; + + hstate = drm_crtc_state_to_atmel_hlcdc_crtc_state(state); + hstate->output_mode = fls(output_fmts) - 1; + + return 0; +} + static int atmel_hlcdc_crtc_atomic_check(struct drm_crtc *c, struct drm_crtc_state *s) { - struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c); + int ret; - if (atmel_hlcdc_dc_mode_valid(crtc->dc, &s->adjusted_mode) != MODE_OK) - return -EINVAL; + ret = atmel_hlcdc_crtc_select_output_mode(s); + if (ret) + return ret; + + ret = atmel_hlcdc_plane_prepare_disc_area(s); + if (ret) + return ret; - return atmel_hlcdc_plane_prepare_disc_area(s); + return atmel_hlcdc_plane_prepare_ahb_routing(s); } static void atmel_hlcdc_crtc_atomic_begin(struct drm_crtc *c, @@ -254,6 +348,7 @@ static void atmel_hlcdc_crtc_atomic_flush(struct drm_crtc *crtc, } static const struct drm_crtc_helper_funcs lcdc_crtc_helper_funcs = { + .mode_fixup = atmel_hlcdc_crtc_mode_fixup, .mode_set = drm_helper_crtc_mode_set, .mode_set_nofb = atmel_hlcdc_crtc_mode_set_nofb, .mode_set_base = drm_helper_crtc_mode_set_base, @@ -292,13 +387,60 @@ void atmel_hlcdc_crtc_irq(struct drm_crtc *c) atmel_hlcdc_crtc_finish_page_flip(drm_crtc_to_atmel_hlcdc_crtc(c)); } +void atmel_hlcdc_crtc_reset(struct drm_crtc *crtc) +{ + struct atmel_hlcdc_crtc_state *state; + + if (crtc->state && crtc->state->mode_blob) + drm_property_unreference_blob(crtc->state->mode_blob); + + if (crtc->state) { + state = drm_crtc_state_to_atmel_hlcdc_crtc_state(crtc->state); + kfree(state); + } + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (state) { + crtc->state = &state->base; + crtc->state->crtc = crtc; + } +} + +static struct drm_crtc_state * +atmel_hlcdc_crtc_duplicate_state(struct drm_crtc *crtc) +{ + struct atmel_hlcdc_crtc_state *state, *cur; + + if (WARN_ON(!crtc->state)) + return NULL; + + state = kmalloc(sizeof(*state), GFP_KERNEL); + if (state) + __drm_atomic_helper_crtc_duplicate_state(crtc, &state->base); + + cur = drm_crtc_state_to_atmel_hlcdc_crtc_state(crtc->state); + state->output_mode = cur->output_mode; + + return &state->base; +} + +static void atmel_hlcdc_crtc_destroy_state(struct drm_crtc *crtc, + struct drm_crtc_state *s) +{ + struct atmel_hlcdc_crtc_state *state; + + state = drm_crtc_state_to_atmel_hlcdc_crtc_state(s); + __drm_atomic_helper_crtc_destroy_state(crtc, s); + kfree(state); +} + static const struct drm_crtc_funcs atmel_hlcdc_crtc_funcs = { .page_flip = drm_atomic_helper_page_flip, .set_config = drm_atomic_helper_set_config, .destroy = atmel_hlcdc_crtc_destroy, - .reset = drm_atomic_helper_crtc_reset, - .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, - .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, + .reset = atmel_hlcdc_crtc_reset, + .atomic_duplicate_state = atmel_hlcdc_crtc_duplicate_state, + .atomic_destroy_state = atmel_hlcdc_crtc_destroy_state, }; int atmel_hlcdc_crtc_create(struct drm_device *dev) diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c index 3d8d16402d07..8ded7645747e 100644 --- a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c @@ -50,6 +50,10 @@ static const struct atmel_hlcdc_dc_desc atmel_hlcdc_dc_at91sam9n12 = { .min_height = 0, .max_width = 1280, .max_height = 860, + .max_spw = 0x3f, + .max_vpw = 0x3f, + .max_hpw = 0xff, + .conflicting_output_formats = true, .nlayers = ARRAY_SIZE(atmel_hlcdc_at91sam9n12_layers), .layers = atmel_hlcdc_at91sam9n12_layers, }; @@ -134,6 +138,10 @@ static const struct atmel_hlcdc_dc_desc atmel_hlcdc_dc_at91sam9x5 = { .min_height = 0, .max_width = 800, .max_height = 600, + .max_spw = 0x3f, + .max_vpw = 0x3f, + .max_hpw = 0xff, + .conflicting_output_formats = true, .nlayers = ARRAY_SIZE(atmel_hlcdc_at91sam9x5_layers), .layers = atmel_hlcdc_at91sam9x5_layers, }; @@ -237,6 +245,10 @@ static const struct atmel_hlcdc_dc_desc atmel_hlcdc_dc_sama5d3 = { .min_height = 0, .max_width = 2048, .max_height = 2048, + .max_spw = 0x3f, + .max_vpw = 0x3f, + .max_hpw = 0x1ff, + .conflicting_output_formats = true, .nlayers = ARRAY_SIZE(atmel_hlcdc_sama5d3_layers), .layers = atmel_hlcdc_sama5d3_layers, }; @@ -320,6 +332,9 @@ static const struct atmel_hlcdc_dc_desc atmel_hlcdc_dc_sama5d4 = { .min_height = 0, .max_width = 2048, .max_height = 2048, + .max_spw = 0xff, + .max_vpw = 0xff, + .max_hpw = 0x3ff, .nlayers = ARRAY_SIZE(atmel_hlcdc_sama5d4_layers), .layers = atmel_hlcdc_sama5d4_layers, }; @@ -358,19 +373,19 @@ int atmel_hlcdc_dc_mode_valid(struct atmel_hlcdc_dc *dc, int hback_porch = mode->htotal - mode->hsync_end; int hsync_len = mode->hsync_end - mode->hsync_start; - if (hsync_len > 0x40 || hsync_len < 1) + if (hsync_len > dc->desc->max_spw + 1 || hsync_len < 1) return MODE_HSYNC; - if (vsync_len > 0x40 || vsync_len < 1) + if (vsync_len > dc->desc->max_spw + 1 || vsync_len < 1) return MODE_VSYNC; - if (hfront_porch > 0x200 || hfront_porch < 1 || - hback_porch > 0x200 || hback_porch < 1 || + if (hfront_porch > dc->desc->max_hpw + 1 || hfront_porch < 1 || + hback_porch > dc->desc->max_hpw + 1 || hback_porch < 1 || mode->hdisplay < 1) return MODE_H_ILLEGAL; - if (vfront_porch > 0x40 || vfront_porch < 1 || - vback_porch > 0x40 || vback_porch < 0 || + if (vfront_porch > dc->desc->max_vpw + 1 || vfront_porch < 1 || + vback_porch > dc->desc->max_vpw || vback_porch < 0 || mode->vdisplay < 1) return MODE_V_ILLEGAL; @@ -427,11 +442,102 @@ static void atmel_hlcdc_fb_output_poll_changed(struct drm_device *dev) } } +struct atmel_hlcdc_dc_commit { + struct work_struct work; + struct drm_device *dev; + struct drm_atomic_state *state; +}; + +static void +atmel_hlcdc_dc_atomic_complete(struct atmel_hlcdc_dc_commit *commit) +{ + struct drm_device *dev = commit->dev; + struct atmel_hlcdc_dc *dc = dev->dev_private; + struct drm_atomic_state *old_state = commit->state; + + /* Apply the atomic update. */ + drm_atomic_helper_commit_modeset_disables(dev, old_state); + drm_atomic_helper_commit_planes(dev, old_state, false); + drm_atomic_helper_commit_modeset_enables(dev, old_state); + + drm_atomic_helper_wait_for_vblanks(dev, old_state); + + drm_atomic_helper_cleanup_planes(dev, old_state); + + drm_atomic_state_free(old_state); + + /* Complete the commit, wake up any waiter. */ + spin_lock(&dc->commit.wait.lock); + dc->commit.pending = false; + wake_up_all_locked(&dc->commit.wait); + spin_unlock(&dc->commit.wait.lock); + + kfree(commit); +} + +static void atmel_hlcdc_dc_atomic_work(struct work_struct *work) +{ + struct atmel_hlcdc_dc_commit *commit = + container_of(work, struct atmel_hlcdc_dc_commit, work); + + atmel_hlcdc_dc_atomic_complete(commit); +} + +static int atmel_hlcdc_dc_atomic_commit(struct drm_device *dev, + struct drm_atomic_state *state, + bool async) +{ + struct atmel_hlcdc_dc *dc = dev->dev_private; + struct atmel_hlcdc_dc_commit *commit; + int ret; + + ret = drm_atomic_helper_prepare_planes(dev, state); + if (ret) + return ret; + + /* Allocate the commit object. */ + commit = kzalloc(sizeof(*commit), GFP_KERNEL); + if (!commit) { + ret = -ENOMEM; + goto error; + } + + INIT_WORK(&commit->work, atmel_hlcdc_dc_atomic_work); + commit->dev = dev; + commit->state = state; + + spin_lock(&dc->commit.wait.lock); + ret = wait_event_interruptible_locked(dc->commit.wait, + !dc->commit.pending); + if (ret == 0) + dc->commit.pending = true; + spin_unlock(&dc->commit.wait.lock); + + if (ret) { + kfree(commit); + goto error; + } + + /* Swap the state, this is the point of no return. */ + drm_atomic_helper_swap_state(dev, state); + + if (async) + queue_work(dc->wq, &commit->work); + else + atmel_hlcdc_dc_atomic_complete(commit); + + return 0; + +error: + drm_atomic_helper_cleanup_planes(dev, state); + return ret; +} + static const struct drm_mode_config_funcs mode_config_funcs = { .fb_create = atmel_hlcdc_fb_create, .output_poll_changed = atmel_hlcdc_fb_output_poll_changed, .atomic_check = drm_atomic_helper_check, - .atomic_commit = drm_atomic_helper_commit, + .atomic_commit = atmel_hlcdc_dc_atomic_commit, }; static int atmel_hlcdc_dc_modeset_init(struct drm_device *dev) @@ -445,7 +551,7 @@ static int atmel_hlcdc_dc_modeset_init(struct drm_device *dev) ret = atmel_hlcdc_create_outputs(dev); if (ret) { - dev_err(dev->dev, "failed to create panel: %d\n", ret); + dev_err(dev->dev, "failed to create HLCDC outputs: %d\n", ret); return ret; } @@ -509,6 +615,7 @@ static int atmel_hlcdc_dc_load(struct drm_device *dev) if (!dc->wq) return -ENOMEM; + init_waitqueue_head(&dc->commit.wait); dc->desc = match->data; dc->hlcdc = dev_get_drvdata(dev->dev->parent); dev->dev_private = dc; @@ -584,38 +691,10 @@ static void atmel_hlcdc_dc_unload(struct drm_device *dev) destroy_workqueue(dc->wq); } -static int atmel_hlcdc_dc_connector_plug_all(struct drm_device *dev) -{ - struct drm_connector *connector, *failed; - int ret; - - mutex_lock(&dev->mode_config.mutex); - list_for_each_entry(connector, &dev->mode_config.connector_list, head) { - ret = drm_connector_register(connector); - if (ret) { - failed = connector; - goto err; - } - } - mutex_unlock(&dev->mode_config.mutex); - return 0; - -err: - list_for_each_entry(connector, &dev->mode_config.connector_list, head) { - if (failed == connector) - break; - - drm_connector_unregister(connector); - } - mutex_unlock(&dev->mode_config.mutex); - - return ret; -} - static void atmel_hlcdc_dc_connector_unplug_all(struct drm_device *dev) { mutex_lock(&dev->mode_config.mutex); - drm_connector_unplug_all(dev); + drm_connector_unregister_all(dev); mutex_unlock(&dev->mode_config.mutex); } @@ -736,7 +815,7 @@ static int atmel_hlcdc_dc_drm_probe(struct platform_device *pdev) if (ret) goto err_unload; - ret = atmel_hlcdc_dc_connector_plug_all(ddev); + ret = drm_connector_register_all(ddev); if (ret) goto err_unregister; diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h index fed517f297da..7a47f8c094d0 100644 --- a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h @@ -50,6 +50,11 @@ * @min_height: minimum height supported by the Display Controller * @max_width: maximum width supported by the Display Controller * @max_height: maximum height supported by the Display Controller + * @max_spw: maximum vertical/horizontal pulse width + * @max_vpw: maximum vertical back/front porch width + * @max_hpw: maximum horizontal back/front porch width + * @conflicting_output_formats: true if RGBXXX output formats conflict with + * each other. * @layers: a layer description table describing available layers * @nlayers: layer description table size */ @@ -58,6 +63,10 @@ struct atmel_hlcdc_dc_desc { int min_height; int max_width; int max_height; + int max_spw; + int max_vpw; + int max_hpw; + bool conflicting_output_formats; const struct atmel_hlcdc_layer_desc *layers; int nlayers; }; @@ -128,6 +137,7 @@ struct atmel_hlcdc_planes { * @planes: instantiated planes * @layers: active HLCDC layer * @wq: display controller workqueue + * @commit: used for async commit handling */ struct atmel_hlcdc_dc { const struct atmel_hlcdc_dc_desc *desc; @@ -137,6 +147,10 @@ struct atmel_hlcdc_dc { struct atmel_hlcdc_planes *planes; struct atmel_hlcdc_layer *layers[ATMEL_HLCDC_MAX_LAYERS]; struct workqueue_struct *wq; + struct { + wait_queue_head_t wait; + bool pending; + } commit; }; extern struct atmel_hlcdc_formats atmel_hlcdc_plane_rgb_formats; @@ -149,6 +163,7 @@ struct atmel_hlcdc_planes * atmel_hlcdc_create_planes(struct drm_device *dev); int atmel_hlcdc_plane_prepare_disc_area(struct drm_crtc_state *c_state); +int atmel_hlcdc_plane_prepare_ahb_routing(struct drm_crtc_state *c_state); void atmel_hlcdc_crtc_irq(struct drm_crtc *c); diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c index 0f7ec016e7a9..39802c0539b6 100644 --- a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c @@ -27,16 +27,6 @@ #include "atmel_hlcdc_dc.h" /** - * Atmel HLCDC RGB output mode - */ -enum atmel_hlcdc_connector_rgb_mode { - ATMEL_HLCDC_CONNECTOR_RGB444, - ATMEL_HLCDC_CONNECTOR_RGB565, - ATMEL_HLCDC_CONNECTOR_RGB666, - ATMEL_HLCDC_CONNECTOR_RGB888, -}; - -/** * Atmel HLCDC RGB connector structure * * This structure stores RGB slave device information. @@ -44,13 +34,13 @@ enum atmel_hlcdc_connector_rgb_mode { * @connector: DRM connector * @encoder: DRM encoder * @dc: pointer to the atmel_hlcdc_dc structure - * @dpms: current DPMS mode + * @panel: panel connected on the RGB output */ struct atmel_hlcdc_rgb_output { struct drm_connector connector; struct drm_encoder encoder; struct atmel_hlcdc_dc *dc; - int dpms; + struct drm_panel *panel; }; static inline struct atmel_hlcdc_rgb_output * @@ -66,91 +56,31 @@ drm_encoder_to_atmel_hlcdc_rgb_output(struct drm_encoder *encoder) return container_of(encoder, struct atmel_hlcdc_rgb_output, encoder); } -/** - * Atmel HLCDC Panel device structure - * - * This structure is specialization of the slave device structure to - * interface with drm panels. - * - * @base: base slave device fields - * @panel: drm panel attached to this slave device - */ -struct atmel_hlcdc_panel { - struct atmel_hlcdc_rgb_output base; - struct drm_panel *panel; -}; - -static inline struct atmel_hlcdc_panel * -atmel_hlcdc_rgb_output_to_panel(struct atmel_hlcdc_rgb_output *output) -{ - return container_of(output, struct atmel_hlcdc_panel, base); -} - -static void atmel_hlcdc_panel_encoder_enable(struct drm_encoder *encoder) +static void atmel_hlcdc_rgb_encoder_enable(struct drm_encoder *encoder) { struct atmel_hlcdc_rgb_output *rgb = drm_encoder_to_atmel_hlcdc_rgb_output(encoder); - struct atmel_hlcdc_panel *panel = atmel_hlcdc_rgb_output_to_panel(rgb); - drm_panel_enable(panel->panel); + if (rgb->panel) { + drm_panel_prepare(rgb->panel); + drm_panel_enable(rgb->panel); + } } -static void atmel_hlcdc_panel_encoder_disable(struct drm_encoder *encoder) +static void atmel_hlcdc_rgb_encoder_disable(struct drm_encoder *encoder) { struct atmel_hlcdc_rgb_output *rgb = drm_encoder_to_atmel_hlcdc_rgb_output(encoder); - struct atmel_hlcdc_panel *panel = atmel_hlcdc_rgb_output_to_panel(rgb); - drm_panel_disable(panel->panel); -} - -static bool -atmel_hlcdc_panel_encoder_mode_fixup(struct drm_encoder *encoder, - const struct drm_display_mode *mode, - struct drm_display_mode *adjusted) -{ - return true; -} - -static void -atmel_hlcdc_rgb_encoder_mode_set(struct drm_encoder *encoder, - struct drm_display_mode *mode, - struct drm_display_mode *adjusted) -{ - struct atmel_hlcdc_rgb_output *rgb = - drm_encoder_to_atmel_hlcdc_rgb_output(encoder); - struct drm_display_info *info = &rgb->connector.display_info; - unsigned int cfg; - - cfg = 0; - - if (info->num_bus_formats) { - switch (info->bus_formats[0]) { - case MEDIA_BUS_FMT_RGB565_1X16: - cfg |= ATMEL_HLCDC_CONNECTOR_RGB565 << 8; - break; - case MEDIA_BUS_FMT_RGB666_1X18: - cfg |= ATMEL_HLCDC_CONNECTOR_RGB666 << 8; - break; - case MEDIA_BUS_FMT_RGB888_1X24: - cfg |= ATMEL_HLCDC_CONNECTOR_RGB888 << 8; - break; - case MEDIA_BUS_FMT_RGB444_1X12: - default: - break; - } + if (rgb->panel) { + drm_panel_disable(rgb->panel); + drm_panel_unprepare(rgb->panel); } - - regmap_update_bits(rgb->dc->hlcdc->regmap, ATMEL_HLCDC_CFG(5), - ATMEL_HLCDC_MODE_MASK, - cfg); } static const struct drm_encoder_helper_funcs atmel_hlcdc_panel_encoder_helper_funcs = { - .mode_fixup = atmel_hlcdc_panel_encoder_mode_fixup, - .mode_set = atmel_hlcdc_rgb_encoder_mode_set, - .disable = atmel_hlcdc_panel_encoder_disable, - .enable = atmel_hlcdc_panel_encoder_enable, + .disable = atmel_hlcdc_rgb_encoder_disable, + .enable = atmel_hlcdc_rgb_encoder_enable, }; static void atmel_hlcdc_rgb_encoder_destroy(struct drm_encoder *encoder) @@ -167,9 +97,11 @@ static int atmel_hlcdc_panel_get_modes(struct drm_connector *connector) { struct atmel_hlcdc_rgb_output *rgb = drm_connector_to_atmel_hlcdc_rgb_output(connector); - struct atmel_hlcdc_panel *panel = atmel_hlcdc_rgb_output_to_panel(rgb); - return panel->panel->funcs->get_modes(panel->panel); + if (rgb->panel) + return rgb->panel->funcs->get_modes(rgb->panel); + + return 0; } static int atmel_hlcdc_rgb_mode_valid(struct drm_connector *connector, @@ -201,7 +133,13 @@ static const struct drm_connector_helper_funcs atmel_hlcdc_panel_connector_helpe static enum drm_connector_status atmel_hlcdc_panel_connector_detect(struct drm_connector *connector, bool force) { - return connector_status_connected; + struct atmel_hlcdc_rgb_output *rgb = + drm_connector_to_atmel_hlcdc_rgb_output(connector); + + if (rgb->panel) + return connector_status_connected; + + return connector_status_disconnected; } static void @@ -209,9 +147,10 @@ atmel_hlcdc_panel_connector_destroy(struct drm_connector *connector) { struct atmel_hlcdc_rgb_output *rgb = drm_connector_to_atmel_hlcdc_rgb_output(connector); - struct atmel_hlcdc_panel *panel = atmel_hlcdc_rgb_output_to_panel(rgb); - drm_panel_detach(panel->panel); + if (rgb->panel) + drm_panel_detach(rgb->panel); + drm_connector_cleanup(connector); } @@ -225,88 +164,122 @@ static const struct drm_connector_funcs atmel_hlcdc_panel_connector_funcs = { .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, }; -static int atmel_hlcdc_create_panel_output(struct drm_device *dev, - struct of_endpoint *ep) +static int atmel_hlcdc_check_endpoint(struct drm_device *dev, + const struct of_endpoint *ep) { - struct atmel_hlcdc_dc *dc = dev->dev_private; struct device_node *np; - struct drm_panel *p = NULL; - struct atmel_hlcdc_panel *panel; - int ret; + void *obj; np = of_graph_get_remote_port_parent(ep->local_node); - if (!np) - return -EINVAL; - p = of_drm_find_panel(np); + obj = of_drm_find_panel(np); + if (!obj) + obj = of_drm_find_bridge(np); + of_node_put(np); - if (!p) - return -EPROBE_DEFER; + return obj ? 0 : -EPROBE_DEFER; +} - panel = devm_kzalloc(dev->dev, sizeof(*panel), GFP_KERNEL); - if (!panel) - return -EINVAL; +static int atmel_hlcdc_attach_endpoint(struct drm_device *dev, + const struct of_endpoint *ep) +{ + struct atmel_hlcdc_dc *dc = dev->dev_private; + struct atmel_hlcdc_rgb_output *output; + struct device_node *np; + struct drm_panel *panel; + struct drm_bridge *bridge; + int ret; - panel->base.dpms = DRM_MODE_DPMS_OFF; + output = devm_kzalloc(dev->dev, sizeof(*output), GFP_KERNEL); + if (!output) + return -EINVAL; - panel->base.dc = dc; + output->dc = dc; - drm_encoder_helper_add(&panel->base.encoder, + drm_encoder_helper_add(&output->encoder, &atmel_hlcdc_panel_encoder_helper_funcs); - ret = drm_encoder_init(dev, &panel->base.encoder, + ret = drm_encoder_init(dev, &output->encoder, &atmel_hlcdc_panel_encoder_funcs, - DRM_MODE_ENCODER_LVDS, NULL); + DRM_MODE_ENCODER_NONE, NULL); if (ret) return ret; - panel->base.connector.dpms = DRM_MODE_DPMS_OFF; - panel->base.connector.polled = DRM_CONNECTOR_POLL_CONNECT; - drm_connector_helper_add(&panel->base.connector, - &atmel_hlcdc_panel_connector_helper_funcs); - ret = drm_connector_init(dev, &panel->base.connector, - &atmel_hlcdc_panel_connector_funcs, - DRM_MODE_CONNECTOR_LVDS); - if (ret) - goto err_encoder_cleanup; + output->encoder.possible_crtcs = 0x1; + + np = of_graph_get_remote_port_parent(ep->local_node); - drm_mode_connector_attach_encoder(&panel->base.connector, - &panel->base.encoder); - panel->base.encoder.possible_crtcs = 0x1; + ret = -EPROBE_DEFER; + + panel = of_drm_find_panel(np); + if (panel) { + of_node_put(np); + output->connector.dpms = DRM_MODE_DPMS_OFF; + output->connector.polled = DRM_CONNECTOR_POLL_CONNECT; + drm_connector_helper_add(&output->connector, + &atmel_hlcdc_panel_connector_helper_funcs); + ret = drm_connector_init(dev, &output->connector, + &atmel_hlcdc_panel_connector_funcs, + DRM_MODE_CONNECTOR_Unknown); + if (ret) + goto err_encoder_cleanup; + + drm_mode_connector_attach_encoder(&output->connector, + &output->encoder); + + ret = drm_panel_attach(panel, &output->connector); + if (ret) { + drm_connector_cleanup(&output->connector); + goto err_encoder_cleanup; + } - drm_panel_attach(p, &panel->base.connector); - panel->panel = p; + output->panel = panel; - return 0; + return 0; + } + + bridge = of_drm_find_bridge(np); + of_node_put(np); + + if (bridge) { + output->encoder.bridge = bridge; + bridge->encoder = &output->encoder; + ret = drm_bridge_attach(dev, bridge); + if (!ret) + return 0; + } err_encoder_cleanup: - drm_encoder_cleanup(&panel->base.encoder); + drm_encoder_cleanup(&output->encoder); return ret; } int atmel_hlcdc_create_outputs(struct drm_device *dev) { - struct device_node *port_np, *np; + struct device_node *ep_np = NULL; struct of_endpoint ep; int ret; - port_np = of_get_child_by_name(dev->dev->of_node, "port"); - if (!port_np) - return -EINVAL; - - np = of_get_child_by_name(port_np, "endpoint"); - of_node_put(port_np); + for_each_endpoint_of_node(dev->dev->of_node, ep_np) { + ret = of_graph_parse_endpoint(ep_np, &ep); + if (!ret) + ret = atmel_hlcdc_check_endpoint(dev, &ep); - if (!np) - return -EINVAL; + of_node_put(ep_np); + if (ret) + return ret; + } - ret = of_graph_parse_endpoint(np, &ep); - of_node_put(port_np); + for_each_endpoint_of_node(dev->dev->of_node, ep_np) { + ret = of_graph_parse_endpoint(ep_np, &ep); + if (!ret) + ret = atmel_hlcdc_attach_endpoint(dev, &ep); - if (ret) - return ret; + of_node_put(ep_np); + if (ret) + return ret; + } - /* We currently only support panel output */ - return atmel_hlcdc_create_panel_output(dev, &ep); + return 0; } diff --git a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c index d65dcaee3832..aef3ca8a81fa 100644 --- a/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c +++ b/drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c @@ -37,6 +37,7 @@ * @xstride: value to add to the pixel pointer between each line * @pstride: value to add to the pixel pointer between each pixel * @nplanes: number of planes (deduced from pixel_format) + * @prepared: plane update has been prepared */ struct atmel_hlcdc_plane_state { struct drm_plane_state base; @@ -58,12 +59,15 @@ struct atmel_hlcdc_plane_state { int disc_w; int disc_h; + int ahb_id; + /* These fields are private and should not be touched */ int bpp[ATMEL_HLCDC_MAX_PLANES]; unsigned int offsets[ATMEL_HLCDC_MAX_PLANES]; int xstride[ATMEL_HLCDC_MAX_PLANES]; int pstride[ATMEL_HLCDC_MAX_PLANES]; int nplanes; + bool prepared; }; static inline struct atmel_hlcdc_plane_state * @@ -359,8 +363,10 @@ atmel_hlcdc_plane_update_general_settings(struct atmel_hlcdc_plane *plane, atmel_hlcdc_layer_update_cfg(&plane->layer, ATMEL_HLCDC_LAYER_DMA_CFG_ID, - ATMEL_HLCDC_LAYER_DMA_BLEN_MASK, - ATMEL_HLCDC_LAYER_DMA_BLEN_INCR16); + ATMEL_HLCDC_LAYER_DMA_BLEN_MASK | + ATMEL_HLCDC_LAYER_DMA_SIF, + ATMEL_HLCDC_LAYER_DMA_BLEN_INCR16 | + state->ahb_id); atmel_hlcdc_layer_update_cfg(&plane->layer, layout->general_config, ATMEL_HLCDC_LAYER_ITER2BL | @@ -435,6 +441,41 @@ static void atmel_hlcdc_plane_update_buffers(struct atmel_hlcdc_plane *plane, } } +int atmel_hlcdc_plane_prepare_ahb_routing(struct drm_crtc_state *c_state) +{ + unsigned int ahb_load[2] = { }; + struct drm_plane *plane; + + drm_atomic_crtc_state_for_each_plane(plane, c_state) { + struct atmel_hlcdc_plane_state *plane_state; + struct drm_plane_state *plane_s; + unsigned int pixels, load = 0; + int i; + + plane_s = drm_atomic_get_plane_state(c_state->state, plane); + if (IS_ERR(plane_s)) + return PTR_ERR(plane_s); + + plane_state = + drm_plane_state_to_atmel_hlcdc_plane_state(plane_s); + + pixels = (plane_state->src_w * plane_state->src_h) - + (plane_state->disc_w * plane_state->disc_h); + + for (i = 0; i < plane_state->nplanes; i++) + load += pixels * plane_state->bpp[i]; + + if (ahb_load[0] <= ahb_load[1]) + plane_state->ahb_id = 0; + else + plane_state->ahb_id = 1; + + ahb_load[plane_state->ahb_id] += load; + } + + return 0; +} + int atmel_hlcdc_plane_prepare_disc_area(struct drm_crtc_state *c_state) { @@ -714,12 +755,54 @@ static int atmel_hlcdc_plane_atomic_check(struct drm_plane *p, static int atmel_hlcdc_plane_prepare_fb(struct drm_plane *p, const struct drm_plane_state *new_state) { + /* + * FIXME: we should avoid this const -> non-const cast but it's + * currently the only solution we have to modify the ->prepared + * state and rollback the update request. + * Ideally, we should rework the code to attach all the resources + * to atmel_hlcdc_plane_state (including the DMA desc allocation), + * but this require a complete rework of the atmel_hlcdc_layer + * code. + */ + struct drm_plane_state *s = (struct drm_plane_state *)new_state; + struct atmel_hlcdc_plane *plane = drm_plane_to_atmel_hlcdc_plane(p); + struct atmel_hlcdc_plane_state *state = + drm_plane_state_to_atmel_hlcdc_plane_state(s); + int ret; + + ret = atmel_hlcdc_layer_update_start(&plane->layer); + if (!ret) + state->prepared = true; + + return ret; +} + +static void atmel_hlcdc_plane_cleanup_fb(struct drm_plane *p, + const struct drm_plane_state *old_state) +{ + /* + * FIXME: we should avoid this const -> non-const cast but it's + * currently the only solution we have to modify the ->prepared + * state and rollback the update request. + * Ideally, we should rework the code to attach all the resources + * to atmel_hlcdc_plane_state (including the DMA desc allocation), + * but this require a complete rework of the atmel_hlcdc_layer + * code. + */ + struct drm_plane_state *s = (struct drm_plane_state *)old_state; struct atmel_hlcdc_plane *plane = drm_plane_to_atmel_hlcdc_plane(p); + struct atmel_hlcdc_plane_state *state = + drm_plane_state_to_atmel_hlcdc_plane_state(s); - if (!new_state->fb) - return 0; + /* + * The Request has already been applied or cancelled, nothing to do + * here. + */ + if (!state->prepared) + return; - return atmel_hlcdc_layer_update_start(&plane->layer); + atmel_hlcdc_layer_update_rollback(&plane->layer); + state->prepared = false; } static void atmel_hlcdc_plane_atomic_update(struct drm_plane *p, @@ -844,6 +927,7 @@ static void atmel_hlcdc_plane_init_properties(struct atmel_hlcdc_plane *plane, static struct drm_plane_helper_funcs atmel_hlcdc_layer_plane_helper_funcs = { .prepare_fb = atmel_hlcdc_plane_prepare_fb, + .cleanup_fb = atmel_hlcdc_plane_cleanup_fb, .atomic_check = atmel_hlcdc_plane_atomic_check, .atomic_update = atmel_hlcdc_plane_atomic_update, .atomic_disable = atmel_hlcdc_plane_atomic_disable, @@ -883,6 +967,7 @@ atmel_hlcdc_plane_atomic_duplicate_state(struct drm_plane *p) return NULL; copy->disc_updated = false; + copy->prepared = false; if (copy->base.fb) drm_framebuffer_reference(copy->base.fb); diff --git a/drivers/gpu/drm/bochs/bochs_fbdev.c b/drivers/gpu/drm/bochs/bochs_fbdev.c index 7520bf81fc25..369f11f10c72 100644 --- a/drivers/gpu/drm/bochs/bochs_fbdev.c +++ b/drivers/gpu/drm/bochs/bochs_fbdev.c @@ -162,22 +162,7 @@ static int bochs_fbdev_destroy(struct bochs_device *bochs) return 0; } -void bochs_fb_gamma_set(struct drm_crtc *crtc, u16 red, u16 green, - u16 blue, int regno) -{ -} - -void bochs_fb_gamma_get(struct drm_crtc *crtc, u16 *red, u16 *green, - u16 *blue, int regno) -{ - *red = regno; - *green = regno; - *blue = regno; -} - static const struct drm_fb_helper_funcs bochs_fb_helper_funcs = { - .gamma_set = bochs_fb_gamma_set, - .gamma_get = bochs_fb_gamma_get, .fb_probe = bochsfb_create, }; diff --git a/drivers/gpu/drm/bochs/bochs_kms.c b/drivers/gpu/drm/bochs/bochs_kms.c index 96926f09e0c9..89adfd916a7c 100644 --- a/drivers/gpu/drm/bochs/bochs_kms.c +++ b/drivers/gpu/drm/bochs/bochs_kms.c @@ -93,11 +93,6 @@ static void bochs_crtc_commit(struct drm_crtc *crtc) { } -static void bochs_crtc_gamma_set(struct drm_crtc *crtc, u16 *red, u16 *green, - u16 *blue, uint32_t start, uint32_t size) -{ -} - static int bochs_crtc_page_flip(struct drm_crtc *crtc, struct drm_framebuffer *fb, struct drm_pending_vblank_event *event, @@ -120,7 +115,6 @@ static int bochs_crtc_page_flip(struct drm_crtc *crtc, /* These provide the minimum set of functions required to handle a CRTC */ static const struct drm_crtc_funcs bochs_crtc_funcs = { - .gamma_set = bochs_crtc_gamma_set, .set_config = drm_crtc_helper_set_config, .destroy = drm_crtc_cleanup, .page_flip = bochs_crtc_page_flip, @@ -140,7 +134,6 @@ static void bochs_crtc_init(struct drm_device *dev) struct drm_crtc *crtc = &bochs->crtc; drm_crtc_init(dev, crtc, &bochs_crtc_funcs); - drm_mode_crtc_set_gamma_size(crtc, 256); drm_crtc_helper_add(crtc, &bochs_helper_funcs); } diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 27e2022de89d..efd94e00c3e5 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -40,4 +40,6 @@ config DRM_PARADE_PS8622 ---help--- Parade eDP-LVDS bridge chip driver. +source "drivers/gpu/drm/bridge/analogix/Kconfig" + endmenu diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index f13c33d67c03..ff821f4b5833 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -4,3 +4,4 @@ obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o +obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/ diff --git a/drivers/gpu/drm/bridge/analogix/Kconfig b/drivers/gpu/drm/bridge/analogix/Kconfig new file mode 100644 index 000000000000..80f286fa3a69 --- /dev/null +++ b/drivers/gpu/drm/bridge/analogix/Kconfig @@ -0,0 +1,3 @@ +config DRM_ANALOGIX_DP + tristate + depends on DRM diff --git a/drivers/gpu/drm/bridge/analogix/Makefile b/drivers/gpu/drm/bridge/analogix/Makefile new file mode 100644 index 000000000000..cd4010ba6890 --- /dev/null +++ b/drivers/gpu/drm/bridge/analogix/Makefile @@ -0,0 +1,2 @@ +analogix_dp-objs := analogix_dp_core.o analogix_dp_reg.o +obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix_dp.o diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c new file mode 100644 index 000000000000..7699597070a1 --- /dev/null +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.c @@ -0,0 +1,1430 @@ +/* +* Analogix DP (Display Port) core interface driver. +* +* Copyright (C) 2012 Samsung Electronics Co., Ltd. +* Author: Jingoo Han <jg1.han@samsung.com> +* +* This program is free software; you can redistribute it and/or modify it +* under the terms of the GNU General Public License as published by the +* Free Software Foundation; either version 2 of the License, or (at your +* option) any later version. +*/ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/gpio.h> +#include <linux/component.h> +#include <linux/phy/phy.h> + +#include <drm/drmP.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_panel.h> + +#include <drm/bridge/analogix_dp.h> + +#include "analogix_dp_core.h" + +#define to_dp(nm) container_of(nm, struct analogix_dp_device, nm) + +struct bridge_init { + struct i2c_client *client; + struct device_node *node; +}; + +static void analogix_dp_init_dp(struct analogix_dp_device *dp) +{ + analogix_dp_reset(dp); + + analogix_dp_swreset(dp); + + analogix_dp_init_analog_param(dp); + analogix_dp_init_interrupt(dp); + + /* SW defined function Normal operation */ + analogix_dp_enable_sw_function(dp); + + analogix_dp_config_interrupt(dp); + analogix_dp_init_analog_func(dp); + + analogix_dp_init_hpd(dp); + analogix_dp_init_aux(dp); +} + +static int analogix_dp_detect_hpd(struct analogix_dp_device *dp) +{ + int timeout_loop = 0; + + while (timeout_loop < DP_TIMEOUT_LOOP_COUNT) { + if (analogix_dp_get_plug_in_status(dp) == 0) + return 0; + + timeout_loop++; + usleep_range(10, 11); + } + + /* + * Some edp screen do not have hpd signal, so we can't just + * return failed when hpd plug in detect failed, DT property + * "force-hpd" would indicate whether driver need this. + */ + if (!dp->force_hpd) + return -ETIMEDOUT; + + /* + * The eDP TRM indicate that if HPD_STATUS(RO) is 0, AUX CH + * will not work, so we need to give a force hpd action to + * set HPD_STATUS manually. + */ + dev_dbg(dp->dev, "failed to get hpd plug status, try to force hpd\n"); + + analogix_dp_force_hpd(dp); + + if (analogix_dp_get_plug_in_status(dp) != 0) { + dev_err(dp->dev, "failed to get hpd plug in status\n"); + return -EINVAL; + } + + dev_dbg(dp->dev, "success to get plug in status after force hpd\n"); + + return 0; +} + +static unsigned char analogix_dp_calc_edid_check_sum(unsigned char *edid_data) +{ + int i; + unsigned char sum = 0; + + for (i = 0; i < EDID_BLOCK_LENGTH; i++) + sum = sum + edid_data[i]; + + return sum; +} + +static int analogix_dp_read_edid(struct analogix_dp_device *dp) +{ + unsigned char *edid = dp->edid; + unsigned int extend_block = 0; + unsigned char sum; + unsigned char test_vector; + int retval; + + /* + * EDID device address is 0x50. + * However, if necessary, you must have set upper address + * into E-EDID in I2C device, 0x30. + */ + + /* Read Extension Flag, Number of 128-byte EDID extension blocks */ + retval = analogix_dp_read_byte_from_i2c(dp, I2C_EDID_DEVICE_ADDR, + EDID_EXTENSION_FLAG, + &extend_block); + if (retval) + return retval; + + if (extend_block > 0) { + dev_dbg(dp->dev, "EDID data includes a single extension!\n"); + + /* Read EDID data */ + retval = analogix_dp_read_bytes_from_i2c(dp, + I2C_EDID_DEVICE_ADDR, + EDID_HEADER_PATTERN, + EDID_BLOCK_LENGTH, + &edid[EDID_HEADER_PATTERN]); + if (retval != 0) { + dev_err(dp->dev, "EDID Read failed!\n"); + return -EIO; + } + sum = analogix_dp_calc_edid_check_sum(edid); + if (sum != 0) { + dev_err(dp->dev, "EDID bad checksum!\n"); + return -EIO; + } + + /* Read additional EDID data */ + retval = analogix_dp_read_bytes_from_i2c(dp, + I2C_EDID_DEVICE_ADDR, + EDID_BLOCK_LENGTH, + EDID_BLOCK_LENGTH, + &edid[EDID_BLOCK_LENGTH]); + if (retval != 0) { + dev_err(dp->dev, "EDID Read failed!\n"); + return -EIO; + } + sum = analogix_dp_calc_edid_check_sum(&edid[EDID_BLOCK_LENGTH]); + if (sum != 0) { + dev_err(dp->dev, "EDID bad checksum!\n"); + return -EIO; + } + + analogix_dp_read_byte_from_dpcd(dp, DP_TEST_REQUEST, + &test_vector); + if (test_vector & DP_TEST_LINK_EDID_READ) { + analogix_dp_write_byte_to_dpcd(dp, + DP_TEST_EDID_CHECKSUM, + edid[EDID_BLOCK_LENGTH + EDID_CHECKSUM]); + analogix_dp_write_byte_to_dpcd(dp, + DP_TEST_RESPONSE, + DP_TEST_EDID_CHECKSUM_WRITE); + } + } else { + dev_info(dp->dev, "EDID data does not include any extensions.\n"); + + /* Read EDID data */ + retval = analogix_dp_read_bytes_from_i2c(dp, + I2C_EDID_DEVICE_ADDR, EDID_HEADER_PATTERN, + EDID_BLOCK_LENGTH, &edid[EDID_HEADER_PATTERN]); + if (retval != 0) { + dev_err(dp->dev, "EDID Read failed!\n"); + return -EIO; + } + sum = analogix_dp_calc_edid_check_sum(edid); + if (sum != 0) { + dev_err(dp->dev, "EDID bad checksum!\n"); + return -EIO; + } + + analogix_dp_read_byte_from_dpcd(dp, DP_TEST_REQUEST, + &test_vector); + if (test_vector & DP_TEST_LINK_EDID_READ) { + analogix_dp_write_byte_to_dpcd(dp, + DP_TEST_EDID_CHECKSUM, edid[EDID_CHECKSUM]); + analogix_dp_write_byte_to_dpcd(dp, + DP_TEST_RESPONSE, DP_TEST_EDID_CHECKSUM_WRITE); + } + } + + dev_dbg(dp->dev, "EDID Read success!\n"); + return 0; +} + +static int analogix_dp_handle_edid(struct analogix_dp_device *dp) +{ + u8 buf[12]; + int i; + int retval; + + /* Read DPCD DP_DPCD_REV~RECEIVE_PORT1_CAP_1 */ + retval = analogix_dp_read_bytes_from_dpcd(dp, DP_DPCD_REV, 12, buf); + if (retval) + return retval; + + /* Read EDID */ + for (i = 0; i < 3; i++) { + retval = analogix_dp_read_edid(dp); + if (!retval) + break; + } + + return retval; +} + +static void +analogix_dp_enable_rx_to_enhanced_mode(struct analogix_dp_device *dp, + bool enable) +{ + u8 data; + + analogix_dp_read_byte_from_dpcd(dp, DP_LANE_COUNT_SET, &data); + + if (enable) + analogix_dp_write_byte_to_dpcd(dp, DP_LANE_COUNT_SET, + DP_LANE_COUNT_ENHANCED_FRAME_EN | + DPCD_LANE_COUNT_SET(data)); + else + analogix_dp_write_byte_to_dpcd(dp, DP_LANE_COUNT_SET, + DPCD_LANE_COUNT_SET(data)); +} + +static int analogix_dp_is_enhanced_mode_available(struct analogix_dp_device *dp) +{ + u8 data; + int retval; + + analogix_dp_read_byte_from_dpcd(dp, DP_MAX_LANE_COUNT, &data); + retval = DPCD_ENHANCED_FRAME_CAP(data); + + return retval; +} + +static void analogix_dp_set_enhanced_mode(struct analogix_dp_device *dp) +{ + u8 data; + + data = analogix_dp_is_enhanced_mode_available(dp); + analogix_dp_enable_rx_to_enhanced_mode(dp, data); + analogix_dp_enable_enhanced_mode(dp, data); +} + +static void analogix_dp_training_pattern_dis(struct analogix_dp_device *dp) +{ + analogix_dp_set_training_pattern(dp, DP_NONE); + + analogix_dp_write_byte_to_dpcd(dp, DP_TRAINING_PATTERN_SET, + DP_TRAINING_PATTERN_DISABLE); +} + +static void +analogix_dp_set_lane_lane_pre_emphasis(struct analogix_dp_device *dp, + int pre_emphasis, int lane) +{ + switch (lane) { + case 0: + analogix_dp_set_lane0_pre_emphasis(dp, pre_emphasis); + break; + case 1: + analogix_dp_set_lane1_pre_emphasis(dp, pre_emphasis); + break; + + case 2: + analogix_dp_set_lane2_pre_emphasis(dp, pre_emphasis); + break; + + case 3: + analogix_dp_set_lane3_pre_emphasis(dp, pre_emphasis); + break; + } +} + +static int analogix_dp_link_start(struct analogix_dp_device *dp) +{ + u8 buf[4]; + int lane, lane_count, pll_tries, retval; + + lane_count = dp->link_train.lane_count; + + dp->link_train.lt_state = CLOCK_RECOVERY; + dp->link_train.eq_loop = 0; + + for (lane = 0; lane < lane_count; lane++) + dp->link_train.cr_loop[lane] = 0; + + /* Set link rate and count as you want to establish*/ + analogix_dp_set_link_bandwidth(dp, dp->link_train.link_rate); + analogix_dp_set_lane_count(dp, dp->link_train.lane_count); + + /* Setup RX configuration */ + buf[0] = dp->link_train.link_rate; + buf[1] = dp->link_train.lane_count; + retval = analogix_dp_write_bytes_to_dpcd(dp, DP_LINK_BW_SET, 2, buf); + if (retval) + return retval; + + /* Set TX pre-emphasis to minimum */ + for (lane = 0; lane < lane_count; lane++) + analogix_dp_set_lane_lane_pre_emphasis(dp, + PRE_EMPHASIS_LEVEL_0, lane); + + /* Wait for PLL lock */ + pll_tries = 0; + while (analogix_dp_get_pll_lock_status(dp) == PLL_UNLOCKED) { + if (pll_tries == DP_TIMEOUT_LOOP_COUNT) { + dev_err(dp->dev, "Wait for PLL lock timed out\n"); + return -ETIMEDOUT; + } + + pll_tries++; + usleep_range(90, 120); + } + + /* Set training pattern 1 */ + analogix_dp_set_training_pattern(dp, TRAINING_PTN1); + + /* Set RX training pattern */ + retval = analogix_dp_write_byte_to_dpcd(dp, + DP_TRAINING_PATTERN_SET, + DP_LINK_SCRAMBLING_DISABLE | DP_TRAINING_PATTERN_1); + if (retval) + return retval; + + for (lane = 0; lane < lane_count; lane++) + buf[lane] = DP_TRAIN_PRE_EMPH_LEVEL_0 | + DP_TRAIN_VOLTAGE_SWING_LEVEL_0; + + retval = analogix_dp_write_bytes_to_dpcd(dp, DP_TRAINING_LANE0_SET, + lane_count, buf); + + return retval; +} + +static unsigned char analogix_dp_get_lane_status(u8 link_status[2], int lane) +{ + int shift = (lane & 1) * 4; + u8 link_value = link_status[lane >> 1]; + + return (link_value >> shift) & 0xf; +} + +static int analogix_dp_clock_recovery_ok(u8 link_status[2], int lane_count) +{ + int lane; + u8 lane_status; + + for (lane = 0; lane < lane_count; lane++) { + lane_status = analogix_dp_get_lane_status(link_status, lane); + if ((lane_status & DP_LANE_CR_DONE) == 0) + return -EINVAL; + } + return 0; +} + +static int analogix_dp_channel_eq_ok(u8 link_status[2], u8 link_align, + int lane_count) +{ + int lane; + u8 lane_status; + + if ((link_align & DP_INTERLANE_ALIGN_DONE) == 0) + return -EINVAL; + + for (lane = 0; lane < lane_count; lane++) { + lane_status = analogix_dp_get_lane_status(link_status, lane); + lane_status &= DP_CHANNEL_EQ_BITS; + if (lane_status != DP_CHANNEL_EQ_BITS) + return -EINVAL; + } + + return 0; +} + +static unsigned char +analogix_dp_get_adjust_request_voltage(u8 adjust_request[2], int lane) +{ + int shift = (lane & 1) * 4; + u8 link_value = adjust_request[lane >> 1]; + + return (link_value >> shift) & 0x3; +} + +static unsigned char analogix_dp_get_adjust_request_pre_emphasis( + u8 adjust_request[2], + int lane) +{ + int shift = (lane & 1) * 4; + u8 link_value = adjust_request[lane >> 1]; + + return ((link_value >> shift) & 0xc) >> 2; +} + +static void analogix_dp_set_lane_link_training(struct analogix_dp_device *dp, + u8 training_lane_set, int lane) +{ + switch (lane) { + case 0: + analogix_dp_set_lane0_link_training(dp, training_lane_set); + break; + case 1: + analogix_dp_set_lane1_link_training(dp, training_lane_set); + break; + + case 2: + analogix_dp_set_lane2_link_training(dp, training_lane_set); + break; + + case 3: + analogix_dp_set_lane3_link_training(dp, training_lane_set); + break; + } +} + +static unsigned int +analogix_dp_get_lane_link_training(struct analogix_dp_device *dp, + int lane) +{ + u32 reg; + + switch (lane) { + case 0: + reg = analogix_dp_get_lane0_link_training(dp); + break; + case 1: + reg = analogix_dp_get_lane1_link_training(dp); + break; + case 2: + reg = analogix_dp_get_lane2_link_training(dp); + break; + case 3: + reg = analogix_dp_get_lane3_link_training(dp); + break; + default: + WARN_ON(1); + return 0; + } + + return reg; +} + +static void analogix_dp_reduce_link_rate(struct analogix_dp_device *dp) +{ + analogix_dp_training_pattern_dis(dp); + analogix_dp_set_enhanced_mode(dp); + + dp->link_train.lt_state = FAILED; +} + +static void analogix_dp_get_adjust_training_lane(struct analogix_dp_device *dp, + u8 adjust_request[2]) +{ + int lane, lane_count; + u8 voltage_swing, pre_emphasis, training_lane; + + lane_count = dp->link_train.lane_count; + for (lane = 0; lane < lane_count; lane++) { + voltage_swing = analogix_dp_get_adjust_request_voltage( + adjust_request, lane); + pre_emphasis = analogix_dp_get_adjust_request_pre_emphasis( + adjust_request, lane); + training_lane = DPCD_VOLTAGE_SWING_SET(voltage_swing) | + DPCD_PRE_EMPHASIS_SET(pre_emphasis); + + if (voltage_swing == VOLTAGE_LEVEL_3) + training_lane |= DP_TRAIN_MAX_SWING_REACHED; + if (pre_emphasis == PRE_EMPHASIS_LEVEL_3) + training_lane |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED; + + dp->link_train.training_lane[lane] = training_lane; + } +} + +static int analogix_dp_process_clock_recovery(struct analogix_dp_device *dp) +{ + int lane, lane_count, retval; + u8 voltage_swing, pre_emphasis, training_lane; + u8 link_status[2], adjust_request[2]; + + usleep_range(100, 101); + + lane_count = dp->link_train.lane_count; + + retval = analogix_dp_read_bytes_from_dpcd(dp, + DP_LANE0_1_STATUS, 2, link_status); + if (retval) + return retval; + + retval = analogix_dp_read_bytes_from_dpcd(dp, + DP_ADJUST_REQUEST_LANE0_1, 2, adjust_request); + if (retval) + return retval; + + if (analogix_dp_clock_recovery_ok(link_status, lane_count) == 0) { + /* set training pattern 2 for EQ */ + analogix_dp_set_training_pattern(dp, TRAINING_PTN2); + + retval = analogix_dp_write_byte_to_dpcd(dp, + DP_TRAINING_PATTERN_SET, + DP_LINK_SCRAMBLING_DISABLE | + DP_TRAINING_PATTERN_2); + if (retval) + return retval; + + dev_info(dp->dev, "Link Training Clock Recovery success\n"); + dp->link_train.lt_state = EQUALIZER_TRAINING; + } else { + for (lane = 0; lane < lane_count; lane++) { + training_lane = analogix_dp_get_lane_link_training( + dp, lane); + voltage_swing = analogix_dp_get_adjust_request_voltage( + adjust_request, lane); + pre_emphasis = analogix_dp_get_adjust_request_pre_emphasis( + adjust_request, lane); + + if (DPCD_VOLTAGE_SWING_GET(training_lane) == + voltage_swing && + DPCD_PRE_EMPHASIS_GET(training_lane) == + pre_emphasis) + dp->link_train.cr_loop[lane]++; + + if (dp->link_train.cr_loop[lane] == MAX_CR_LOOP || + voltage_swing == VOLTAGE_LEVEL_3 || + pre_emphasis == PRE_EMPHASIS_LEVEL_3) { + dev_err(dp->dev, "CR Max reached (%d,%d,%d)\n", + dp->link_train.cr_loop[lane], + voltage_swing, pre_emphasis); + analogix_dp_reduce_link_rate(dp); + return -EIO; + } + } + } + + analogix_dp_get_adjust_training_lane(dp, adjust_request); + + for (lane = 0; lane < lane_count; lane++) + analogix_dp_set_lane_link_training(dp, + dp->link_train.training_lane[lane], lane); + + retval = analogix_dp_write_bytes_to_dpcd(dp, + DP_TRAINING_LANE0_SET, lane_count, + dp->link_train.training_lane); + if (retval) + return retval; + + return retval; +} + +static int analogix_dp_process_equalizer_training(struct analogix_dp_device *dp) +{ + int lane, lane_count, retval; + u32 reg; + u8 link_align, link_status[2], adjust_request[2]; + + usleep_range(400, 401); + + lane_count = dp->link_train.lane_count; + + retval = analogix_dp_read_bytes_from_dpcd(dp, + DP_LANE0_1_STATUS, 2, link_status); + if (retval) + return retval; + + if (analogix_dp_clock_recovery_ok(link_status, lane_count)) { + analogix_dp_reduce_link_rate(dp); + return -EIO; + } + + retval = analogix_dp_read_bytes_from_dpcd(dp, + DP_ADJUST_REQUEST_LANE0_1, 2, adjust_request); + if (retval) + return retval; + + retval = analogix_dp_read_byte_from_dpcd(dp, + DP_LANE_ALIGN_STATUS_UPDATED, &link_align); + if (retval) + return retval; + + analogix_dp_get_adjust_training_lane(dp, adjust_request); + + if (!analogix_dp_channel_eq_ok(link_status, link_align, lane_count)) { + /* traing pattern Set to Normal */ + analogix_dp_training_pattern_dis(dp); + + dev_info(dp->dev, "Link Training success!\n"); + + analogix_dp_get_link_bandwidth(dp, ®); + dp->link_train.link_rate = reg; + dev_dbg(dp->dev, "final bandwidth = %.2x\n", + dp->link_train.link_rate); + + analogix_dp_get_lane_count(dp, ®); + dp->link_train.lane_count = reg; + dev_dbg(dp->dev, "final lane count = %.2x\n", + dp->link_train.lane_count); + + /* set enhanced mode if available */ + analogix_dp_set_enhanced_mode(dp); + dp->link_train.lt_state = FINISHED; + + return 0; + } + + /* not all locked */ + dp->link_train.eq_loop++; + + if (dp->link_train.eq_loop > MAX_EQ_LOOP) { + dev_err(dp->dev, "EQ Max loop\n"); + analogix_dp_reduce_link_rate(dp); + return -EIO; + } + + for (lane = 0; lane < lane_count; lane++) + analogix_dp_set_lane_link_training(dp, + dp->link_train.training_lane[lane], lane); + + retval = analogix_dp_write_bytes_to_dpcd(dp, DP_TRAINING_LANE0_SET, + lane_count, dp->link_train.training_lane); + + return retval; +} + +static void analogix_dp_get_max_rx_bandwidth(struct analogix_dp_device *dp, + u8 *bandwidth) +{ + u8 data; + + /* + * For DP rev.1.1, Maximum link rate of Main Link lanes + * 0x06 = 1.62 Gbps, 0x0a = 2.7 Gbps + * For DP rev.1.2, Maximum link rate of Main Link lanes + * 0x06 = 1.62 Gbps, 0x0a = 2.7 Gbps, 0x14 = 5.4Gbps + */ + analogix_dp_read_byte_from_dpcd(dp, DP_MAX_LINK_RATE, &data); + *bandwidth = data; +} + +static void analogix_dp_get_max_rx_lane_count(struct analogix_dp_device *dp, + u8 *lane_count) +{ + u8 data; + + /* + * For DP rev.1.1, Maximum number of Main Link lanes + * 0x01 = 1 lane, 0x02 = 2 lanes, 0x04 = 4 lanes + */ + analogix_dp_read_byte_from_dpcd(dp, DP_MAX_LANE_COUNT, &data); + *lane_count = DPCD_MAX_LANE_COUNT(data); +} + +static void analogix_dp_init_training(struct analogix_dp_device *dp, + enum link_lane_count_type max_lane, + int max_rate) +{ + /* + * MACRO_RST must be applied after the PLL_LOCK to avoid + * the DP inter pair skew issue for at least 10 us + */ + analogix_dp_reset_macro(dp); + + /* Initialize by reading RX's DPCD */ + analogix_dp_get_max_rx_bandwidth(dp, &dp->link_train.link_rate); + analogix_dp_get_max_rx_lane_count(dp, &dp->link_train.lane_count); + + if ((dp->link_train.link_rate != DP_LINK_BW_1_62) && + (dp->link_train.link_rate != DP_LINK_BW_2_7) && + (dp->link_train.link_rate != DP_LINK_BW_5_4)) { + dev_err(dp->dev, "Rx Max Link Rate is abnormal :%x !\n", + dp->link_train.link_rate); + dp->link_train.link_rate = DP_LINK_BW_1_62; + } + + if (dp->link_train.lane_count == 0) { + dev_err(dp->dev, "Rx Max Lane count is abnormal :%x !\n", + dp->link_train.lane_count); + dp->link_train.lane_count = (u8)LANE_COUNT1; + } + + /* Setup TX lane count & rate */ + if (dp->link_train.lane_count > max_lane) + dp->link_train.lane_count = max_lane; + if (dp->link_train.link_rate > max_rate) + dp->link_train.link_rate = max_rate; + + /* All DP analog module power up */ + analogix_dp_set_analog_power_down(dp, POWER_ALL, 0); +} + +static int analogix_dp_sw_link_training(struct analogix_dp_device *dp) +{ + int retval = 0, training_finished = 0; + + dp->link_train.lt_state = START; + + /* Process here */ + while (!retval && !training_finished) { + switch (dp->link_train.lt_state) { + case START: + retval = analogix_dp_link_start(dp); + if (retval) + dev_err(dp->dev, "LT link start failed!\n"); + break; + case CLOCK_RECOVERY: + retval = analogix_dp_process_clock_recovery(dp); + if (retval) + dev_err(dp->dev, "LT CR failed!\n"); + break; + case EQUALIZER_TRAINING: + retval = analogix_dp_process_equalizer_training(dp); + if (retval) + dev_err(dp->dev, "LT EQ failed!\n"); + break; + case FINISHED: + training_finished = 1; + break; + case FAILED: + return -EREMOTEIO; + } + } + if (retval) + dev_err(dp->dev, "eDP link training failed (%d)\n", retval); + + return retval; +} + +static int analogix_dp_set_link_train(struct analogix_dp_device *dp, + u32 count, u32 bwtype) +{ + int i; + int retval; + + for (i = 0; i < DP_TIMEOUT_LOOP_COUNT; i++) { + analogix_dp_init_training(dp, count, bwtype); + retval = analogix_dp_sw_link_training(dp); + if (retval == 0) + break; + + usleep_range(100, 110); + } + + return retval; +} + +static int analogix_dp_config_video(struct analogix_dp_device *dp) +{ + int retval = 0; + int timeout_loop = 0; + int done_count = 0; + + analogix_dp_config_video_slave_mode(dp); + + analogix_dp_set_video_color_format(dp); + + if (analogix_dp_get_pll_lock_status(dp) == PLL_UNLOCKED) { + dev_err(dp->dev, "PLL is not locked yet.\n"); + return -EINVAL; + } + + for (;;) { + timeout_loop++; + if (analogix_dp_is_slave_video_stream_clock_on(dp) == 0) + break; + if (timeout_loop > DP_TIMEOUT_LOOP_COUNT) { + dev_err(dp->dev, "Timeout of video streamclk ok\n"); + return -ETIMEDOUT; + } + + usleep_range(1, 2); + } + + /* Set to use the register calculated M/N video */ + analogix_dp_set_video_cr_mn(dp, CALCULATED_M, 0, 0); + + /* For video bist, Video timing must be generated by register */ + analogix_dp_set_video_timing_mode(dp, VIDEO_TIMING_FROM_CAPTURE); + + /* Disable video mute */ + analogix_dp_enable_video_mute(dp, 0); + + /* Configure video slave mode */ + analogix_dp_enable_video_master(dp, 0); + + timeout_loop = 0; + + for (;;) { + timeout_loop++; + if (analogix_dp_is_video_stream_on(dp) == 0) { + done_count++; + if (done_count > 10) + break; + } else if (done_count) { + done_count = 0; + } + if (timeout_loop > DP_TIMEOUT_LOOP_COUNT) { + dev_err(dp->dev, "Timeout of video streamclk ok\n"); + return -ETIMEDOUT; + } + + usleep_range(1000, 1001); + } + + if (retval != 0) + dev_err(dp->dev, "Video stream is not detected!\n"); + + return retval; +} + +static void analogix_dp_enable_scramble(struct analogix_dp_device *dp, + bool enable) +{ + u8 data; + + if (enable) { + analogix_dp_enable_scrambling(dp); + + analogix_dp_read_byte_from_dpcd(dp, DP_TRAINING_PATTERN_SET, + &data); + analogix_dp_write_byte_to_dpcd(dp, + DP_TRAINING_PATTERN_SET, + (u8)(data & ~DP_LINK_SCRAMBLING_DISABLE)); + } else { + analogix_dp_disable_scrambling(dp); + + analogix_dp_read_byte_from_dpcd(dp, DP_TRAINING_PATTERN_SET, + &data); + analogix_dp_write_byte_to_dpcd(dp, + DP_TRAINING_PATTERN_SET, + (u8)(data | DP_LINK_SCRAMBLING_DISABLE)); + } +} + +static irqreturn_t analogix_dp_hardirq(int irq, void *arg) +{ + struct analogix_dp_device *dp = arg; + irqreturn_t ret = IRQ_NONE; + enum dp_irq_type irq_type; + + irq_type = analogix_dp_get_irq_type(dp); + if (irq_type != DP_IRQ_TYPE_UNKNOWN) { + analogix_dp_mute_hpd_interrupt(dp); + ret = IRQ_WAKE_THREAD; + } + + return ret; +} + +static irqreturn_t analogix_dp_irq_thread(int irq, void *arg) +{ + struct analogix_dp_device *dp = arg; + enum dp_irq_type irq_type; + + irq_type = analogix_dp_get_irq_type(dp); + if (irq_type & DP_IRQ_TYPE_HP_CABLE_IN || + irq_type & DP_IRQ_TYPE_HP_CABLE_OUT) { + dev_dbg(dp->dev, "Detected cable status changed!\n"); + if (dp->drm_dev) + drm_helper_hpd_irq_event(dp->drm_dev); + } + + if (irq_type != DP_IRQ_TYPE_UNKNOWN) { + analogix_dp_clear_hotplug_interrupts(dp); + analogix_dp_unmute_hpd_interrupt(dp); + } + + return IRQ_HANDLED; +} + +static void analogix_dp_commit(struct analogix_dp_device *dp) +{ + int ret; + + /* Keep the panel disabled while we configure video */ + if (dp->plat_data->panel) { + if (drm_panel_disable(dp->plat_data->panel)) + DRM_ERROR("failed to disable the panel\n"); + } + + ret = analogix_dp_set_link_train(dp, dp->video_info.max_lane_count, + dp->video_info.max_link_rate); + if (ret) { + dev_err(dp->dev, "unable to do link train\n"); + return; + } + + analogix_dp_enable_scramble(dp, 1); + analogix_dp_enable_rx_to_enhanced_mode(dp, 1); + analogix_dp_enable_enhanced_mode(dp, 1); + + analogix_dp_init_video(dp); + ret = analogix_dp_config_video(dp); + if (ret) + dev_err(dp->dev, "unable to config video\n"); + + /* Safe to enable the panel now */ + if (dp->plat_data->panel) { + if (drm_panel_enable(dp->plat_data->panel)) + DRM_ERROR("failed to enable the panel\n"); + } + + /* Enable video */ + analogix_dp_start_video(dp); +} + +int analogix_dp_get_modes(struct drm_connector *connector) +{ + struct analogix_dp_device *dp = to_dp(connector); + struct edid *edid = (struct edid *)dp->edid; + int num_modes = 0; + + if (analogix_dp_handle_edid(dp) == 0) { + drm_mode_connector_update_edid_property(&dp->connector, edid); + num_modes += drm_add_edid_modes(&dp->connector, edid); + } + + if (dp->plat_data->panel) + num_modes += drm_panel_get_modes(dp->plat_data->panel); + + if (dp->plat_data->get_modes) + num_modes += dp->plat_data->get_modes(dp->plat_data); + + return num_modes; +} + +static struct drm_encoder * +analogix_dp_best_encoder(struct drm_connector *connector) +{ + struct analogix_dp_device *dp = to_dp(connector); + + return dp->encoder; +} + +static const struct drm_connector_helper_funcs analogix_dp_connector_helper_funcs = { + .get_modes = analogix_dp_get_modes, + .best_encoder = analogix_dp_best_encoder, +}; + +enum drm_connector_status +analogix_dp_detect(struct drm_connector *connector, bool force) +{ + struct analogix_dp_device *dp = to_dp(connector); + + if (analogix_dp_detect_hpd(dp)) + return connector_status_disconnected; + + return connector_status_connected; +} + +static void analogix_dp_connector_destroy(struct drm_connector *connector) +{ + drm_connector_unregister(connector); + drm_connector_cleanup(connector); + +} + +static const struct drm_connector_funcs analogix_dp_connector_funcs = { + .dpms = drm_atomic_helper_connector_dpms, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = analogix_dp_detect, + .destroy = analogix_dp_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 int analogix_dp_bridge_attach(struct drm_bridge *bridge) +{ + struct analogix_dp_device *dp = bridge->driver_private; + struct drm_encoder *encoder = dp->encoder; + struct drm_connector *connector = &dp->connector; + int ret; + + if (!bridge->encoder) { + DRM_ERROR("Parent encoder object not found"); + return -ENODEV; + } + + connector->polled = DRM_CONNECTOR_POLL_HPD; + + ret = drm_connector_init(dp->drm_dev, connector, + &analogix_dp_connector_funcs, + DRM_MODE_CONNECTOR_eDP); + if (ret) { + DRM_ERROR("Failed to initialize connector with drm\n"); + return ret; + } + + drm_connector_helper_add(connector, + &analogix_dp_connector_helper_funcs); + drm_mode_connector_attach_encoder(connector, encoder); + + /* + * NOTE: the connector registration is implemented in analogix + * platform driver, that to say connector would be exist after + * plat_data->attch return, that's why we record the connector + * point after plat attached. + */ + if (dp->plat_data->attach) { + ret = dp->plat_data->attach(dp->plat_data, bridge, connector); + if (ret) { + DRM_ERROR("Failed at platform attch func\n"); + return ret; + } + } + + if (dp->plat_data->panel) { + ret = drm_panel_attach(dp->plat_data->panel, &dp->connector); + if (ret) { + DRM_ERROR("Failed to attach panel\n"); + return ret; + } + } + + return 0; +} + +static void analogix_dp_bridge_enable(struct drm_bridge *bridge) +{ + struct analogix_dp_device *dp = bridge->driver_private; + + if (dp->dpms_mode == DRM_MODE_DPMS_ON) + return; + + pm_runtime_get_sync(dp->dev); + + if (dp->plat_data->power_on) + dp->plat_data->power_on(dp->plat_data); + + phy_power_on(dp->phy); + analogix_dp_init_dp(dp); + enable_irq(dp->irq); + analogix_dp_commit(dp); + + dp->dpms_mode = DRM_MODE_DPMS_ON; +} + +static void analogix_dp_bridge_disable(struct drm_bridge *bridge) +{ + struct analogix_dp_device *dp = bridge->driver_private; + + if (dp->dpms_mode != DRM_MODE_DPMS_ON) + return; + + if (dp->plat_data->panel) { + if (drm_panel_disable(dp->plat_data->panel)) { + DRM_ERROR("failed to disable the panel\n"); + return; + } + } + + disable_irq(dp->irq); + phy_power_off(dp->phy); + + if (dp->plat_data->power_off) + dp->plat_data->power_off(dp->plat_data); + + pm_runtime_put_sync(dp->dev); + + dp->dpms_mode = DRM_MODE_DPMS_OFF; +} + +static void analogix_dp_bridge_mode_set(struct drm_bridge *bridge, + struct drm_display_mode *orig_mode, + struct drm_display_mode *mode) +{ + struct analogix_dp_device *dp = bridge->driver_private; + struct drm_display_info *display_info = &dp->connector.display_info; + struct video_info *video = &dp->video_info; + struct device_node *dp_node = dp->dev->of_node; + int vic; + + /* Input video interlaces & hsync pol & vsync pol */ + video->interlaced = !!(mode->flags & DRM_MODE_FLAG_INTERLACE); + video->v_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NVSYNC); + video->h_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NHSYNC); + + /* Input video dynamic_range & colorimetry */ + vic = drm_match_cea_mode(mode); + if ((vic == 6) || (vic == 7) || (vic == 21) || (vic == 22) || + (vic == 2) || (vic == 3) || (vic == 17) || (vic == 18)) { + video->dynamic_range = CEA; + video->ycbcr_coeff = COLOR_YCBCR601; + } else if (vic) { + video->dynamic_range = CEA; + video->ycbcr_coeff = COLOR_YCBCR709; + } else { + video->dynamic_range = VESA; + video->ycbcr_coeff = COLOR_YCBCR709; + } + + /* Input vide bpc and color_formats */ + switch (display_info->bpc) { + case 12: + video->color_depth = COLOR_12; + break; + case 10: + video->color_depth = COLOR_10; + break; + case 8: + video->color_depth = COLOR_8; + break; + case 6: + video->color_depth = COLOR_6; + break; + default: + video->color_depth = COLOR_8; + break; + } + if (display_info->color_formats & DRM_COLOR_FORMAT_YCRCB444) + video->color_space = COLOR_YCBCR444; + else if (display_info->color_formats & DRM_COLOR_FORMAT_YCRCB422) + video->color_space = COLOR_YCBCR422; + else if (display_info->color_formats & DRM_COLOR_FORMAT_RGB444) + video->color_space = COLOR_RGB; + else + video->color_space = COLOR_RGB; + + /* + * NOTE: those property parsing code is used for providing backward + * compatibility for samsung platform. + * Due to we used the "of_property_read_u32" interfaces, when this + * property isn't present, the "video_info" can keep the original + * values and wouldn't be modified. + */ + of_property_read_u32(dp_node, "samsung,color-space", + &video->color_space); + of_property_read_u32(dp_node, "samsung,dynamic-range", + &video->dynamic_range); + of_property_read_u32(dp_node, "samsung,ycbcr-coeff", + &video->ycbcr_coeff); + of_property_read_u32(dp_node, "samsung,color-depth", + &video->color_depth); + if (of_property_read_bool(dp_node, "hsync-active-high")) + video->h_sync_polarity = true; + if (of_property_read_bool(dp_node, "vsync-active-high")) + video->v_sync_polarity = true; + if (of_property_read_bool(dp_node, "interlaced")) + video->interlaced = true; +} + +static void analogix_dp_bridge_nop(struct drm_bridge *bridge) +{ + /* do nothing */ +} + +static const struct drm_bridge_funcs analogix_dp_bridge_funcs = { + .enable = analogix_dp_bridge_enable, + .disable = analogix_dp_bridge_disable, + .pre_enable = analogix_dp_bridge_nop, + .post_disable = analogix_dp_bridge_nop, + .mode_set = analogix_dp_bridge_mode_set, + .attach = analogix_dp_bridge_attach, +}; + +static int analogix_dp_create_bridge(struct drm_device *drm_dev, + struct analogix_dp_device *dp) +{ + struct drm_bridge *bridge; + int ret; + + bridge = devm_kzalloc(drm_dev->dev, sizeof(*bridge), GFP_KERNEL); + if (!bridge) { + DRM_ERROR("failed to allocate for drm bridge\n"); + return -ENOMEM; + } + + dp->bridge = bridge; + + dp->encoder->bridge = bridge; + bridge->driver_private = dp; + bridge->encoder = dp->encoder; + bridge->funcs = &analogix_dp_bridge_funcs; + + ret = drm_bridge_attach(drm_dev, bridge); + if (ret) { + DRM_ERROR("failed to attach drm bridge\n"); + return -EINVAL; + } + + return 0; +} + +static int analogix_dp_dt_parse_pdata(struct analogix_dp_device *dp) +{ + struct device_node *dp_node = dp->dev->of_node; + struct video_info *video_info = &dp->video_info; + + switch (dp->plat_data->dev_type) { + case RK3288_DP: + /* + * Like Rk3288 DisplayPort TRM indicate that "Main link + * containing 4 physical lanes of 2.7/1.62 Gbps/lane". + */ + video_info->max_link_rate = 0x0A; + video_info->max_lane_count = 0x04; + break; + case EXYNOS_DP: + /* + * NOTE: those property parseing code is used for + * providing backward compatibility for samsung platform. + */ + of_property_read_u32(dp_node, "samsung,link-rate", + &video_info->max_link_rate); + of_property_read_u32(dp_node, "samsung,lane-count", + &video_info->max_lane_count); + break; + } + + return 0; +} + +int analogix_dp_bind(struct device *dev, struct drm_device *drm_dev, + struct analogix_dp_plat_data *plat_data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct analogix_dp_device *dp; + struct resource *res; + unsigned int irq_flags; + int ret; + + if (!plat_data) { + dev_err(dev, "Invalided input plat_data\n"); + return -EINVAL; + } + + dp = devm_kzalloc(dev, sizeof(struct analogix_dp_device), GFP_KERNEL); + if (!dp) + return -ENOMEM; + + dev_set_drvdata(dev, dp); + + dp->dev = &pdev->dev; + dp->dpms_mode = DRM_MODE_DPMS_OFF; + + /* + * platform dp driver need containor_of the plat_data to get + * the driver private data, so we need to store the point of + * plat_data, not the context of plat_data. + */ + dp->plat_data = plat_data; + + ret = analogix_dp_dt_parse_pdata(dp); + if (ret) + return ret; + + dp->phy = devm_phy_get(dp->dev, "dp"); + if (IS_ERR(dp->phy)) { + dev_err(dp->dev, "no DP phy configured\n"); + ret = PTR_ERR(dp->phy); + if (ret) { + /* + * phy itself is not enabled, so we can move forward + * assigning NULL to phy pointer. + */ + if (ret == -ENOSYS || ret == -ENODEV) + dp->phy = NULL; + else + return ret; + } + } + + dp->clock = devm_clk_get(&pdev->dev, "dp"); + if (IS_ERR(dp->clock)) { + dev_err(&pdev->dev, "failed to get clock\n"); + return PTR_ERR(dp->clock); + } + + clk_prepare_enable(dp->clock); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + dp->reg_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(dp->reg_base)) + return PTR_ERR(dp->reg_base); + + dp->force_hpd = of_property_read_bool(dev->of_node, "force-hpd"); + + dp->hpd_gpio = of_get_named_gpio(dev->of_node, "hpd-gpios", 0); + if (!gpio_is_valid(dp->hpd_gpio)) + dp->hpd_gpio = of_get_named_gpio(dev->of_node, + "samsung,hpd-gpio", 0); + + if (gpio_is_valid(dp->hpd_gpio)) { + /* + * Set up the hotplug GPIO from the device tree as an interrupt. + * Simply specifying a different interrupt in the device tree + * doesn't work since we handle hotplug rather differently when + * using a GPIO. We also need the actual GPIO specifier so + * that we can get the current state of the GPIO. + */ + ret = devm_gpio_request_one(&pdev->dev, dp->hpd_gpio, GPIOF_IN, + "hpd_gpio"); + if (ret) { + dev_err(&pdev->dev, "failed to get hpd gpio\n"); + return ret; + } + dp->irq = gpio_to_irq(dp->hpd_gpio); + irq_flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING; + } else { + dp->hpd_gpio = -ENODEV; + dp->irq = platform_get_irq(pdev, 0); + irq_flags = 0; + } + + if (dp->irq == -ENXIO) { + dev_err(&pdev->dev, "failed to get irq\n"); + return -ENODEV; + } + + pm_runtime_enable(dev); + + phy_power_on(dp->phy); + + if (dp->plat_data->panel) { + if (drm_panel_prepare(dp->plat_data->panel)) { + DRM_ERROR("failed to setup the panel\n"); + return -EBUSY; + } + } + + analogix_dp_init_dp(dp); + + ret = devm_request_threaded_irq(&pdev->dev, dp->irq, + analogix_dp_hardirq, + analogix_dp_irq_thread, + irq_flags, "analogix-dp", dp); + if (ret) { + dev_err(&pdev->dev, "failed to request irq\n"); + goto err_disable_pm_runtime; + } + disable_irq(dp->irq); + + dp->drm_dev = drm_dev; + dp->encoder = dp->plat_data->encoder; + + ret = analogix_dp_create_bridge(drm_dev, dp); + if (ret) { + DRM_ERROR("failed to create bridge (%d)\n", ret); + drm_encoder_cleanup(dp->encoder); + goto err_disable_pm_runtime; + } + + return 0; + +err_disable_pm_runtime: + pm_runtime_disable(dev); + + return ret; +} +EXPORT_SYMBOL_GPL(analogix_dp_bind); + +void analogix_dp_unbind(struct device *dev, struct device *master, + void *data) +{ + struct analogix_dp_device *dp = dev_get_drvdata(dev); + + analogix_dp_bridge_disable(dp->bridge); + + if (dp->plat_data->panel) { + if (drm_panel_unprepare(dp->plat_data->panel)) + DRM_ERROR("failed to turnoff the panel\n"); + } + + pm_runtime_disable(dev); +} +EXPORT_SYMBOL_GPL(analogix_dp_unbind); + +#ifdef CONFIG_PM +int analogix_dp_suspend(struct device *dev) +{ + struct analogix_dp_device *dp = dev_get_drvdata(dev); + + clk_disable_unprepare(dp->clock); + + if (dp->plat_data->panel) { + if (drm_panel_unprepare(dp->plat_data->panel)) + DRM_ERROR("failed to turnoff the panel\n"); + } + + return 0; +} +EXPORT_SYMBOL_GPL(analogix_dp_suspend); + +int analogix_dp_resume(struct device *dev) +{ + struct analogix_dp_device *dp = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(dp->clock); + if (ret < 0) { + DRM_ERROR("Failed to prepare_enable the clock clk [%d]\n", ret); + return ret; + } + + if (dp->plat_data->panel) { + if (drm_panel_prepare(dp->plat_data->panel)) { + DRM_ERROR("failed to setup the panel\n"); + return -EBUSY; + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(analogix_dp_resume); +#endif + +MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>"); +MODULE_DESCRIPTION("Analogix DP Core Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h new file mode 100644 index 000000000000..f09275d40f70 --- /dev/null +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_core.h @@ -0,0 +1,281 @@ +/* + * Header file for Analogix DP (Display Port) core interface driver. + * + * Copyright (C) 2012 Samsung Electronics Co., Ltd. + * Author: Jingoo Han <jg1.han@samsung.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef _ANALOGIX_DP_CORE_H +#define _ANALOGIX_DP_CORE_H + +#include <drm/drm_crtc.h> +#include <drm/drm_dp_helper.h> + +#define DP_TIMEOUT_LOOP_COUNT 100 +#define MAX_CR_LOOP 5 +#define MAX_EQ_LOOP 5 + +/* I2C EDID Chip ID, Slave Address */ +#define I2C_EDID_DEVICE_ADDR 0x50 +#define I2C_E_EDID_DEVICE_ADDR 0x30 + +#define EDID_BLOCK_LENGTH 0x80 +#define EDID_HEADER_PATTERN 0x00 +#define EDID_EXTENSION_FLAG 0x7e +#define EDID_CHECKSUM 0x7f + +/* DP_MAX_LANE_COUNT */ +#define DPCD_ENHANCED_FRAME_CAP(x) (((x) >> 7) & 0x1) +#define DPCD_MAX_LANE_COUNT(x) ((x) & 0x1f) + +/* DP_LANE_COUNT_SET */ +#define DPCD_LANE_COUNT_SET(x) ((x) & 0x1f) + +/* DP_TRAINING_LANE0_SET */ +#define DPCD_PRE_EMPHASIS_SET(x) (((x) & 0x3) << 3) +#define DPCD_PRE_EMPHASIS_GET(x) (((x) >> 3) & 0x3) +#define DPCD_VOLTAGE_SWING_SET(x) (((x) & 0x3) << 0) +#define DPCD_VOLTAGE_SWING_GET(x) (((x) >> 0) & 0x3) + +enum link_lane_count_type { + LANE_COUNT1 = 1, + LANE_COUNT2 = 2, + LANE_COUNT4 = 4 +}; + +enum link_training_state { + START, + CLOCK_RECOVERY, + EQUALIZER_TRAINING, + FINISHED, + FAILED +}; + +enum voltage_swing_level { + VOLTAGE_LEVEL_0, + VOLTAGE_LEVEL_1, + VOLTAGE_LEVEL_2, + VOLTAGE_LEVEL_3, +}; + +enum pre_emphasis_level { + PRE_EMPHASIS_LEVEL_0, + PRE_EMPHASIS_LEVEL_1, + PRE_EMPHASIS_LEVEL_2, + PRE_EMPHASIS_LEVEL_3, +}; + +enum pattern_set { + PRBS7, + D10_2, + TRAINING_PTN1, + TRAINING_PTN2, + DP_NONE +}; + +enum color_space { + COLOR_RGB, + COLOR_YCBCR422, + COLOR_YCBCR444 +}; + +enum color_depth { + COLOR_6, + COLOR_8, + COLOR_10, + COLOR_12 +}; + +enum color_coefficient { + COLOR_YCBCR601, + COLOR_YCBCR709 +}; + +enum dynamic_range { + VESA, + CEA +}; + +enum pll_status { + PLL_UNLOCKED, + PLL_LOCKED +}; + +enum clock_recovery_m_value_type { + CALCULATED_M, + REGISTER_M +}; + +enum video_timing_recognition_type { + VIDEO_TIMING_FROM_CAPTURE, + VIDEO_TIMING_FROM_REGISTER +}; + +enum analog_power_block { + AUX_BLOCK, + CH0_BLOCK, + CH1_BLOCK, + CH2_BLOCK, + CH3_BLOCK, + ANALOG_TOTAL, + POWER_ALL +}; + +enum dp_irq_type { + DP_IRQ_TYPE_HP_CABLE_IN, + DP_IRQ_TYPE_HP_CABLE_OUT, + DP_IRQ_TYPE_HP_CHANGE, + DP_IRQ_TYPE_UNKNOWN, +}; + +struct video_info { + char *name; + + bool h_sync_polarity; + bool v_sync_polarity; + bool interlaced; + + enum color_space color_space; + enum dynamic_range dynamic_range; + enum color_coefficient ycbcr_coeff; + enum color_depth color_depth; + + int max_link_rate; + enum link_lane_count_type max_lane_count; +}; + +struct link_train { + int eq_loop; + int cr_loop[4]; + + u8 link_rate; + u8 lane_count; + u8 training_lane[4]; + + enum link_training_state lt_state; +}; + +struct analogix_dp_device { + struct drm_encoder *encoder; + struct device *dev; + struct drm_device *drm_dev; + struct drm_connector connector; + struct drm_bridge *bridge; + struct clk *clock; + unsigned int irq; + void __iomem *reg_base; + + struct video_info video_info; + struct link_train link_train; + struct phy *phy; + int dpms_mode; + int hpd_gpio; + bool force_hpd; + unsigned char edid[EDID_BLOCK_LENGTH * 2]; + + struct analogix_dp_plat_data *plat_data; +}; + +/* analogix_dp_reg.c */ +void analogix_dp_enable_video_mute(struct analogix_dp_device *dp, bool enable); +void analogix_dp_stop_video(struct analogix_dp_device *dp); +void analogix_dp_lane_swap(struct analogix_dp_device *dp, bool enable); +void analogix_dp_init_analog_param(struct analogix_dp_device *dp); +void analogix_dp_init_interrupt(struct analogix_dp_device *dp); +void analogix_dp_reset(struct analogix_dp_device *dp); +void analogix_dp_swreset(struct analogix_dp_device *dp); +void analogix_dp_config_interrupt(struct analogix_dp_device *dp); +void analogix_dp_mute_hpd_interrupt(struct analogix_dp_device *dp); +void analogix_dp_unmute_hpd_interrupt(struct analogix_dp_device *dp); +enum pll_status analogix_dp_get_pll_lock_status(struct analogix_dp_device *dp); +void analogix_dp_set_pll_power_down(struct analogix_dp_device *dp, bool enable); +void analogix_dp_set_analog_power_down(struct analogix_dp_device *dp, + enum analog_power_block block, + bool enable); +void analogix_dp_init_analog_func(struct analogix_dp_device *dp); +void analogix_dp_init_hpd(struct analogix_dp_device *dp); +void analogix_dp_force_hpd(struct analogix_dp_device *dp); +enum dp_irq_type analogix_dp_get_irq_type(struct analogix_dp_device *dp); +void analogix_dp_clear_hotplug_interrupts(struct analogix_dp_device *dp); +void analogix_dp_reset_aux(struct analogix_dp_device *dp); +void analogix_dp_init_aux(struct analogix_dp_device *dp); +int analogix_dp_get_plug_in_status(struct analogix_dp_device *dp); +void analogix_dp_enable_sw_function(struct analogix_dp_device *dp); +int analogix_dp_start_aux_transaction(struct analogix_dp_device *dp); +int analogix_dp_write_byte_to_dpcd(struct analogix_dp_device *dp, + unsigned int reg_addr, + unsigned char data); +int analogix_dp_read_byte_from_dpcd(struct analogix_dp_device *dp, + unsigned int reg_addr, + unsigned char *data); +int analogix_dp_write_bytes_to_dpcd(struct analogix_dp_device *dp, + unsigned int reg_addr, + unsigned int count, + unsigned char data[]); +int analogix_dp_read_bytes_from_dpcd(struct analogix_dp_device *dp, + unsigned int reg_addr, + unsigned int count, + unsigned char data[]); +int analogix_dp_select_i2c_device(struct analogix_dp_device *dp, + unsigned int device_addr, + unsigned int reg_addr); +int analogix_dp_read_byte_from_i2c(struct analogix_dp_device *dp, + unsigned int device_addr, + unsigned int reg_addr, + unsigned int *data); +int analogix_dp_read_bytes_from_i2c(struct analogix_dp_device *dp, + unsigned int device_addr, + unsigned int reg_addr, + unsigned int count, + unsigned char edid[]); +void analogix_dp_set_link_bandwidth(struct analogix_dp_device *dp, u32 bwtype); +void analogix_dp_get_link_bandwidth(struct analogix_dp_device *dp, u32 *bwtype); +void analogix_dp_set_lane_count(struct analogix_dp_device *dp, u32 count); +void analogix_dp_get_lane_count(struct analogix_dp_device *dp, u32 *count); +void analogix_dp_enable_enhanced_mode(struct analogix_dp_device *dp, + bool enable); +void analogix_dp_set_training_pattern(struct analogix_dp_device *dp, + enum pattern_set pattern); +void analogix_dp_set_lane0_pre_emphasis(struct analogix_dp_device *dp, + u32 level); +void analogix_dp_set_lane1_pre_emphasis(struct analogix_dp_device *dp, + u32 level); +void analogix_dp_set_lane2_pre_emphasis(struct analogix_dp_device *dp, + u32 level); +void analogix_dp_set_lane3_pre_emphasis(struct analogix_dp_device *dp, + u32 level); +void analogix_dp_set_lane0_link_training(struct analogix_dp_device *dp, + u32 training_lane); +void analogix_dp_set_lane1_link_training(struct analogix_dp_device *dp, + u32 training_lane); +void analogix_dp_set_lane2_link_training(struct analogix_dp_device *dp, + u32 training_lane); +void analogix_dp_set_lane3_link_training(struct analogix_dp_device *dp, + u32 training_lane); +u32 analogix_dp_get_lane0_link_training(struct analogix_dp_device *dp); +u32 analogix_dp_get_lane1_link_training(struct analogix_dp_device *dp); +u32 analogix_dp_get_lane2_link_training(struct analogix_dp_device *dp); +u32 analogix_dp_get_lane3_link_training(struct analogix_dp_device *dp); +void analogix_dp_reset_macro(struct analogix_dp_device *dp); +void analogix_dp_init_video(struct analogix_dp_device *dp); + +void analogix_dp_set_video_color_format(struct analogix_dp_device *dp); +int analogix_dp_is_slave_video_stream_clock_on(struct analogix_dp_device *dp); +void analogix_dp_set_video_cr_mn(struct analogix_dp_device *dp, + enum clock_recovery_m_value_type type, + u32 m_value, + u32 n_value); +void analogix_dp_set_video_timing_mode(struct analogix_dp_device *dp, u32 type); +void analogix_dp_enable_video_master(struct analogix_dp_device *dp, + bool enable); +void analogix_dp_start_video(struct analogix_dp_device *dp); +int analogix_dp_is_video_stream_on(struct analogix_dp_device *dp); +void analogix_dp_config_video_slave_mode(struct analogix_dp_device *dp); +void analogix_dp_enable_scrambling(struct analogix_dp_device *dp); +void analogix_dp_disable_scrambling(struct analogix_dp_device *dp); +#endif /* _ANALOGIX_DP_CORE_H */ diff --git a/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c new file mode 100644 index 000000000000..49205ef02be3 --- /dev/null +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.c @@ -0,0 +1,1320 @@ +/* + * Analogix DP (Display port) core register interface driver. + * + * Copyright (C) 2012 Samsung Electronics Co., Ltd. + * Author: Jingoo Han <jg1.han@samsung.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/device.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/gpio.h> + +#include <drm/bridge/analogix_dp.h> + +#include "analogix_dp_core.h" +#include "analogix_dp_reg.h" + +#define COMMON_INT_MASK_1 0 +#define COMMON_INT_MASK_2 0 +#define COMMON_INT_MASK_3 0 +#define COMMON_INT_MASK_4 (HOTPLUG_CHG | HPD_LOST | PLUG) +#define INT_STA_MASK INT_HPD + +void analogix_dp_enable_video_mute(struct analogix_dp_device *dp, bool enable) +{ + u32 reg; + + if (enable) { + reg = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_1); + reg |= HDCP_VIDEO_MUTE; + writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_1); + } else { + reg = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_1); + reg &= ~HDCP_VIDEO_MUTE; + writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_1); + } +} + +void analogix_dp_stop_video(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_1); + reg &= ~VIDEO_EN; + writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_1); +} + +void analogix_dp_lane_swap(struct analogix_dp_device *dp, bool enable) +{ + u32 reg; + + if (enable) + reg = LANE3_MAP_LOGIC_LANE_0 | LANE2_MAP_LOGIC_LANE_1 | + LANE1_MAP_LOGIC_LANE_2 | LANE0_MAP_LOGIC_LANE_3; + else + reg = LANE3_MAP_LOGIC_LANE_3 | LANE2_MAP_LOGIC_LANE_2 | + LANE1_MAP_LOGIC_LANE_1 | LANE0_MAP_LOGIC_LANE_0; + + writel(reg, dp->reg_base + ANALOGIX_DP_LANE_MAP); +} + +void analogix_dp_init_analog_param(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = TX_TERMINAL_CTRL_50_OHM; + writel(reg, dp->reg_base + ANALOGIX_DP_ANALOG_CTL_1); + + reg = SEL_24M | TX_DVDD_BIT_1_0625V; + writel(reg, dp->reg_base + ANALOGIX_DP_ANALOG_CTL_2); + + if (dp->plat_data && (dp->plat_data->dev_type == RK3288_DP)) { + writel(REF_CLK_24M, dp->reg_base + ANALOGIX_DP_PLL_REG_1); + writel(0x95, dp->reg_base + ANALOGIX_DP_PLL_REG_2); + writel(0x40, dp->reg_base + ANALOGIX_DP_PLL_REG_3); + writel(0x58, dp->reg_base + ANALOGIX_DP_PLL_REG_4); + writel(0x22, dp->reg_base + ANALOGIX_DP_PLL_REG_5); + } + + reg = DRIVE_DVDD_BIT_1_0625V | VCO_BIT_600_MICRO; + writel(reg, dp->reg_base + ANALOGIX_DP_ANALOG_CTL_3); + + reg = PD_RING_OSC | AUX_TERMINAL_CTRL_50_OHM | + TX_CUR1_2X | TX_CUR_16_MA; + writel(reg, dp->reg_base + ANALOGIX_DP_PLL_FILTER_CTL_1); + + reg = CH3_AMP_400_MV | CH2_AMP_400_MV | + CH1_AMP_400_MV | CH0_AMP_400_MV; + writel(reg, dp->reg_base + ANALOGIX_DP_TX_AMP_TUNING_CTL); +} + +void analogix_dp_init_interrupt(struct analogix_dp_device *dp) +{ + /* Set interrupt pin assertion polarity as high */ + writel(INT_POL1 | INT_POL0, dp->reg_base + ANALOGIX_DP_INT_CTL); + + /* Clear pending regisers */ + writel(0xff, dp->reg_base + ANALOGIX_DP_COMMON_INT_STA_1); + writel(0x4f, dp->reg_base + ANALOGIX_DP_COMMON_INT_STA_2); + writel(0xe0, dp->reg_base + ANALOGIX_DP_COMMON_INT_STA_3); + writel(0xe7, dp->reg_base + ANALOGIX_DP_COMMON_INT_STA_4); + writel(0x63, dp->reg_base + ANALOGIX_DP_INT_STA); + + /* 0:mask,1: unmask */ + writel(0x00, dp->reg_base + ANALOGIX_DP_COMMON_INT_MASK_1); + writel(0x00, dp->reg_base + ANALOGIX_DP_COMMON_INT_MASK_2); + writel(0x00, dp->reg_base + ANALOGIX_DP_COMMON_INT_MASK_3); + writel(0x00, dp->reg_base + ANALOGIX_DP_COMMON_INT_MASK_4); + writel(0x00, dp->reg_base + ANALOGIX_DP_INT_STA_MASK); +} + +void analogix_dp_reset(struct analogix_dp_device *dp) +{ + u32 reg; + + analogix_dp_stop_video(dp); + analogix_dp_enable_video_mute(dp, 0); + + reg = MASTER_VID_FUNC_EN_N | SLAVE_VID_FUNC_EN_N | + AUD_FIFO_FUNC_EN_N | AUD_FUNC_EN_N | + HDCP_FUNC_EN_N | SW_FUNC_EN_N; + writel(reg, dp->reg_base + ANALOGIX_DP_FUNC_EN_1); + + reg = SSC_FUNC_EN_N | AUX_FUNC_EN_N | + SERDES_FIFO_FUNC_EN_N | + LS_CLK_DOMAIN_FUNC_EN_N; + writel(reg, dp->reg_base + ANALOGIX_DP_FUNC_EN_2); + + usleep_range(20, 30); + + analogix_dp_lane_swap(dp, 0); + + writel(0x0, dp->reg_base + ANALOGIX_DP_SYS_CTL_1); + writel(0x40, dp->reg_base + ANALOGIX_DP_SYS_CTL_2); + writel(0x0, dp->reg_base + ANALOGIX_DP_SYS_CTL_3); + writel(0x0, dp->reg_base + ANALOGIX_DP_SYS_CTL_4); + + writel(0x0, dp->reg_base + ANALOGIX_DP_PKT_SEND_CTL); + writel(0x0, dp->reg_base + ANALOGIX_DP_HDCP_CTL); + + writel(0x5e, dp->reg_base + ANALOGIX_DP_HPD_DEGLITCH_L); + writel(0x1a, dp->reg_base + ANALOGIX_DP_HPD_DEGLITCH_H); + + writel(0x10, dp->reg_base + ANALOGIX_DP_LINK_DEBUG_CTL); + + writel(0x0, dp->reg_base + ANALOGIX_DP_PHY_TEST); + + writel(0x0, dp->reg_base + ANALOGIX_DP_VIDEO_FIFO_THRD); + writel(0x20, dp->reg_base + ANALOGIX_DP_AUDIO_MARGIN); + + writel(0x4, dp->reg_base + ANALOGIX_DP_M_VID_GEN_FILTER_TH); + writel(0x2, dp->reg_base + ANALOGIX_DP_M_AUD_GEN_FILTER_TH); + + writel(0x00000101, dp->reg_base + ANALOGIX_DP_SOC_GENERAL_CTL); +} + +void analogix_dp_swreset(struct analogix_dp_device *dp) +{ + writel(RESET_DP_TX, dp->reg_base + ANALOGIX_DP_TX_SW_RESET); +} + +void analogix_dp_config_interrupt(struct analogix_dp_device *dp) +{ + u32 reg; + + /* 0: mask, 1: unmask */ + reg = COMMON_INT_MASK_1; + writel(reg, dp->reg_base + ANALOGIX_DP_COMMON_INT_MASK_1); + + reg = COMMON_INT_MASK_2; + writel(reg, dp->reg_base + ANALOGIX_DP_COMMON_INT_MASK_2); + + reg = COMMON_INT_MASK_3; + writel(reg, dp->reg_base + ANALOGIX_DP_COMMON_INT_MASK_3); + + reg = COMMON_INT_MASK_4; + writel(reg, dp->reg_base + ANALOGIX_DP_COMMON_INT_MASK_4); + + reg = INT_STA_MASK; + writel(reg, dp->reg_base + ANALOGIX_DP_INT_STA_MASK); +} + +void analogix_dp_mute_hpd_interrupt(struct analogix_dp_device *dp) +{ + u32 reg; + + /* 0: mask, 1: unmask */ + reg = readl(dp->reg_base + ANALOGIX_DP_COMMON_INT_MASK_4); + reg &= ~COMMON_INT_MASK_4; + writel(reg, dp->reg_base + ANALOGIX_DP_COMMON_INT_MASK_4); + + reg = readl(dp->reg_base + ANALOGIX_DP_INT_STA_MASK); + reg &= ~INT_STA_MASK; + writel(reg, dp->reg_base + ANALOGIX_DP_INT_STA_MASK); +} + +void analogix_dp_unmute_hpd_interrupt(struct analogix_dp_device *dp) +{ + u32 reg; + + /* 0: mask, 1: unmask */ + reg = COMMON_INT_MASK_4; + writel(reg, dp->reg_base + ANALOGIX_DP_COMMON_INT_MASK_4); + + reg = INT_STA_MASK; + writel(reg, dp->reg_base + ANALOGIX_DP_INT_STA_MASK); +} + +enum pll_status analogix_dp_get_pll_lock_status(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_DEBUG_CTL); + if (reg & PLL_LOCK) + return PLL_LOCKED; + else + return PLL_UNLOCKED; +} + +void analogix_dp_set_pll_power_down(struct analogix_dp_device *dp, bool enable) +{ + u32 reg; + + if (enable) { + reg = readl(dp->reg_base + ANALOGIX_DP_PLL_CTL); + reg |= DP_PLL_PD; + writel(reg, dp->reg_base + ANALOGIX_DP_PLL_CTL); + } else { + reg = readl(dp->reg_base + ANALOGIX_DP_PLL_CTL); + reg &= ~DP_PLL_PD; + writel(reg, dp->reg_base + ANALOGIX_DP_PLL_CTL); + } +} + +void analogix_dp_set_analog_power_down(struct analogix_dp_device *dp, + enum analog_power_block block, + bool enable) +{ + u32 reg; + u32 phy_pd_addr = ANALOGIX_DP_PHY_PD; + + if (dp->plat_data && (dp->plat_data->dev_type == RK3288_DP)) + phy_pd_addr = ANALOGIX_DP_PD; + + switch (block) { + case AUX_BLOCK: + if (enable) { + reg = readl(dp->reg_base + phy_pd_addr); + reg |= AUX_PD; + writel(reg, dp->reg_base + phy_pd_addr); + } else { + reg = readl(dp->reg_base + phy_pd_addr); + reg &= ~AUX_PD; + writel(reg, dp->reg_base + phy_pd_addr); + } + break; + case CH0_BLOCK: + if (enable) { + reg = readl(dp->reg_base + phy_pd_addr); + reg |= CH0_PD; + writel(reg, dp->reg_base + phy_pd_addr); + } else { + reg = readl(dp->reg_base + phy_pd_addr); + reg &= ~CH0_PD; + writel(reg, dp->reg_base + phy_pd_addr); + } + break; + case CH1_BLOCK: + if (enable) { + reg = readl(dp->reg_base + phy_pd_addr); + reg |= CH1_PD; + writel(reg, dp->reg_base + phy_pd_addr); + } else { + reg = readl(dp->reg_base + phy_pd_addr); + reg &= ~CH1_PD; + writel(reg, dp->reg_base + phy_pd_addr); + } + break; + case CH2_BLOCK: + if (enable) { + reg = readl(dp->reg_base + phy_pd_addr); + reg |= CH2_PD; + writel(reg, dp->reg_base + phy_pd_addr); + } else { + reg = readl(dp->reg_base + phy_pd_addr); + reg &= ~CH2_PD; + writel(reg, dp->reg_base + phy_pd_addr); + } + break; + case CH3_BLOCK: + if (enable) { + reg = readl(dp->reg_base + phy_pd_addr); + reg |= CH3_PD; + writel(reg, dp->reg_base + phy_pd_addr); + } else { + reg = readl(dp->reg_base + phy_pd_addr); + reg &= ~CH3_PD; + writel(reg, dp->reg_base + phy_pd_addr); + } + break; + case ANALOG_TOTAL: + if (enable) { + reg = readl(dp->reg_base + phy_pd_addr); + reg |= DP_PHY_PD; + writel(reg, dp->reg_base + phy_pd_addr); + } else { + reg = readl(dp->reg_base + phy_pd_addr); + reg &= ~DP_PHY_PD; + writel(reg, dp->reg_base + phy_pd_addr); + } + break; + case POWER_ALL: + if (enable) { + reg = DP_PHY_PD | AUX_PD | CH3_PD | CH2_PD | + CH1_PD | CH0_PD; + writel(reg, dp->reg_base + phy_pd_addr); + } else { + writel(0x00, dp->reg_base + phy_pd_addr); + } + break; + default: + break; + } +} + +void analogix_dp_init_analog_func(struct analogix_dp_device *dp) +{ + u32 reg; + int timeout_loop = 0; + + analogix_dp_set_analog_power_down(dp, POWER_ALL, 0); + + reg = PLL_LOCK_CHG; + writel(reg, dp->reg_base + ANALOGIX_DP_COMMON_INT_STA_1); + + reg = readl(dp->reg_base + ANALOGIX_DP_DEBUG_CTL); + reg &= ~(F_PLL_LOCK | PLL_LOCK_CTRL); + writel(reg, dp->reg_base + ANALOGIX_DP_DEBUG_CTL); + + /* Power up PLL */ + if (analogix_dp_get_pll_lock_status(dp) == PLL_UNLOCKED) { + analogix_dp_set_pll_power_down(dp, 0); + + while (analogix_dp_get_pll_lock_status(dp) == PLL_UNLOCKED) { + timeout_loop++; + if (DP_TIMEOUT_LOOP_COUNT < timeout_loop) { + dev_err(dp->dev, "failed to get pll lock status\n"); + return; + } + usleep_range(10, 20); + } + } + + /* Enable Serdes FIFO function and Link symbol clock domain module */ + reg = readl(dp->reg_base + ANALOGIX_DP_FUNC_EN_2); + reg &= ~(SERDES_FIFO_FUNC_EN_N | LS_CLK_DOMAIN_FUNC_EN_N + | AUX_FUNC_EN_N); + writel(reg, dp->reg_base + ANALOGIX_DP_FUNC_EN_2); +} + +void analogix_dp_clear_hotplug_interrupts(struct analogix_dp_device *dp) +{ + u32 reg; + + if (gpio_is_valid(dp->hpd_gpio)) + return; + + reg = HOTPLUG_CHG | HPD_LOST | PLUG; + writel(reg, dp->reg_base + ANALOGIX_DP_COMMON_INT_STA_4); + + reg = INT_HPD; + writel(reg, dp->reg_base + ANALOGIX_DP_INT_STA); +} + +void analogix_dp_init_hpd(struct analogix_dp_device *dp) +{ + u32 reg; + + if (gpio_is_valid(dp->hpd_gpio)) + return; + + analogix_dp_clear_hotplug_interrupts(dp); + + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_3); + reg &= ~(F_HPD | HPD_CTRL); + writel(reg, dp->reg_base + ANALOGIX_DP_SYS_CTL_3); +} + +void analogix_dp_force_hpd(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_3); + reg = (F_HPD | HPD_CTRL); + writel(reg, dp->reg_base + ANALOGIX_DP_SYS_CTL_3); +} + +enum dp_irq_type analogix_dp_get_irq_type(struct analogix_dp_device *dp) +{ + u32 reg; + + if (gpio_is_valid(dp->hpd_gpio)) { + reg = gpio_get_value(dp->hpd_gpio); + if (reg) + return DP_IRQ_TYPE_HP_CABLE_IN; + else + return DP_IRQ_TYPE_HP_CABLE_OUT; + } else { + /* Parse hotplug interrupt status register */ + reg = readl(dp->reg_base + ANALOGIX_DP_COMMON_INT_STA_4); + + if (reg & PLUG) + return DP_IRQ_TYPE_HP_CABLE_IN; + + if (reg & HPD_LOST) + return DP_IRQ_TYPE_HP_CABLE_OUT; + + if (reg & HOTPLUG_CHG) + return DP_IRQ_TYPE_HP_CHANGE; + + return DP_IRQ_TYPE_UNKNOWN; + } +} + +void analogix_dp_reset_aux(struct analogix_dp_device *dp) +{ + u32 reg; + + /* Disable AUX channel module */ + reg = readl(dp->reg_base + ANALOGIX_DP_FUNC_EN_2); + reg |= AUX_FUNC_EN_N; + writel(reg, dp->reg_base + ANALOGIX_DP_FUNC_EN_2); +} + +void analogix_dp_init_aux(struct analogix_dp_device *dp) +{ + u32 reg; + + /* Clear inerrupts related to AUX channel */ + reg = RPLY_RECEIV | AUX_ERR; + writel(reg, dp->reg_base + ANALOGIX_DP_INT_STA); + + analogix_dp_reset_aux(dp); + + /* Disable AUX transaction H/W retry */ + if (dp->plat_data && (dp->plat_data->dev_type == RK3288_DP)) + reg = AUX_BIT_PERIOD_EXPECTED_DELAY(0) | + AUX_HW_RETRY_COUNT_SEL(3) | + AUX_HW_RETRY_INTERVAL_600_MICROSECONDS; + else + reg = AUX_BIT_PERIOD_EXPECTED_DELAY(3) | + AUX_HW_RETRY_COUNT_SEL(0) | + AUX_HW_RETRY_INTERVAL_600_MICROSECONDS; + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_HW_RETRY_CTL); + + /* Receive AUX Channel DEFER commands equal to DEFFER_COUNT*64 */ + reg = DEFER_CTRL_EN | DEFER_COUNT(1); + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_CH_DEFER_CTL); + + /* Enable AUX channel module */ + reg = readl(dp->reg_base + ANALOGIX_DP_FUNC_EN_2); + reg &= ~AUX_FUNC_EN_N; + writel(reg, dp->reg_base + ANALOGIX_DP_FUNC_EN_2); +} + +int analogix_dp_get_plug_in_status(struct analogix_dp_device *dp) +{ + u32 reg; + + if (gpio_is_valid(dp->hpd_gpio)) { + if (gpio_get_value(dp->hpd_gpio)) + return 0; + } else { + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_3); + if (reg & HPD_STATUS) + return 0; + } + + return -EINVAL; +} + +void analogix_dp_enable_sw_function(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_FUNC_EN_1); + reg &= ~SW_FUNC_EN_N; + writel(reg, dp->reg_base + ANALOGIX_DP_FUNC_EN_1); +} + +int analogix_dp_start_aux_transaction(struct analogix_dp_device *dp) +{ + int reg; + int retval = 0; + int timeout_loop = 0; + + /* Enable AUX CH operation */ + reg = readl(dp->reg_base + ANALOGIX_DP_AUX_CH_CTL_2); + reg |= AUX_EN; + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_CH_CTL_2); + + /* Is AUX CH command reply received? */ + reg = readl(dp->reg_base + ANALOGIX_DP_INT_STA); + while (!(reg & RPLY_RECEIV)) { + timeout_loop++; + if (DP_TIMEOUT_LOOP_COUNT < timeout_loop) { + dev_err(dp->dev, "AUX CH command reply failed!\n"); + return -ETIMEDOUT; + } + reg = readl(dp->reg_base + ANALOGIX_DP_INT_STA); + usleep_range(10, 11); + } + + /* Clear interrupt source for AUX CH command reply */ + writel(RPLY_RECEIV, dp->reg_base + ANALOGIX_DP_INT_STA); + + /* Clear interrupt source for AUX CH access error */ + reg = readl(dp->reg_base + ANALOGIX_DP_INT_STA); + if (reg & AUX_ERR) { + writel(AUX_ERR, dp->reg_base + ANALOGIX_DP_INT_STA); + return -EREMOTEIO; + } + + /* Check AUX CH error access status */ + reg = readl(dp->reg_base + ANALOGIX_DP_AUX_CH_STA); + if ((reg & AUX_STATUS_MASK) != 0) { + dev_err(dp->dev, "AUX CH error happens: %d\n\n", + reg & AUX_STATUS_MASK); + return -EREMOTEIO; + } + + return retval; +} + +int analogix_dp_write_byte_to_dpcd(struct analogix_dp_device *dp, + unsigned int reg_addr, + unsigned char data) +{ + u32 reg; + int i; + int retval; + + for (i = 0; i < 3; i++) { + /* Clear AUX CH data buffer */ + reg = BUF_CLR; + writel(reg, dp->reg_base + ANALOGIX_DP_BUFFER_DATA_CTL); + + /* Select DPCD device address */ + reg = AUX_ADDR_7_0(reg_addr); + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_ADDR_7_0); + reg = AUX_ADDR_15_8(reg_addr); + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_ADDR_15_8); + reg = AUX_ADDR_19_16(reg_addr); + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_ADDR_19_16); + + /* Write data buffer */ + reg = (unsigned int)data; + writel(reg, dp->reg_base + ANALOGIX_DP_BUF_DATA_0); + + /* + * Set DisplayPort transaction and write 1 byte + * If bit 3 is 1, DisplayPort transaction. + * If Bit 3 is 0, I2C transaction. + */ + reg = AUX_TX_COMM_DP_TRANSACTION | AUX_TX_COMM_WRITE; + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_CH_CTL_1); + + /* Start AUX transaction */ + retval = analogix_dp_start_aux_transaction(dp); + if (retval == 0) + break; + + dev_dbg(dp->dev, "%s: Aux Transaction fail!\n", __func__); + } + + return retval; +} + +int analogix_dp_read_byte_from_dpcd(struct analogix_dp_device *dp, + unsigned int reg_addr, + unsigned char *data) +{ + u32 reg; + int i; + int retval; + + for (i = 0; i < 3; i++) { + /* Clear AUX CH data buffer */ + reg = BUF_CLR; + writel(reg, dp->reg_base + ANALOGIX_DP_BUFFER_DATA_CTL); + + /* Select DPCD device address */ + reg = AUX_ADDR_7_0(reg_addr); + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_ADDR_7_0); + reg = AUX_ADDR_15_8(reg_addr); + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_ADDR_15_8); + reg = AUX_ADDR_19_16(reg_addr); + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_ADDR_19_16); + + /* + * Set DisplayPort transaction and read 1 byte + * If bit 3 is 1, DisplayPort transaction. + * If Bit 3 is 0, I2C transaction. + */ + reg = AUX_TX_COMM_DP_TRANSACTION | AUX_TX_COMM_READ; + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_CH_CTL_1); + + /* Start AUX transaction */ + retval = analogix_dp_start_aux_transaction(dp); + if (retval == 0) + break; + + dev_dbg(dp->dev, "%s: Aux Transaction fail!\n", __func__); + } + + /* Read data buffer */ + reg = readl(dp->reg_base + ANALOGIX_DP_BUF_DATA_0); + *data = (unsigned char)(reg & 0xff); + + return retval; +} + +int analogix_dp_write_bytes_to_dpcd(struct analogix_dp_device *dp, + unsigned int reg_addr, + unsigned int count, + unsigned char data[]) +{ + u32 reg; + unsigned int start_offset; + unsigned int cur_data_count; + unsigned int cur_data_idx; + int i; + int retval = 0; + + /* Clear AUX CH data buffer */ + reg = BUF_CLR; + writel(reg, dp->reg_base + ANALOGIX_DP_BUFFER_DATA_CTL); + + start_offset = 0; + while (start_offset < count) { + /* Buffer size of AUX CH is 16 * 4bytes */ + if ((count - start_offset) > 16) + cur_data_count = 16; + else + cur_data_count = count - start_offset; + + for (i = 0; i < 3; i++) { + /* Select DPCD device address */ + reg = AUX_ADDR_7_0(reg_addr + start_offset); + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_ADDR_7_0); + reg = AUX_ADDR_15_8(reg_addr + start_offset); + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_ADDR_15_8); + reg = AUX_ADDR_19_16(reg_addr + start_offset); + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_ADDR_19_16); + + for (cur_data_idx = 0; cur_data_idx < cur_data_count; + cur_data_idx++) { + reg = data[start_offset + cur_data_idx]; + writel(reg, dp->reg_base + + ANALOGIX_DP_BUF_DATA_0 + + 4 * cur_data_idx); + } + + /* + * Set DisplayPort transaction and write + * If bit 3 is 1, DisplayPort transaction. + * If Bit 3 is 0, I2C transaction. + */ + reg = AUX_LENGTH(cur_data_count) | + AUX_TX_COMM_DP_TRANSACTION | AUX_TX_COMM_WRITE; + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_CH_CTL_1); + + /* Start AUX transaction */ + retval = analogix_dp_start_aux_transaction(dp); + if (retval == 0) + break; + + dev_dbg(dp->dev, "%s: Aux Transaction fail!\n", + __func__); + } + + start_offset += cur_data_count; + } + + return retval; +} + +int analogix_dp_read_bytes_from_dpcd(struct analogix_dp_device *dp, + unsigned int reg_addr, + unsigned int count, + unsigned char data[]) +{ + u32 reg; + unsigned int start_offset; + unsigned int cur_data_count; + unsigned int cur_data_idx; + int i; + int retval = 0; + + /* Clear AUX CH data buffer */ + reg = BUF_CLR; + writel(reg, dp->reg_base + ANALOGIX_DP_BUFFER_DATA_CTL); + + start_offset = 0; + while (start_offset < count) { + /* Buffer size of AUX CH is 16 * 4bytes */ + if ((count - start_offset) > 16) + cur_data_count = 16; + else + cur_data_count = count - start_offset; + + /* AUX CH Request Transaction process */ + for (i = 0; i < 3; i++) { + /* Select DPCD device address */ + reg = AUX_ADDR_7_0(reg_addr + start_offset); + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_ADDR_7_0); + reg = AUX_ADDR_15_8(reg_addr + start_offset); + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_ADDR_15_8); + reg = AUX_ADDR_19_16(reg_addr + start_offset); + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_ADDR_19_16); + + /* + * Set DisplayPort transaction and read + * If bit 3 is 1, DisplayPort transaction. + * If Bit 3 is 0, I2C transaction. + */ + reg = AUX_LENGTH(cur_data_count) | + AUX_TX_COMM_DP_TRANSACTION | AUX_TX_COMM_READ; + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_CH_CTL_1); + + /* Start AUX transaction */ + retval = analogix_dp_start_aux_transaction(dp); + if (retval == 0) + break; + + dev_dbg(dp->dev, "%s: Aux Transaction fail!\n", + __func__); + } + + for (cur_data_idx = 0; cur_data_idx < cur_data_count; + cur_data_idx++) { + reg = readl(dp->reg_base + ANALOGIX_DP_BUF_DATA_0 + + 4 * cur_data_idx); + data[start_offset + cur_data_idx] = + (unsigned char)reg; + } + + start_offset += cur_data_count; + } + + return retval; +} + +int analogix_dp_select_i2c_device(struct analogix_dp_device *dp, + unsigned int device_addr, + unsigned int reg_addr) +{ + u32 reg; + int retval; + + /* Set EDID device address */ + reg = device_addr; + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_ADDR_7_0); + writel(0x0, dp->reg_base + ANALOGIX_DP_AUX_ADDR_15_8); + writel(0x0, dp->reg_base + ANALOGIX_DP_AUX_ADDR_19_16); + + /* Set offset from base address of EDID device */ + writel(reg_addr, dp->reg_base + ANALOGIX_DP_BUF_DATA_0); + + /* + * Set I2C transaction and write address + * If bit 3 is 1, DisplayPort transaction. + * If Bit 3 is 0, I2C transaction. + */ + reg = AUX_TX_COMM_I2C_TRANSACTION | AUX_TX_COMM_MOT | + AUX_TX_COMM_WRITE; + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_CH_CTL_1); + + /* Start AUX transaction */ + retval = analogix_dp_start_aux_transaction(dp); + if (retval != 0) + dev_dbg(dp->dev, "%s: Aux Transaction fail!\n", __func__); + + return retval; +} + +int analogix_dp_read_byte_from_i2c(struct analogix_dp_device *dp, + unsigned int device_addr, + unsigned int reg_addr, + unsigned int *data) +{ + u32 reg; + int i; + int retval; + + for (i = 0; i < 3; i++) { + /* Clear AUX CH data buffer */ + reg = BUF_CLR; + writel(reg, dp->reg_base + ANALOGIX_DP_BUFFER_DATA_CTL); + + /* Select EDID device */ + retval = analogix_dp_select_i2c_device(dp, device_addr, + reg_addr); + if (retval != 0) + continue; + + /* + * Set I2C transaction and read data + * If bit 3 is 1, DisplayPort transaction. + * If Bit 3 is 0, I2C transaction. + */ + reg = AUX_TX_COMM_I2C_TRANSACTION | + AUX_TX_COMM_READ; + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_CH_CTL_1); + + /* Start AUX transaction */ + retval = analogix_dp_start_aux_transaction(dp); + if (retval == 0) + break; + + dev_dbg(dp->dev, "%s: Aux Transaction fail!\n", __func__); + } + + /* Read data */ + if (retval == 0) + *data = readl(dp->reg_base + ANALOGIX_DP_BUF_DATA_0); + + return retval; +} + +int analogix_dp_read_bytes_from_i2c(struct analogix_dp_device *dp, + unsigned int device_addr, + unsigned int reg_addr, + unsigned int count, + unsigned char edid[]) +{ + u32 reg; + unsigned int i, j; + unsigned int cur_data_idx; + unsigned int defer = 0; + int retval = 0; + + for (i = 0; i < count; i += 16) { + for (j = 0; j < 3; j++) { + /* Clear AUX CH data buffer */ + reg = BUF_CLR; + writel(reg, dp->reg_base + ANALOGIX_DP_BUFFER_DATA_CTL); + + /* Set normal AUX CH command */ + reg = readl(dp->reg_base + ANALOGIX_DP_AUX_CH_CTL_2); + reg &= ~ADDR_ONLY; + writel(reg, dp->reg_base + ANALOGIX_DP_AUX_CH_CTL_2); + + /* + * If Rx sends defer, Tx sends only reads + * request without sending address + */ + if (!defer) + retval = analogix_dp_select_i2c_device(dp, + device_addr, reg_addr + i); + else + defer = 0; + + if (retval == 0) { + /* + * Set I2C transaction and write data + * If bit 3 is 1, DisplayPort transaction. + * If Bit 3 is 0, I2C transaction. + */ + reg = AUX_LENGTH(16) | + AUX_TX_COMM_I2C_TRANSACTION | + AUX_TX_COMM_READ; + writel(reg, dp->reg_base + + ANALOGIX_DP_AUX_CH_CTL_1); + + /* Start AUX transaction */ + retval = analogix_dp_start_aux_transaction(dp); + if (retval == 0) + break; + + dev_dbg(dp->dev, "%s: Aux Transaction fail!\n", + __func__); + } + /* Check if Rx sends defer */ + reg = readl(dp->reg_base + ANALOGIX_DP_AUX_RX_COMM); + if (reg == AUX_RX_COMM_AUX_DEFER || + reg == AUX_RX_COMM_I2C_DEFER) { + dev_err(dp->dev, "Defer: %d\n\n", reg); + defer = 1; + } + } + + for (cur_data_idx = 0; cur_data_idx < 16; cur_data_idx++) { + reg = readl(dp->reg_base + ANALOGIX_DP_BUF_DATA_0 + + 4 * cur_data_idx); + edid[i + cur_data_idx] = (unsigned char)reg; + } + } + + return retval; +} + +void analogix_dp_set_link_bandwidth(struct analogix_dp_device *dp, u32 bwtype) +{ + u32 reg; + + reg = bwtype; + if ((bwtype == DP_LINK_BW_2_7) || (bwtype == DP_LINK_BW_1_62)) + writel(reg, dp->reg_base + ANALOGIX_DP_LINK_BW_SET); +} + +void analogix_dp_get_link_bandwidth(struct analogix_dp_device *dp, u32 *bwtype) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_LINK_BW_SET); + *bwtype = reg; +} + +void analogix_dp_set_lane_count(struct analogix_dp_device *dp, u32 count) +{ + u32 reg; + + reg = count; + writel(reg, dp->reg_base + ANALOGIX_DP_LANE_COUNT_SET); +} + +void analogix_dp_get_lane_count(struct analogix_dp_device *dp, u32 *count) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_LANE_COUNT_SET); + *count = reg; +} + +void analogix_dp_enable_enhanced_mode(struct analogix_dp_device *dp, + bool enable) +{ + u32 reg; + + if (enable) { + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_4); + reg |= ENHANCED; + writel(reg, dp->reg_base + ANALOGIX_DP_SYS_CTL_4); + } else { + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_4); + reg &= ~ENHANCED; + writel(reg, dp->reg_base + ANALOGIX_DP_SYS_CTL_4); + } +} + +void analogix_dp_set_training_pattern(struct analogix_dp_device *dp, + enum pattern_set pattern) +{ + u32 reg; + + switch (pattern) { + case PRBS7: + reg = SCRAMBLING_ENABLE | LINK_QUAL_PATTERN_SET_PRBS7; + writel(reg, dp->reg_base + ANALOGIX_DP_TRAINING_PTN_SET); + break; + case D10_2: + reg = SCRAMBLING_ENABLE | LINK_QUAL_PATTERN_SET_D10_2; + writel(reg, dp->reg_base + ANALOGIX_DP_TRAINING_PTN_SET); + break; + case TRAINING_PTN1: + reg = SCRAMBLING_DISABLE | SW_TRAINING_PATTERN_SET_PTN1; + writel(reg, dp->reg_base + ANALOGIX_DP_TRAINING_PTN_SET); + break; + case TRAINING_PTN2: + reg = SCRAMBLING_DISABLE | SW_TRAINING_PATTERN_SET_PTN2; + writel(reg, dp->reg_base + ANALOGIX_DP_TRAINING_PTN_SET); + break; + case DP_NONE: + reg = SCRAMBLING_ENABLE | + LINK_QUAL_PATTERN_SET_DISABLE | + SW_TRAINING_PATTERN_SET_NORMAL; + writel(reg, dp->reg_base + ANALOGIX_DP_TRAINING_PTN_SET); + break; + default: + break; + } +} + +void analogix_dp_set_lane0_pre_emphasis(struct analogix_dp_device *dp, + u32 level) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_LN0_LINK_TRAINING_CTL); + reg &= ~PRE_EMPHASIS_SET_MASK; + reg |= level << PRE_EMPHASIS_SET_SHIFT; + writel(reg, dp->reg_base + ANALOGIX_DP_LN0_LINK_TRAINING_CTL); +} + +void analogix_dp_set_lane1_pre_emphasis(struct analogix_dp_device *dp, + u32 level) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_LN1_LINK_TRAINING_CTL); + reg &= ~PRE_EMPHASIS_SET_MASK; + reg |= level << PRE_EMPHASIS_SET_SHIFT; + writel(reg, dp->reg_base + ANALOGIX_DP_LN1_LINK_TRAINING_CTL); +} + +void analogix_dp_set_lane2_pre_emphasis(struct analogix_dp_device *dp, + u32 level) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_LN2_LINK_TRAINING_CTL); + reg &= ~PRE_EMPHASIS_SET_MASK; + reg |= level << PRE_EMPHASIS_SET_SHIFT; + writel(reg, dp->reg_base + ANALOGIX_DP_LN2_LINK_TRAINING_CTL); +} + +void analogix_dp_set_lane3_pre_emphasis(struct analogix_dp_device *dp, + u32 level) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_LN3_LINK_TRAINING_CTL); + reg &= ~PRE_EMPHASIS_SET_MASK; + reg |= level << PRE_EMPHASIS_SET_SHIFT; + writel(reg, dp->reg_base + ANALOGIX_DP_LN3_LINK_TRAINING_CTL); +} + +void analogix_dp_set_lane0_link_training(struct analogix_dp_device *dp, + u32 training_lane) +{ + u32 reg; + + reg = training_lane; + writel(reg, dp->reg_base + ANALOGIX_DP_LN0_LINK_TRAINING_CTL); +} + +void analogix_dp_set_lane1_link_training(struct analogix_dp_device *dp, + u32 training_lane) +{ + u32 reg; + + reg = training_lane; + writel(reg, dp->reg_base + ANALOGIX_DP_LN1_LINK_TRAINING_CTL); +} + +void analogix_dp_set_lane2_link_training(struct analogix_dp_device *dp, + u32 training_lane) +{ + u32 reg; + + reg = training_lane; + writel(reg, dp->reg_base + ANALOGIX_DP_LN2_LINK_TRAINING_CTL); +} + +void analogix_dp_set_lane3_link_training(struct analogix_dp_device *dp, + u32 training_lane) +{ + u32 reg; + + reg = training_lane; + writel(reg, dp->reg_base + ANALOGIX_DP_LN3_LINK_TRAINING_CTL); +} + +u32 analogix_dp_get_lane0_link_training(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_LN0_LINK_TRAINING_CTL); + return reg; +} + +u32 analogix_dp_get_lane1_link_training(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_LN1_LINK_TRAINING_CTL); + return reg; +} + +u32 analogix_dp_get_lane2_link_training(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_LN2_LINK_TRAINING_CTL); + return reg; +} + +u32 analogix_dp_get_lane3_link_training(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_LN3_LINK_TRAINING_CTL); + return reg; +} + +void analogix_dp_reset_macro(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_PHY_TEST); + reg |= MACRO_RST; + writel(reg, dp->reg_base + ANALOGIX_DP_PHY_TEST); + + /* 10 us is the minimum reset time. */ + usleep_range(10, 20); + + reg &= ~MACRO_RST; + writel(reg, dp->reg_base + ANALOGIX_DP_PHY_TEST); +} + +void analogix_dp_init_video(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = VSYNC_DET | VID_FORMAT_CHG | VID_CLK_CHG; + writel(reg, dp->reg_base + ANALOGIX_DP_COMMON_INT_STA_1); + + reg = 0x0; + writel(reg, dp->reg_base + ANALOGIX_DP_SYS_CTL_1); + + reg = CHA_CRI(4) | CHA_CTRL; + writel(reg, dp->reg_base + ANALOGIX_DP_SYS_CTL_2); + + reg = 0x0; + writel(reg, dp->reg_base + ANALOGIX_DP_SYS_CTL_3); + + reg = VID_HRES_TH(2) | VID_VRES_TH(0); + writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_8); +} + +void analogix_dp_set_video_color_format(struct analogix_dp_device *dp) +{ + u32 reg; + + /* Configure the input color depth, color space, dynamic range */ + reg = (dp->video_info.dynamic_range << IN_D_RANGE_SHIFT) | + (dp->video_info.color_depth << IN_BPC_SHIFT) | + (dp->video_info.color_space << IN_COLOR_F_SHIFT); + writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_2); + + /* Set Input Color YCbCr Coefficients to ITU601 or ITU709 */ + reg = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_3); + reg &= ~IN_YC_COEFFI_MASK; + if (dp->video_info.ycbcr_coeff) + reg |= IN_YC_COEFFI_ITU709; + else + reg |= IN_YC_COEFFI_ITU601; + writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_3); +} + +int analogix_dp_is_slave_video_stream_clock_on(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_1); + writel(reg, dp->reg_base + ANALOGIX_DP_SYS_CTL_1); + + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_1); + + if (!(reg & DET_STA)) { + dev_dbg(dp->dev, "Input stream clock not detected.\n"); + return -EINVAL; + } + + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_2); + writel(reg, dp->reg_base + ANALOGIX_DP_SYS_CTL_2); + + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_2); + dev_dbg(dp->dev, "wait SYS_CTL_2.\n"); + + if (reg & CHA_STA) { + dev_dbg(dp->dev, "Input stream clk is changing\n"); + return -EINVAL; + } + + return 0; +} + +void analogix_dp_set_video_cr_mn(struct analogix_dp_device *dp, + enum clock_recovery_m_value_type type, + u32 m_value, u32 n_value) +{ + u32 reg; + + if (type == REGISTER_M) { + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_4); + reg |= FIX_M_VID; + writel(reg, dp->reg_base + ANALOGIX_DP_SYS_CTL_4); + reg = m_value & 0xff; + writel(reg, dp->reg_base + ANALOGIX_DP_M_VID_0); + reg = (m_value >> 8) & 0xff; + writel(reg, dp->reg_base + ANALOGIX_DP_M_VID_1); + reg = (m_value >> 16) & 0xff; + writel(reg, dp->reg_base + ANALOGIX_DP_M_VID_2); + + reg = n_value & 0xff; + writel(reg, dp->reg_base + ANALOGIX_DP_N_VID_0); + reg = (n_value >> 8) & 0xff; + writel(reg, dp->reg_base + ANALOGIX_DP_N_VID_1); + reg = (n_value >> 16) & 0xff; + writel(reg, dp->reg_base + ANALOGIX_DP_N_VID_2); + } else { + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_4); + reg &= ~FIX_M_VID; + writel(reg, dp->reg_base + ANALOGIX_DP_SYS_CTL_4); + + writel(0x00, dp->reg_base + ANALOGIX_DP_N_VID_0); + writel(0x80, dp->reg_base + ANALOGIX_DP_N_VID_1); + writel(0x00, dp->reg_base + ANALOGIX_DP_N_VID_2); + } +} + +void analogix_dp_set_video_timing_mode(struct analogix_dp_device *dp, u32 type) +{ + u32 reg; + + if (type == VIDEO_TIMING_FROM_CAPTURE) { + reg = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_10); + reg &= ~FORMAT_SEL; + writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_10); + } else { + reg = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_10); + reg |= FORMAT_SEL; + writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_10); + } +} + +void analogix_dp_enable_video_master(struct analogix_dp_device *dp, bool enable) +{ + u32 reg; + + if (enable) { + reg = readl(dp->reg_base + ANALOGIX_DP_SOC_GENERAL_CTL); + reg &= ~VIDEO_MODE_MASK; + reg |= VIDEO_MASTER_MODE_EN | VIDEO_MODE_MASTER_MODE; + writel(reg, dp->reg_base + ANALOGIX_DP_SOC_GENERAL_CTL); + } else { + reg = readl(dp->reg_base + ANALOGIX_DP_SOC_GENERAL_CTL); + reg &= ~VIDEO_MODE_MASK; + reg |= VIDEO_MODE_SLAVE_MODE; + writel(reg, dp->reg_base + ANALOGIX_DP_SOC_GENERAL_CTL); + } +} + +void analogix_dp_start_video(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_1); + reg |= VIDEO_EN; + writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_1); +} + +int analogix_dp_is_video_stream_on(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_3); + writel(reg, dp->reg_base + ANALOGIX_DP_SYS_CTL_3); + + reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_3); + if (!(reg & STRM_VALID)) { + dev_dbg(dp->dev, "Input video stream is not detected.\n"); + return -EINVAL; + } + + return 0; +} + +void analogix_dp_config_video_slave_mode(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_FUNC_EN_1); + reg &= ~(MASTER_VID_FUNC_EN_N | SLAVE_VID_FUNC_EN_N); + reg |= MASTER_VID_FUNC_EN_N; + writel(reg, dp->reg_base + ANALOGIX_DP_FUNC_EN_1); + + reg = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_10); + reg &= ~INTERACE_SCAN_CFG; + reg |= (dp->video_info.interlaced << 2); + writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_10); + + reg = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_10); + reg &= ~VSYNC_POLARITY_CFG; + reg |= (dp->video_info.v_sync_polarity << 1); + writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_10); + + reg = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_10); + reg &= ~HSYNC_POLARITY_CFG; + reg |= (dp->video_info.h_sync_polarity << 0); + writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_10); + + reg = AUDIO_MODE_SPDIF_MODE | VIDEO_MODE_SLAVE_MODE; + writel(reg, dp->reg_base + ANALOGIX_DP_SOC_GENERAL_CTL); +} + +void analogix_dp_enable_scrambling(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_TRAINING_PTN_SET); + reg &= ~SCRAMBLING_DISABLE; + writel(reg, dp->reg_base + ANALOGIX_DP_TRAINING_PTN_SET); +} + +void analogix_dp_disable_scrambling(struct analogix_dp_device *dp) +{ + u32 reg; + + reg = readl(dp->reg_base + ANALOGIX_DP_TRAINING_PTN_SET); + reg |= SCRAMBLING_DISABLE; + writel(reg, dp->reg_base + ANALOGIX_DP_TRAINING_PTN_SET); +} diff --git a/drivers/gpu/drm/exynos/exynos_dp_reg.h b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h index 2e9bd0e0b9f2..337912b0aeab 100644 --- a/drivers/gpu/drm/exynos/exynos_dp_reg.h +++ b/drivers/gpu/drm/bridge/analogix/analogix_dp_reg.h @@ -1,5 +1,5 @@ /* - * Register definition file for Samsung DP driver + * Register definition file for Analogix DP core driver * * Copyright (C) 2012 Samsung Electronics Co., Ltd. * Author: Jingoo Han <jg1.han@samsung.com> @@ -9,96 +9,104 @@ * published by the Free Software Foundation. */ -#ifndef _EXYNOS_DP_REG_H -#define _EXYNOS_DP_REG_H - -#define EXYNOS_DP_TX_SW_RESET 0x14 -#define EXYNOS_DP_FUNC_EN_1 0x18 -#define EXYNOS_DP_FUNC_EN_2 0x1C -#define EXYNOS_DP_VIDEO_CTL_1 0x20 -#define EXYNOS_DP_VIDEO_CTL_2 0x24 -#define EXYNOS_DP_VIDEO_CTL_3 0x28 - -#define EXYNOS_DP_VIDEO_CTL_8 0x3C -#define EXYNOS_DP_VIDEO_CTL_10 0x44 - -#define EXYNOS_DP_LANE_MAP 0x35C - -#define EXYNOS_DP_ANALOG_CTL_1 0x370 -#define EXYNOS_DP_ANALOG_CTL_2 0x374 -#define EXYNOS_DP_ANALOG_CTL_3 0x378 -#define EXYNOS_DP_PLL_FILTER_CTL_1 0x37C -#define EXYNOS_DP_TX_AMP_TUNING_CTL 0x380 - -#define EXYNOS_DP_AUX_HW_RETRY_CTL 0x390 - -#define EXYNOS_DP_COMMON_INT_STA_1 0x3C4 -#define EXYNOS_DP_COMMON_INT_STA_2 0x3C8 -#define EXYNOS_DP_COMMON_INT_STA_3 0x3CC -#define EXYNOS_DP_COMMON_INT_STA_4 0x3D0 -#define EXYNOS_DP_INT_STA 0x3DC -#define EXYNOS_DP_COMMON_INT_MASK_1 0x3E0 -#define EXYNOS_DP_COMMON_INT_MASK_2 0x3E4 -#define EXYNOS_DP_COMMON_INT_MASK_3 0x3E8 -#define EXYNOS_DP_COMMON_INT_MASK_4 0x3EC -#define EXYNOS_DP_INT_STA_MASK 0x3F8 -#define EXYNOS_DP_INT_CTL 0x3FC - -#define EXYNOS_DP_SYS_CTL_1 0x600 -#define EXYNOS_DP_SYS_CTL_2 0x604 -#define EXYNOS_DP_SYS_CTL_3 0x608 -#define EXYNOS_DP_SYS_CTL_4 0x60C - -#define EXYNOS_DP_PKT_SEND_CTL 0x640 -#define EXYNOS_DP_HDCP_CTL 0x648 - -#define EXYNOS_DP_LINK_BW_SET 0x680 -#define EXYNOS_DP_LANE_COUNT_SET 0x684 -#define EXYNOS_DP_TRAINING_PTN_SET 0x688 -#define EXYNOS_DP_LN0_LINK_TRAINING_CTL 0x68C -#define EXYNOS_DP_LN1_LINK_TRAINING_CTL 0x690 -#define EXYNOS_DP_LN2_LINK_TRAINING_CTL 0x694 -#define EXYNOS_DP_LN3_LINK_TRAINING_CTL 0x698 - -#define EXYNOS_DP_DEBUG_CTL 0x6C0 -#define EXYNOS_DP_HPD_DEGLITCH_L 0x6C4 -#define EXYNOS_DP_HPD_DEGLITCH_H 0x6C8 -#define EXYNOS_DP_LINK_DEBUG_CTL 0x6E0 - -#define EXYNOS_DP_M_VID_0 0x700 -#define EXYNOS_DP_M_VID_1 0x704 -#define EXYNOS_DP_M_VID_2 0x708 -#define EXYNOS_DP_N_VID_0 0x70C -#define EXYNOS_DP_N_VID_1 0x710 -#define EXYNOS_DP_N_VID_2 0x714 - -#define EXYNOS_DP_PLL_CTL 0x71C -#define EXYNOS_DP_PHY_PD 0x720 -#define EXYNOS_DP_PHY_TEST 0x724 - -#define EXYNOS_DP_VIDEO_FIFO_THRD 0x730 -#define EXYNOS_DP_AUDIO_MARGIN 0x73C - -#define EXYNOS_DP_M_VID_GEN_FILTER_TH 0x764 -#define EXYNOS_DP_M_AUD_GEN_FILTER_TH 0x778 -#define EXYNOS_DP_AUX_CH_STA 0x780 -#define EXYNOS_DP_AUX_CH_DEFER_CTL 0x788 -#define EXYNOS_DP_AUX_RX_COMM 0x78C -#define EXYNOS_DP_BUFFER_DATA_CTL 0x790 -#define EXYNOS_DP_AUX_CH_CTL_1 0x794 -#define EXYNOS_DP_AUX_ADDR_7_0 0x798 -#define EXYNOS_DP_AUX_ADDR_15_8 0x79C -#define EXYNOS_DP_AUX_ADDR_19_16 0x7A0 -#define EXYNOS_DP_AUX_CH_CTL_2 0x7A4 - -#define EXYNOS_DP_BUF_DATA_0 0x7C0 - -#define EXYNOS_DP_SOC_GENERAL_CTL 0x800 - -/* EXYNOS_DP_TX_SW_RESET */ +#ifndef _ANALOGIX_DP_REG_H +#define _ANALOGIX_DP_REG_H + +#define ANALOGIX_DP_TX_SW_RESET 0x14 +#define ANALOGIX_DP_FUNC_EN_1 0x18 +#define ANALOGIX_DP_FUNC_EN_2 0x1C +#define ANALOGIX_DP_VIDEO_CTL_1 0x20 +#define ANALOGIX_DP_VIDEO_CTL_2 0x24 +#define ANALOGIX_DP_VIDEO_CTL_3 0x28 + +#define ANALOGIX_DP_VIDEO_CTL_8 0x3C +#define ANALOGIX_DP_VIDEO_CTL_10 0x44 + +#define ANALOGIX_DP_PLL_REG_1 0xfc +#define ANALOGIX_DP_PLL_REG_2 0x9e4 +#define ANALOGIX_DP_PLL_REG_3 0x9e8 +#define ANALOGIX_DP_PLL_REG_4 0x9ec +#define ANALOGIX_DP_PLL_REG_5 0xa00 + +#define ANALOGIX_DP_PD 0x12c + +#define ANALOGIX_DP_LANE_MAP 0x35C + +#define ANALOGIX_DP_ANALOG_CTL_1 0x370 +#define ANALOGIX_DP_ANALOG_CTL_2 0x374 +#define ANALOGIX_DP_ANALOG_CTL_3 0x378 +#define ANALOGIX_DP_PLL_FILTER_CTL_1 0x37C +#define ANALOGIX_DP_TX_AMP_TUNING_CTL 0x380 + +#define ANALOGIX_DP_AUX_HW_RETRY_CTL 0x390 + +#define ANALOGIX_DP_COMMON_INT_STA_1 0x3C4 +#define ANALOGIX_DP_COMMON_INT_STA_2 0x3C8 +#define ANALOGIX_DP_COMMON_INT_STA_3 0x3CC +#define ANALOGIX_DP_COMMON_INT_STA_4 0x3D0 +#define ANALOGIX_DP_INT_STA 0x3DC +#define ANALOGIX_DP_COMMON_INT_MASK_1 0x3E0 +#define ANALOGIX_DP_COMMON_INT_MASK_2 0x3E4 +#define ANALOGIX_DP_COMMON_INT_MASK_3 0x3E8 +#define ANALOGIX_DP_COMMON_INT_MASK_4 0x3EC +#define ANALOGIX_DP_INT_STA_MASK 0x3F8 +#define ANALOGIX_DP_INT_CTL 0x3FC + +#define ANALOGIX_DP_SYS_CTL_1 0x600 +#define ANALOGIX_DP_SYS_CTL_2 0x604 +#define ANALOGIX_DP_SYS_CTL_3 0x608 +#define ANALOGIX_DP_SYS_CTL_4 0x60C + +#define ANALOGIX_DP_PKT_SEND_CTL 0x640 +#define ANALOGIX_DP_HDCP_CTL 0x648 + +#define ANALOGIX_DP_LINK_BW_SET 0x680 +#define ANALOGIX_DP_LANE_COUNT_SET 0x684 +#define ANALOGIX_DP_TRAINING_PTN_SET 0x688 +#define ANALOGIX_DP_LN0_LINK_TRAINING_CTL 0x68C +#define ANALOGIX_DP_LN1_LINK_TRAINING_CTL 0x690 +#define ANALOGIX_DP_LN2_LINK_TRAINING_CTL 0x694 +#define ANALOGIX_DP_LN3_LINK_TRAINING_CTL 0x698 + +#define ANALOGIX_DP_DEBUG_CTL 0x6C0 +#define ANALOGIX_DP_HPD_DEGLITCH_L 0x6C4 +#define ANALOGIX_DP_HPD_DEGLITCH_H 0x6C8 +#define ANALOGIX_DP_LINK_DEBUG_CTL 0x6E0 + +#define ANALOGIX_DP_M_VID_0 0x700 +#define ANALOGIX_DP_M_VID_1 0x704 +#define ANALOGIX_DP_M_VID_2 0x708 +#define ANALOGIX_DP_N_VID_0 0x70C +#define ANALOGIX_DP_N_VID_1 0x710 +#define ANALOGIX_DP_N_VID_2 0x714 + +#define ANALOGIX_DP_PLL_CTL 0x71C +#define ANALOGIX_DP_PHY_PD 0x720 +#define ANALOGIX_DP_PHY_TEST 0x724 + +#define ANALOGIX_DP_VIDEO_FIFO_THRD 0x730 +#define ANALOGIX_DP_AUDIO_MARGIN 0x73C + +#define ANALOGIX_DP_M_VID_GEN_FILTER_TH 0x764 +#define ANALOGIX_DP_M_AUD_GEN_FILTER_TH 0x778 +#define ANALOGIX_DP_AUX_CH_STA 0x780 +#define ANALOGIX_DP_AUX_CH_DEFER_CTL 0x788 +#define ANALOGIX_DP_AUX_RX_COMM 0x78C +#define ANALOGIX_DP_BUFFER_DATA_CTL 0x790 +#define ANALOGIX_DP_AUX_CH_CTL_1 0x794 +#define ANALOGIX_DP_AUX_ADDR_7_0 0x798 +#define ANALOGIX_DP_AUX_ADDR_15_8 0x79C +#define ANALOGIX_DP_AUX_ADDR_19_16 0x7A0 +#define ANALOGIX_DP_AUX_CH_CTL_2 0x7A4 + +#define ANALOGIX_DP_BUF_DATA_0 0x7C0 + +#define ANALOGIX_DP_SOC_GENERAL_CTL 0x800 + +/* ANALOGIX_DP_TX_SW_RESET */ #define RESET_DP_TX (0x1 << 0) -/* EXYNOS_DP_FUNC_EN_1 */ +/* ANALOGIX_DP_FUNC_EN_1 */ #define MASTER_VID_FUNC_EN_N (0x1 << 7) #define SLAVE_VID_FUNC_EN_N (0x1 << 5) #define AUD_FIFO_FUNC_EN_N (0x1 << 4) @@ -107,17 +115,17 @@ #define CRC_FUNC_EN_N (0x1 << 1) #define SW_FUNC_EN_N (0x1 << 0) -/* EXYNOS_DP_FUNC_EN_2 */ +/* ANALOGIX_DP_FUNC_EN_2 */ #define SSC_FUNC_EN_N (0x1 << 7) #define AUX_FUNC_EN_N (0x1 << 2) #define SERDES_FIFO_FUNC_EN_N (0x1 << 1) #define LS_CLK_DOMAIN_FUNC_EN_N (0x1 << 0) -/* EXYNOS_DP_VIDEO_CTL_1 */ +/* ANALOGIX_DP_VIDEO_CTL_1 */ #define VIDEO_EN (0x1 << 7) #define HDCP_VIDEO_MUTE (0x1 << 6) -/* EXYNOS_DP_VIDEO_CTL_1 */ +/* ANALOGIX_DP_VIDEO_CTL_1 */ #define IN_D_RANGE_MASK (0x1 << 7) #define IN_D_RANGE_SHIFT (7) #define IN_D_RANGE_CEA (0x1 << 7) @@ -134,7 +142,7 @@ #define IN_COLOR_F_YCBCR422 (0x1 << 0) #define IN_COLOR_F_RGB (0x0 << 0) -/* EXYNOS_DP_VIDEO_CTL_3 */ +/* ANALOGIX_DP_VIDEO_CTL_3 */ #define IN_YC_COEFFI_MASK (0x1 << 7) #define IN_YC_COEFFI_SHIFT (7) #define IN_YC_COEFFI_ITU709 (0x1 << 7) @@ -144,17 +152,21 @@ #define VID_CHK_UPDATE_TYPE_1 (0x1 << 4) #define VID_CHK_UPDATE_TYPE_0 (0x0 << 4) -/* EXYNOS_DP_VIDEO_CTL_8 */ +/* ANALOGIX_DP_VIDEO_CTL_8 */ #define VID_HRES_TH(x) (((x) & 0xf) << 4) #define VID_VRES_TH(x) (((x) & 0xf) << 0) -/* EXYNOS_DP_VIDEO_CTL_10 */ +/* ANALOGIX_DP_VIDEO_CTL_10 */ #define FORMAT_SEL (0x1 << 4) #define INTERACE_SCAN_CFG (0x1 << 2) #define VSYNC_POLARITY_CFG (0x1 << 1) #define HSYNC_POLARITY_CFG (0x1 << 0) -/* EXYNOS_DP_LANE_MAP */ +/* ANALOGIX_DP_PLL_REG_1 */ +#define REF_CLK_24M (0x1 << 1) +#define REF_CLK_27M (0x0 << 1) + +/* ANALOGIX_DP_LANE_MAP */ #define LANE3_MAP_LOGIC_LANE_0 (0x0 << 6) #define LANE3_MAP_LOGIC_LANE_1 (0x1 << 6) #define LANE3_MAP_LOGIC_LANE_2 (0x2 << 6) @@ -172,30 +184,30 @@ #define LANE0_MAP_LOGIC_LANE_2 (0x2 << 0) #define LANE0_MAP_LOGIC_LANE_3 (0x3 << 0) -/* EXYNOS_DP_ANALOG_CTL_1 */ +/* ANALOGIX_DP_ANALOG_CTL_1 */ #define TX_TERMINAL_CTRL_50_OHM (0x1 << 4) -/* EXYNOS_DP_ANALOG_CTL_2 */ +/* ANALOGIX_DP_ANALOG_CTL_2 */ #define SEL_24M (0x1 << 3) #define TX_DVDD_BIT_1_0625V (0x4 << 0) -/* EXYNOS_DP_ANALOG_CTL_3 */ +/* ANALOGIX_DP_ANALOG_CTL_3 */ #define DRIVE_DVDD_BIT_1_0625V (0x4 << 5) #define VCO_BIT_600_MICRO (0x5 << 0) -/* EXYNOS_DP_PLL_FILTER_CTL_1 */ +/* ANALOGIX_DP_PLL_FILTER_CTL_1 */ #define PD_RING_OSC (0x1 << 6) #define AUX_TERMINAL_CTRL_50_OHM (0x2 << 4) #define TX_CUR1_2X (0x1 << 2) #define TX_CUR_16_MA (0x3 << 0) -/* EXYNOS_DP_TX_AMP_TUNING_CTL */ +/* ANALOGIX_DP_TX_AMP_TUNING_CTL */ #define CH3_AMP_400_MV (0x0 << 24) #define CH2_AMP_400_MV (0x0 << 16) #define CH1_AMP_400_MV (0x0 << 8) #define CH0_AMP_400_MV (0x0 << 0) -/* EXYNOS_DP_AUX_HW_RETRY_CTL */ +/* ANALOGIX_DP_AUX_HW_RETRY_CTL */ #define AUX_BIT_PERIOD_EXPECTED_DELAY(x) (((x) & 0x7) << 8) #define AUX_HW_RETRY_INTERVAL_MASK (0x3 << 3) #define AUX_HW_RETRY_INTERVAL_600_MICROSECONDS (0x0 << 3) @@ -204,7 +216,7 @@ #define AUX_HW_RETRY_INTERVAL_1800_MICROSECONDS (0x3 << 3) #define AUX_HW_RETRY_COUNT_SEL(x) (((x) & 0x7) << 0) -/* EXYNOS_DP_COMMON_INT_STA_1 */ +/* ANALOGIX_DP_COMMON_INT_STA_1 */ #define VSYNC_DET (0x1 << 7) #define PLL_LOCK_CHG (0x1 << 6) #define SPDIF_ERR (0x1 << 5) @@ -214,19 +226,19 @@ #define VID_CLK_CHG (0x1 << 1) #define SW_INT (0x1 << 0) -/* EXYNOS_DP_COMMON_INT_STA_2 */ +/* ANALOGIX_DP_COMMON_INT_STA_2 */ #define ENC_EN_CHG (0x1 << 6) #define HW_BKSV_RDY (0x1 << 3) #define HW_SHA_DONE (0x1 << 2) #define HW_AUTH_STATE_CHG (0x1 << 1) #define HW_AUTH_DONE (0x1 << 0) -/* EXYNOS_DP_COMMON_INT_STA_3 */ +/* ANALOGIX_DP_COMMON_INT_STA_3 */ #define AFIFO_UNDER (0x1 << 7) #define AFIFO_OVER (0x1 << 6) #define R0_CHK_FLAG (0x1 << 5) -/* EXYNOS_DP_COMMON_INT_STA_4 */ +/* ANALOGIX_DP_COMMON_INT_STA_4 */ #define PSR_ACTIVE (0x1 << 7) #define PSR_INACTIVE (0x1 << 6) #define SPDIF_BI_PHASE_ERR (0x1 << 5) @@ -234,29 +246,29 @@ #define HPD_LOST (0x1 << 1) #define PLUG (0x1 << 0) -/* EXYNOS_DP_INT_STA */ +/* ANALOGIX_DP_INT_STA */ #define INT_HPD (0x1 << 6) #define HW_TRAINING_FINISH (0x1 << 5) #define RPLY_RECEIV (0x1 << 1) #define AUX_ERR (0x1 << 0) -/* EXYNOS_DP_INT_CTL */ +/* ANALOGIX_DP_INT_CTL */ #define SOFT_INT_CTRL (0x1 << 2) #define INT_POL1 (0x1 << 1) #define INT_POL0 (0x1 << 0) -/* EXYNOS_DP_SYS_CTL_1 */ +/* ANALOGIX_DP_SYS_CTL_1 */ #define DET_STA (0x1 << 2) #define FORCE_DET (0x1 << 1) #define DET_CTRL (0x1 << 0) -/* EXYNOS_DP_SYS_CTL_2 */ +/* ANALOGIX_DP_SYS_CTL_2 */ #define CHA_CRI(x) (((x) & 0xf) << 4) #define CHA_STA (0x1 << 2) #define FORCE_CHA (0x1 << 1) #define CHA_CTRL (0x1 << 0) -/* EXYNOS_DP_SYS_CTL_3 */ +/* ANALOGIX_DP_SYS_CTL_3 */ #define HPD_STATUS (0x1 << 6) #define F_HPD (0x1 << 5) #define HPD_CTRL (0x1 << 4) @@ -265,13 +277,13 @@ #define F_VALID (0x1 << 1) #define VALID_CTRL (0x1 << 0) -/* EXYNOS_DP_SYS_CTL_4 */ +/* ANALOGIX_DP_SYS_CTL_4 */ #define FIX_M_AUD (0x1 << 4) #define ENHANCED (0x1 << 3) #define FIX_M_VID (0x1 << 2) #define M_VID_UPDATE_CTRL (0x3 << 0) -/* EXYNOS_DP_TRAINING_PTN_SET */ +/* ANALOGIX_DP_TRAINING_PTN_SET */ #define SCRAMBLER_TYPE (0x1 << 9) #define HW_LINK_TRAINING_PATTERN (0x1 << 8) #define SCRAMBLING_DISABLE (0x1 << 5) @@ -285,24 +297,24 @@ #define SW_TRAINING_PATTERN_SET_PTN1 (0x1 << 0) #define SW_TRAINING_PATTERN_SET_NORMAL (0x0 << 0) -/* EXYNOS_DP_LN0_LINK_TRAINING_CTL */ +/* ANALOGIX_DP_LN0_LINK_TRAINING_CTL */ #define PRE_EMPHASIS_SET_MASK (0x3 << 3) #define PRE_EMPHASIS_SET_SHIFT (3) -/* EXYNOS_DP_DEBUG_CTL */ +/* ANALOGIX_DP_DEBUG_CTL */ #define PLL_LOCK (0x1 << 4) #define F_PLL_LOCK (0x1 << 3) #define PLL_LOCK_CTRL (0x1 << 2) #define PN_INV (0x1 << 0) -/* EXYNOS_DP_PLL_CTL */ +/* ANALOGIX_DP_PLL_CTL */ #define DP_PLL_PD (0x1 << 7) #define DP_PLL_RESET (0x1 << 6) #define DP_PLL_LOOP_BIT_DEFAULT (0x1 << 4) #define DP_PLL_REF_BIT_1_1250V (0x5 << 0) #define DP_PLL_REF_BIT_1_2500V (0x7 << 0) -/* EXYNOS_DP_PHY_PD */ +/* ANALOGIX_DP_PHY_PD */ #define DP_PHY_PD (0x1 << 5) #define AUX_PD (0x1 << 4) #define CH3_PD (0x1 << 3) @@ -310,28 +322,28 @@ #define CH1_PD (0x1 << 1) #define CH0_PD (0x1 << 0) -/* EXYNOS_DP_PHY_TEST */ +/* ANALOGIX_DP_PHY_TEST */ #define MACRO_RST (0x1 << 5) #define CH1_TEST (0x1 << 1) #define CH0_TEST (0x1 << 0) -/* EXYNOS_DP_AUX_CH_STA */ +/* ANALOGIX_DP_AUX_CH_STA */ #define AUX_BUSY (0x1 << 4) #define AUX_STATUS_MASK (0xf << 0) -/* EXYNOS_DP_AUX_CH_DEFER_CTL */ +/* ANALOGIX_DP_AUX_CH_DEFER_CTL */ #define DEFER_CTRL_EN (0x1 << 7) #define DEFER_COUNT(x) (((x) & 0x7f) << 0) -/* EXYNOS_DP_AUX_RX_COMM */ +/* ANALOGIX_DP_AUX_RX_COMM */ #define AUX_RX_COMM_I2C_DEFER (0x2 << 2) #define AUX_RX_COMM_AUX_DEFER (0x2 << 0) -/* EXYNOS_DP_BUFFER_DATA_CTL */ +/* ANALOGIX_DP_BUFFER_DATA_CTL */ #define BUF_CLR (0x1 << 7) #define BUF_DATA_COUNT(x) (((x) & 0x1f) << 0) -/* EXYNOS_DP_AUX_CH_CTL_1 */ +/* ANALOGIX_DP_AUX_CH_CTL_1 */ #define AUX_LENGTH(x) (((x - 1) & 0xf) << 4) #define AUX_TX_COMM_MASK (0xf << 0) #define AUX_TX_COMM_DP_TRANSACTION (0x1 << 3) @@ -340,20 +352,20 @@ #define AUX_TX_COMM_WRITE (0x0 << 0) #define AUX_TX_COMM_READ (0x1 << 0) -/* EXYNOS_DP_AUX_ADDR_7_0 */ +/* ANALOGIX_DP_AUX_ADDR_7_0 */ #define AUX_ADDR_7_0(x) (((x) >> 0) & 0xff) -/* EXYNOS_DP_AUX_ADDR_15_8 */ +/* ANALOGIX_DP_AUX_ADDR_15_8 */ #define AUX_ADDR_15_8(x) (((x) >> 8) & 0xff) -/* EXYNOS_DP_AUX_ADDR_19_16 */ +/* ANALOGIX_DP_AUX_ADDR_19_16 */ #define AUX_ADDR_19_16(x) (((x) >> 16) & 0x0f) -/* EXYNOS_DP_AUX_CH_CTL_2 */ +/* ANALOGIX_DP_AUX_CH_CTL_2 */ #define ADDR_ONLY (0x1 << 1) #define AUX_EN (0x1 << 0) -/* EXYNOS_DP_SOC_GENERAL_CTL */ +/* ANALOGIX_DP_SOC_GENERAL_CTL */ #define AUDIO_MODE_SPDIF_MODE (0x1 << 8) #define AUDIO_MODE_MASTER_MODE (0x0 << 8) #define MASTER_VIDEO_INTERLACE_EN (0x1 << 4) @@ -363,4 +375,4 @@ #define VIDEO_MODE_SLAVE_MODE (0x1 << 0) #define VIDEO_MODE_MASTER_MODE (0x0 << 0) -#endif /* _EXYNOS_DP_REG_H */ +#endif /* _ANALOGIX_DP_REG_H */ diff --git a/drivers/gpu/drm/bridge/dw-hdmi.c b/drivers/gpu/drm/bridge/dw-hdmi.c index 9795b72472ba..c9d941283d30 100644 --- a/drivers/gpu/drm/bridge/dw-hdmi.c +++ b/drivers/gpu/drm/bridge/dw-hdmi.c @@ -1413,11 +1413,6 @@ static void dw_hdmi_bridge_enable(struct drm_bridge *bridge) mutex_unlock(&hdmi->mutex); } -static void dw_hdmi_bridge_nop(struct drm_bridge *bridge) -{ - /* do nothing */ -} - static enum drm_connector_status dw_hdmi_connector_detect(struct drm_connector *connector, bool force) { @@ -1536,8 +1531,6 @@ static const struct drm_connector_helper_funcs dw_hdmi_connector_helper_funcs = static const struct drm_bridge_funcs dw_hdmi_bridge_funcs = { .enable = dw_hdmi_bridge_enable, .disable = dw_hdmi_bridge_disable, - .pre_enable = dw_hdmi_bridge_nop, - .post_disable = dw_hdmi_bridge_nop, .mode_set = dw_hdmi_bridge_mode_set, }; diff --git a/drivers/gpu/drm/cirrus/cirrus_drv.c b/drivers/gpu/drm/cirrus/cirrus_drv.c index 7bc394ec9fb3..dc83f69da6f1 100644 --- a/drivers/gpu/drm/cirrus/cirrus_drv.c +++ b/drivers/gpu/drm/cirrus/cirrus_drv.c @@ -163,10 +163,8 @@ static struct pci_driver cirrus_pci_driver = { static int __init cirrus_init(void) { -#ifdef CONFIG_VGA_CONSOLE if (vgacon_text_force() && cirrus_modeset == -1) return -EINVAL; -#endif if (cirrus_modeset == 0) return -EINVAL; diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c index 4befe25c81c7..d25abce0450f 100644 --- a/drivers/gpu/drm/drm_atomic_helper.c +++ b/drivers/gpu/drm/drm_atomic_helper.c @@ -984,7 +984,17 @@ void drm_atomic_helper_commit_modeset_enables(struct drm_device *dev, } EXPORT_SYMBOL(drm_atomic_helper_commit_modeset_enables); -static void wait_for_fences(struct drm_device *dev, +/** + * drm_atomic_helper_wait_for_fences - wait for fences stashed in plane state + * @dev: DRM device + * @state: atomic state object with old state structures + * + * For implicit sync, driver should fish the exclusive fence out from the + * incoming fb's and stash it in the drm_plane_state. This is called after + * drm_atomic_helper_swap_state() so it uses the current plane state (and + * just uses the atomic state to find the changed planes) + */ +void drm_atomic_helper_wait_for_fences(struct drm_device *dev, struct drm_atomic_state *state) { struct drm_plane *plane; @@ -1002,6 +1012,7 @@ static void wait_for_fences(struct drm_device *dev, plane->state->fence = NULL; } } +EXPORT_SYMBOL(drm_atomic_helper_wait_for_fences); /** * drm_atomic_helper_framebuffer_changed - check if framebuffer has changed @@ -1092,6 +1103,8 @@ drm_atomic_helper_wait_for_vblanks(struct drm_device *dev, drm_crtc_vblank_count(crtc), msecs_to_jiffies(50)); + WARN(!ret, "[CRTC:%d] vblank wait timed out\n", crtc->base.id); + drm_crtc_vblank_put(crtc); } } @@ -1163,7 +1176,7 @@ int drm_atomic_helper_commit(struct drm_device *dev, * current layout. */ - wait_for_fences(dev, state); + drm_atomic_helper_wait_for_fences(dev, state); drm_atomic_helper_commit_modeset_disables(dev, state); @@ -2497,12 +2510,9 @@ EXPORT_SYMBOL(drm_atomic_helper_connector_dpms); */ void drm_atomic_helper_crtc_reset(struct drm_crtc *crtc) { - if (crtc->state) { - drm_property_unreference_blob(crtc->state->mode_blob); - drm_property_unreference_blob(crtc->state->degamma_lut); - drm_property_unreference_blob(crtc->state->ctm); - drm_property_unreference_blob(crtc->state->gamma_lut); - } + if (crtc->state) + __drm_atomic_helper_crtc_destroy_state(crtc, crtc->state); + kfree(crtc->state); crtc->state = kzalloc(sizeof(*crtc->state), GFP_KERNEL); @@ -2608,8 +2618,8 @@ EXPORT_SYMBOL(drm_atomic_helper_crtc_destroy_state); */ void drm_atomic_helper_plane_reset(struct drm_plane *plane) { - if (plane->state && plane->state->fb) - drm_framebuffer_unreference(plane->state->fb); + if (plane->state) + __drm_atomic_helper_plane_destroy_state(plane, plane->state); kfree(plane->state); plane->state = kzalloc(sizeof(*plane->state), GFP_KERNEL); @@ -2730,6 +2740,10 @@ void drm_atomic_helper_connector_reset(struct drm_connector *connector) struct drm_connector_state *conn_state = kzalloc(sizeof(*conn_state), GFP_KERNEL); + if (connector->state) + __drm_atomic_helper_connector_destroy_state(connector, + connector->state); + kfree(connector->state); __drm_atomic_helper_connector_reset(connector, conn_state); } diff --git a/drivers/gpu/drm/drm_crtc.c b/drivers/gpu/drm/drm_crtc.c index e08f962288d9..4e5b015a5e3a 100644 --- a/drivers/gpu/drm/drm_crtc.c +++ b/drivers/gpu/drm/drm_crtc.c @@ -275,7 +275,8 @@ EXPORT_SYMBOL(drm_get_format_name); static int drm_mode_object_get_reg(struct drm_device *dev, struct drm_mode_object *obj, uint32_t obj_type, - bool register_obj) + bool register_obj, + void (*obj_free_cb)(struct kref *kref)) { int ret; @@ -288,6 +289,10 @@ static int drm_mode_object_get_reg(struct drm_device *dev, */ obj->id = ret; obj->type = obj_type; + if (obj_free_cb) { + obj->free_cb = obj_free_cb; + kref_init(&obj->refcount); + } } mutex_unlock(&dev->mode_config.idr_mutex); @@ -311,7 +316,7 @@ static int drm_mode_object_get_reg(struct drm_device *dev, int drm_mode_object_get(struct drm_device *dev, struct drm_mode_object *obj, uint32_t obj_type) { - return drm_mode_object_get_reg(dev, obj, obj_type, true); + return drm_mode_object_get_reg(dev, obj, obj_type, true, NULL); } static void drm_mode_object_register(struct drm_device *dev, @@ -323,19 +328,24 @@ static void drm_mode_object_register(struct drm_device *dev, } /** - * drm_mode_object_put - free a modeset identifer + * drm_mode_object_unregister - free a modeset identifer * @dev: DRM device * @object: object to free * - * Free @id from @dev's unique identifier pool. Note that despite the _get - * postfix modeset identifiers are _not_ reference counted. Hence don't use this + * Free @id from @dev's unique identifier pool. + * This function can be called multiple times, and guards against + * multiple removals. + * These modeset identifiers are _not_ reference counted. Hence don't use this * for reference counted modeset objects like framebuffers. */ -void drm_mode_object_put(struct drm_device *dev, +void drm_mode_object_unregister(struct drm_device *dev, struct drm_mode_object *object) { mutex_lock(&dev->mode_config.idr_mutex); - idr_remove(&dev->mode_config.crtc_idr, object->id); + if (object->id) { + idr_remove(&dev->mode_config.crtc_idr, object->id); + object->id = 0; + } mutex_unlock(&dev->mode_config.idr_mutex); } @@ -350,11 +360,11 @@ static struct drm_mode_object *_object_find(struct drm_device *dev, obj = NULL; if (obj && obj->id != id) obj = NULL; - /* don't leak out unref'd fb's */ - if (obj && - (obj->type == DRM_MODE_OBJECT_FB || - obj->type == DRM_MODE_OBJECT_BLOB)) - obj = NULL; + + if (obj && obj->free_cb) { + if (!kref_get_unless_zero(&obj->refcount)) + obj = NULL; + } mutex_unlock(&dev->mode_config.idr_mutex); return obj; @@ -366,25 +376,70 @@ static struct drm_mode_object *_object_find(struct drm_device *dev, * @id: id of the mode object * @type: type of the mode object * - * Note that framebuffers cannot be looked up with this functions - since those - * are reference counted, they need special treatment. Even with - * DRM_MODE_OBJECT_ANY (although that will simply return NULL - * rather than WARN_ON()). + * This function is used to look up a modeset object. It will acquire a + * reference for reference counted objects. This reference must be dropped again + * by callind drm_mode_object_unreference(). */ struct drm_mode_object *drm_mode_object_find(struct drm_device *dev, uint32_t id, uint32_t type) { struct drm_mode_object *obj = NULL; - /* Framebuffers are reference counted and need their own lookup - * function.*/ - WARN_ON(type == DRM_MODE_OBJECT_FB || type == DRM_MODE_OBJECT_BLOB); obj = _object_find(dev, id, type); return obj; } EXPORT_SYMBOL(drm_mode_object_find); /** + * drm_mode_object_unreference - decr the object refcnt + * @obj: mode_object + * + * This functions decrements the object's refcount if it is a refcounted modeset + * object. It is a no-op on any other object. This is used to drop references + * acquired with drm_mode_object_reference(). + */ +void drm_mode_object_unreference(struct drm_mode_object *obj) +{ + if (obj->free_cb) { + DRM_DEBUG("OBJ ID: %d (%d)\n", obj->id, atomic_read(&obj->refcount.refcount)); + kref_put(&obj->refcount, obj->free_cb); + } +} +EXPORT_SYMBOL(drm_mode_object_unreference); + +/** + * drm_mode_object_reference - incr the object refcnt + * @obj: mode_object + * + * This functions increments the object's refcount if it is a refcounted modeset + * object. It is a no-op on any other object. References should be dropped again + * by calling drm_mode_object_unreference(). + */ +void drm_mode_object_reference(struct drm_mode_object *obj) +{ + if (obj->free_cb) { + DRM_DEBUG("OBJ ID: %d (%d)\n", obj->id, atomic_read(&obj->refcount.refcount)); + kref_get(&obj->refcount); + } +} +EXPORT_SYMBOL(drm_mode_object_reference); + +static void drm_framebuffer_free(struct kref *kref) +{ + struct drm_framebuffer *fb = + container_of(kref, struct drm_framebuffer, base.refcount); + struct drm_device *dev = fb->dev; + + /* + * The lookup idr holds a weak reference, which has not necessarily been + * removed at this point. Check for that. + */ + drm_mode_object_unregister(dev, &fb->base); + + fb->funcs->destroy(fb); +} + +/** * drm_framebuffer_init - initialize a framebuffer * @dev: DRM device * @fb: framebuffer to be initialized @@ -407,71 +462,26 @@ int drm_framebuffer_init(struct drm_device *dev, struct drm_framebuffer *fb, { int ret; - mutex_lock(&dev->mode_config.fb_lock); - kref_init(&fb->refcount); INIT_LIST_HEAD(&fb->filp_head); fb->dev = dev; fb->funcs = funcs; - ret = drm_mode_object_get(dev, &fb->base, DRM_MODE_OBJECT_FB); + ret = drm_mode_object_get_reg(dev, &fb->base, DRM_MODE_OBJECT_FB, + false, drm_framebuffer_free); if (ret) goto out; + mutex_lock(&dev->mode_config.fb_lock); dev->mode_config.num_fb++; list_add(&fb->head, &dev->mode_config.fb_list); -out: mutex_unlock(&dev->mode_config.fb_lock); + drm_mode_object_register(dev, &fb->base); +out: return ret; } EXPORT_SYMBOL(drm_framebuffer_init); -/* dev->mode_config.fb_lock must be held! */ -static void __drm_framebuffer_unregister(struct drm_device *dev, - struct drm_framebuffer *fb) -{ - drm_mode_object_put(dev, &fb->base); - - fb->base.id = 0; -} - -static void drm_framebuffer_free(struct kref *kref) -{ - struct drm_framebuffer *fb = - container_of(kref, struct drm_framebuffer, refcount); - struct drm_device *dev = fb->dev; - - /* - * The lookup idr holds a weak reference, which has not necessarily been - * removed at this point. Check for that. - */ - mutex_lock(&dev->mode_config.fb_lock); - if (fb->base.id) { - /* Mark fb as reaped and drop idr ref. */ - __drm_framebuffer_unregister(dev, fb); - } - mutex_unlock(&dev->mode_config.fb_lock); - - fb->funcs->destroy(fb); -} - -static struct drm_framebuffer *__drm_framebuffer_lookup(struct drm_device *dev, - uint32_t id) -{ - struct drm_mode_object *obj = NULL; - struct drm_framebuffer *fb; - - mutex_lock(&dev->mode_config.idr_mutex); - obj = idr_find(&dev->mode_config.crtc_idr, id); - if (!obj || (obj->type != DRM_MODE_OBJECT_FB) || (obj->id != id)) - fb = NULL; - else - fb = obj_to_fb(obj); - mutex_unlock(&dev->mode_config.idr_mutex); - - return fb; -} - /** * drm_framebuffer_lookup - look up a drm framebuffer and grab a reference * @dev: drm device @@ -484,47 +494,17 @@ static struct drm_framebuffer *__drm_framebuffer_lookup(struct drm_device *dev, struct drm_framebuffer *drm_framebuffer_lookup(struct drm_device *dev, uint32_t id) { - struct drm_framebuffer *fb; - - mutex_lock(&dev->mode_config.fb_lock); - fb = __drm_framebuffer_lookup(dev, id); - if (fb) { - if (!kref_get_unless_zero(&fb->refcount)) - fb = NULL; - } - mutex_unlock(&dev->mode_config.fb_lock); + struct drm_mode_object *obj; + struct drm_framebuffer *fb = NULL; + obj = _object_find(dev, id, DRM_MODE_OBJECT_FB); + if (obj) + fb = obj_to_fb(obj); return fb; } EXPORT_SYMBOL(drm_framebuffer_lookup); /** - * drm_framebuffer_unreference - unref a framebuffer - * @fb: framebuffer to unref - * - * This functions decrements the fb's refcount and frees it if it drops to zero. - */ -void drm_framebuffer_unreference(struct drm_framebuffer *fb) -{ - DRM_DEBUG("%p: FB ID: %d (%d)\n", fb, fb->base.id, atomic_read(&fb->refcount.refcount)); - kref_put(&fb->refcount, drm_framebuffer_free); -} -EXPORT_SYMBOL(drm_framebuffer_unreference); - -/** - * drm_framebuffer_reference - incr the fb refcnt - * @fb: framebuffer - * - * This functions increments the fb's refcount. - */ -void drm_framebuffer_reference(struct drm_framebuffer *fb) -{ - DRM_DEBUG("%p: FB ID: %d (%d)\n", fb, fb->base.id, atomic_read(&fb->refcount.refcount)); - kref_get(&fb->refcount); -} -EXPORT_SYMBOL(drm_framebuffer_reference); - -/** * drm_framebuffer_unregister_private - unregister a private fb from the lookup idr * @fb: fb to unregister * @@ -542,10 +522,8 @@ void drm_framebuffer_unregister_private(struct drm_framebuffer *fb) dev = fb->dev; - mutex_lock(&dev->mode_config.fb_lock); /* Mark fb as reaped and drop idr ref. */ - __drm_framebuffer_unregister(dev, fb); - mutex_unlock(&dev->mode_config.fb_lock); + drm_mode_object_unregister(dev, &fb->base); } EXPORT_SYMBOL(drm_framebuffer_unregister_private); @@ -619,7 +597,7 @@ void drm_framebuffer_remove(struct drm_framebuffer *fb) * in-use fb with fb-id == 0. Userspace is allowed to shoot its own foot * in this manner. */ - if (atomic_read(&fb->refcount.refcount) > 1) { + if (drm_framebuffer_read_refcount(fb) > 1) { drm_modeset_lock_all(dev); /* remove from any CRTC */ drm_for_each_crtc(crtc, dev) { @@ -705,7 +683,7 @@ int drm_crtc_init_with_planes(struct drm_device *dev, struct drm_crtc *crtc, drm_num_crtcs(dev)); } if (!crtc->name) { - drm_mode_object_put(dev, &crtc->base); + drm_mode_object_unregister(dev, &crtc->base); return -ENOMEM; } @@ -747,7 +725,7 @@ void drm_crtc_cleanup(struct drm_crtc *crtc) drm_modeset_lock_fini(&crtc->mutex); - drm_mode_object_put(dev, &crtc->base); + drm_mode_object_unregister(dev, &crtc->base); list_del(&crtc->head); dev->mode_config.num_crtc--; @@ -909,7 +887,7 @@ int drm_connector_init(struct drm_device *dev, drm_modeset_lock_all(dev); - ret = drm_mode_object_get_reg(dev, &connector->base, DRM_MODE_OBJECT_CONNECTOR, false); + ret = drm_mode_object_get_reg(dev, &connector->base, DRM_MODE_OBJECT_CONNECTOR, false, NULL); if (ret) goto out_unlock; @@ -972,7 +950,7 @@ out_put_id: ida_remove(&config->connector_ida, connector->connector_id); out_put: if (ret) - drm_mode_object_put(dev, &connector->base); + drm_mode_object_unregister(dev, &connector->base); out_unlock: drm_modeset_unlock_all(dev); @@ -1010,7 +988,7 @@ void drm_connector_cleanup(struct drm_connector *connector) connector->connector_id); kfree(connector->display_info.bus_formats); - drm_mode_object_put(dev, &connector->base); + drm_mode_object_unregister(dev, &connector->base); kfree(connector->name); connector->name = NULL; list_del(&connector->head); @@ -1067,25 +1045,65 @@ void drm_connector_unregister(struct drm_connector *connector) } EXPORT_SYMBOL(drm_connector_unregister); +/** + * drm_connector_register_all - register all connectors + * @dev: drm device + * + * This function registers all connectors in sysfs and other places so that + * userspace can start to access them. Drivers can call it after calling + * drm_dev_register() to complete the device registration, if they don't call + * drm_connector_register() on each connector individually. + * + * When a device is unplugged and should be removed from userspace access, + * call drm_connector_unregister_all(), which is the inverse of this + * function. + * + * Returns: + * Zero on success, error code on failure. + */ +int drm_connector_register_all(struct drm_device *dev) +{ + struct drm_connector *connector; + int ret; + + mutex_lock(&dev->mode_config.mutex); + + drm_for_each_connector(connector, dev) { + ret = drm_connector_register(connector); + if (ret) + goto err; + } + + mutex_unlock(&dev->mode_config.mutex); + + return 0; + +err: + mutex_unlock(&dev->mode_config.mutex); + drm_connector_unregister_all(dev); + return ret; +} +EXPORT_SYMBOL(drm_connector_register_all); /** - * drm_connector_unplug_all - unregister connector userspace interfaces + * drm_connector_unregister_all - unregister connector userspace interfaces * @dev: drm device * - * This function unregisters all connector userspace interfaces in sysfs. Should - * be call when the device is disconnected, e.g. from an usb driver's - * ->disconnect callback. + * This functions unregisters all connectors from sysfs and other places so + * that userspace can no longer access them. Drivers should call this as the + * first step tearing down the device instace, or when the underlying + * physical device disappeared (e.g. USB unplug), right before calling + * drm_dev_unregister(). */ -void drm_connector_unplug_all(struct drm_device *dev) +void drm_connector_unregister_all(struct drm_device *dev) { struct drm_connector *connector; /* FIXME: taking the mode config mutex ends up in a clash with sysfs */ list_for_each_entry(connector, &dev->mode_config.connector_list, head) drm_connector_unregister(connector); - } -EXPORT_SYMBOL(drm_connector_unplug_all); +EXPORT_SYMBOL(drm_connector_unregister_all); /** * drm_encoder_init - Init a preallocated encoder @@ -1138,7 +1156,7 @@ int drm_encoder_init(struct drm_device *dev, out_put: if (ret) - drm_mode_object_put(dev, &encoder->base); + drm_mode_object_unregister(dev, &encoder->base); out_unlock: drm_modeset_unlock_all(dev); @@ -1181,7 +1199,7 @@ void drm_encoder_cleanup(struct drm_encoder *encoder) struct drm_device *dev = encoder->dev; drm_modeset_lock_all(dev); - drm_mode_object_put(dev, &encoder->base); + drm_mode_object_unregister(dev, &encoder->base); kfree(encoder->name); list_del(&encoder->head); dev->mode_config.num_encoder--; @@ -1242,7 +1260,7 @@ int drm_universal_plane_init(struct drm_device *dev, struct drm_plane *plane, GFP_KERNEL); if (!plane->format_types) { DRM_DEBUG_KMS("out of memory when allocating plane\n"); - drm_mode_object_put(dev, &plane->base); + drm_mode_object_unregister(dev, &plane->base); return -ENOMEM; } @@ -1258,7 +1276,7 @@ int drm_universal_plane_init(struct drm_device *dev, struct drm_plane *plane, } if (!plane->name) { kfree(plane->format_types); - drm_mode_object_put(dev, &plane->base); + drm_mode_object_unregister(dev, &plane->base); return -ENOMEM; } @@ -1338,7 +1356,7 @@ void drm_plane_cleanup(struct drm_plane *plane) drm_modeset_lock_all(dev); kfree(plane->format_types); - drm_mode_object_put(dev, &plane->base); + drm_mode_object_unregister(dev, &plane->base); BUG_ON(list_empty(&plane->head)); @@ -3423,11 +3441,11 @@ int drm_mode_addfb2(struct drm_device *dev, if (IS_ERR(fb)) return PTR_ERR(fb); - /* Transfer ownership to the filp for reaping on close */ - DRM_DEBUG_KMS("[FB:%d]\n", fb->base.id); - mutex_lock(&file_priv->fbs_lock); r->fb_id = fb->base.id; + + /* Transfer ownership to the filp for reaping on close */ + mutex_lock(&file_priv->fbs_lock); list_add(&fb->filp_head, &file_priv->fbs); mutex_unlock(&file_priv->fbs_lock); @@ -3458,30 +3476,32 @@ int drm_mode_rmfb(struct drm_device *dev, if (!drm_core_check_feature(dev, DRIVER_MODESET)) return -EINVAL; - mutex_lock(&file_priv->fbs_lock); - mutex_lock(&dev->mode_config.fb_lock); - fb = __drm_framebuffer_lookup(dev, *id); + fb = drm_framebuffer_lookup(dev, *id); if (!fb) - goto fail_lookup; + return -ENOENT; + mutex_lock(&file_priv->fbs_lock); list_for_each_entry(fbl, &file_priv->fbs, filp_head) if (fb == fbl) found = 1; - if (!found) - goto fail_lookup; + if (!found) { + mutex_unlock(&file_priv->fbs_lock); + goto fail_unref; + } list_del_init(&fb->filp_head); - mutex_unlock(&dev->mode_config.fb_lock); mutex_unlock(&file_priv->fbs_lock); + /* we now own the reference that was stored in the fbs list */ drm_framebuffer_unreference(fb); - return 0; + /* drop the reference we picked up in framebuffer lookup */ + drm_framebuffer_unreference(fb); -fail_lookup: - mutex_unlock(&dev->mode_config.fb_lock); - mutex_unlock(&file_priv->fbs_lock); + return 0; +fail_unref: + drm_framebuffer_unreference(fb); return -ENOENT; } @@ -4029,7 +4049,7 @@ void drm_property_destroy(struct drm_device *dev, struct drm_property *property) if (property->num_values) kfree(property->values); - drm_mode_object_put(dev, &property->base); + drm_mode_object_unregister(dev, &property->base); list_del(&property->head); kfree(property); } @@ -4234,6 +4254,20 @@ done: return ret; } +static void drm_property_free_blob(struct kref *kref) +{ + struct drm_property_blob *blob = + container_of(kref, struct drm_property_blob, base.refcount); + + mutex_lock(&blob->dev->mode_config.blob_lock); + list_del(&blob->head_global); + mutex_unlock(&blob->dev->mode_config.blob_lock); + + drm_mode_object_unregister(blob->dev, &blob->base); + + kfree(blob); +} + /** * drm_property_create_blob - Create new blob property * @@ -4271,20 +4305,16 @@ drm_property_create_blob(struct drm_device *dev, size_t length, if (data) memcpy(blob->data, data, length); - mutex_lock(&dev->mode_config.blob_lock); - - ret = drm_mode_object_get(dev, &blob->base, DRM_MODE_OBJECT_BLOB); + ret = drm_mode_object_get_reg(dev, &blob->base, DRM_MODE_OBJECT_BLOB, + true, drm_property_free_blob); if (ret) { kfree(blob); - mutex_unlock(&dev->mode_config.blob_lock); return ERR_PTR(-EINVAL); } - kref_init(&blob->refcount); - + mutex_lock(&dev->mode_config.blob_lock); list_add_tail(&blob->head_global, &dev->mode_config.property_blob_list); - mutex_unlock(&dev->mode_config.blob_lock); return blob; @@ -4292,27 +4322,6 @@ drm_property_create_blob(struct drm_device *dev, size_t length, EXPORT_SYMBOL(drm_property_create_blob); /** - * drm_property_free_blob - Blob property destructor - * - * Internal free function for blob properties; must not be used directly. - * - * @kref: Reference - */ -static void drm_property_free_blob(struct kref *kref) -{ - struct drm_property_blob *blob = - container_of(kref, struct drm_property_blob, refcount); - - WARN_ON(!mutex_is_locked(&blob->dev->mode_config.blob_lock)); - - list_del(&blob->head_global); - list_del(&blob->head_file); - drm_mode_object_put(blob->dev, &blob->base); - - kfree(blob); -} - -/** * drm_property_unreference_blob - Unreference a blob property * * Drop a reference on a blob property. May free the object. @@ -4321,42 +4330,14 @@ static void drm_property_free_blob(struct kref *kref) */ void drm_property_unreference_blob(struct drm_property_blob *blob) { - struct drm_device *dev; - if (!blob) return; - dev = blob->dev; - - DRM_DEBUG("%p: blob ID: %d (%d)\n", blob, blob->base.id, atomic_read(&blob->refcount.refcount)); - - if (kref_put_mutex(&blob->refcount, drm_property_free_blob, - &dev->mode_config.blob_lock)) - mutex_unlock(&dev->mode_config.blob_lock); - else - might_lock(&dev->mode_config.blob_lock); + drm_mode_object_unreference(&blob->base); } EXPORT_SYMBOL(drm_property_unreference_blob); /** - * drm_property_unreference_blob_locked - Unreference a blob property with blob_lock held - * - * Drop a reference on a blob property. May free the object. This must be - * called with blob_lock held. - * - * @blob: Pointer to blob property - */ -static void drm_property_unreference_blob_locked(struct drm_property_blob *blob) -{ - if (!blob) - return; - - DRM_DEBUG("%p: blob ID: %d (%d)\n", blob, blob->base.id, atomic_read(&blob->refcount.refcount)); - - kref_put(&blob->refcount, drm_property_free_blob); -} - -/** * drm_property_destroy_user_blobs - destroy all blobs created by this client * @dev: DRM device * @file_priv: destroy all blobs owned by this file handle @@ -4366,14 +4347,14 @@ void drm_property_destroy_user_blobs(struct drm_device *dev, { struct drm_property_blob *blob, *bt; - mutex_lock(&dev->mode_config.blob_lock); - + /* + * When the file gets released that means no one else can access the + * blob list any more, so no need to grab dev->blob_lock. + */ list_for_each_entry_safe(blob, bt, &file_priv->blobs, head_file) { list_del_init(&blob->head_file); - drm_property_unreference_blob_locked(blob); + drm_property_unreference_blob(blob); } - - mutex_unlock(&dev->mode_config.blob_lock); } /** @@ -4385,35 +4366,11 @@ void drm_property_destroy_user_blobs(struct drm_device *dev, */ struct drm_property_blob *drm_property_reference_blob(struct drm_property_blob *blob) { - DRM_DEBUG("%p: blob ID: %d (%d)\n", blob, blob->base.id, atomic_read(&blob->refcount.refcount)); - kref_get(&blob->refcount); + drm_mode_object_reference(&blob->base); return blob; } EXPORT_SYMBOL(drm_property_reference_blob); -/* - * Like drm_property_lookup_blob, but does not return an additional reference. - * Must be called with blob_lock held. - */ -static struct drm_property_blob *__drm_property_lookup_blob(struct drm_device *dev, - uint32_t id) -{ - struct drm_mode_object *obj = NULL; - struct drm_property_blob *blob; - - WARN_ON(!mutex_is_locked(&dev->mode_config.blob_lock)); - - mutex_lock(&dev->mode_config.idr_mutex); - obj = idr_find(&dev->mode_config.crtc_idr, id); - if (!obj || (obj->type != DRM_MODE_OBJECT_BLOB) || (obj->id != id)) - blob = NULL; - else - blob = obj_to_blob(obj); - mutex_unlock(&dev->mode_config.idr_mutex); - - return blob; -} - /** * drm_property_lookup_blob - look up a blob property and take a reference * @dev: drm device @@ -4426,16 +4383,12 @@ static struct drm_property_blob *__drm_property_lookup_blob(struct drm_device *d struct drm_property_blob *drm_property_lookup_blob(struct drm_device *dev, uint32_t id) { - struct drm_property_blob *blob; - - mutex_lock(&dev->mode_config.blob_lock); - blob = __drm_property_lookup_blob(dev, id); - if (blob) { - if (!kref_get_unless_zero(&blob->refcount)) - blob = NULL; - } - mutex_unlock(&dev->mode_config.blob_lock); + struct drm_mode_object *obj; + struct drm_property_blob *blob = NULL; + obj = _object_find(dev, id, DRM_MODE_OBJECT_BLOB); + if (obj) + blob = obj_to_blob(obj); return blob; } EXPORT_SYMBOL(drm_property_lookup_blob); @@ -4540,26 +4493,21 @@ int drm_mode_getblob_ioctl(struct drm_device *dev, if (!drm_core_check_feature(dev, DRIVER_MODESET)) return -EINVAL; - drm_modeset_lock_all(dev); - mutex_lock(&dev->mode_config.blob_lock); - blob = __drm_property_lookup_blob(dev, out_resp->blob_id); - if (!blob) { - ret = -ENOENT; - goto done; - } + blob = drm_property_lookup_blob(dev, out_resp->blob_id); + if (!blob) + return -ENOENT; if (out_resp->length == blob->length) { blob_ptr = (void __user *)(unsigned long)out_resp->data; if (copy_to_user(blob_ptr, blob->data, blob->length)) { ret = -EFAULT; - goto done; + goto unref; } } out_resp->length = blob->length; +unref: + drm_property_unreference_blob(blob); -done: - mutex_unlock(&dev->mode_config.blob_lock); - drm_modeset_unlock_all(dev); return ret; } @@ -4638,13 +4586,11 @@ int drm_mode_destroyblob_ioctl(struct drm_device *dev, if (!drm_core_check_feature(dev, DRIVER_MODESET)) return -EINVAL; - mutex_lock(&dev->mode_config.blob_lock); - blob = __drm_property_lookup_blob(dev, out_resp->blob_id); - if (!blob) { - ret = -ENOENT; - goto err; - } + blob = drm_property_lookup_blob(dev, out_resp->blob_id); + if (!blob) + return -ENOENT; + mutex_lock(&dev->mode_config.blob_lock); /* Ensure the property was actually created by this user. */ list_for_each_entry(bt, &file_priv->blobs, head_file) { if (bt == blob) { @@ -4661,13 +4607,18 @@ int drm_mode_destroyblob_ioctl(struct drm_device *dev, /* We must drop head_file here, because we may not be the last * reference on the blob. */ list_del_init(&blob->head_file); - drm_property_unreference_blob_locked(blob); mutex_unlock(&dev->mode_config.blob_lock); + /* One reference from lookup, and one from the filp. */ + drm_property_unreference_blob(blob); + drm_property_unreference_blob(blob); + return 0; err: mutex_unlock(&dev->mode_config.blob_lock); + drm_property_unreference_blob(blob); + return ret; } @@ -4831,19 +4782,7 @@ bool drm_property_change_valid_get(struct drm_property *property, if (value == 0) return true; - /* handle refcnt'd objects specially: */ - if (property->values[0] == DRM_MODE_OBJECT_FB) { - struct drm_framebuffer *fb; - fb = drm_framebuffer_lookup(property->dev, value); - if (fb) { - *ref = &fb->base; - return true; - } else { - return false; - } - } else { - return _object_find(property->dev, value, property->values[0]) != NULL; - } + return _object_find(property->dev, value, property->values[0]) != NULL; } for (i = 0; i < property->num_values; i++) @@ -4859,8 +4798,7 @@ void drm_property_change_valid_put(struct drm_property *property, return; if (drm_property_type_is(property, DRM_MODE_PROP_OBJECT)) { - if (property->values[0] == DRM_MODE_OBJECT_FB) - drm_framebuffer_unreference(obj_to_fb(ref)); + drm_mode_object_unreference(ref); } else if (drm_property_type_is(property, DRM_MODE_PROP_BLOB)) drm_property_unreference_blob(obj_to_blob(ref)); } @@ -4991,7 +4929,7 @@ int drm_mode_obj_get_properties_ioctl(struct drm_device *dev, void *data, } if (!obj->properties) { ret = -EINVAL; - goto out; + goto out_unref; } ret = get_properties(obj, file_priv->atomic, @@ -4999,6 +4937,8 @@ int drm_mode_obj_get_properties_ioctl(struct drm_device *dev, void *data, (uint64_t __user *)(unsigned long)(arg->prop_values_ptr), &arg->count_props); +out_unref: + drm_mode_object_unreference(obj); out: drm_modeset_unlock_all(dev); return ret; @@ -5041,20 +4981,20 @@ int drm_mode_obj_set_property_ioctl(struct drm_device *dev, void *data, goto out; } if (!arg_obj->properties) - goto out; + goto out_unref; for (i = 0; i < arg_obj->properties->count; i++) if (arg_obj->properties->properties[i]->base.id == arg->prop_id) break; if (i == arg_obj->properties->count) - goto out; + goto out_unref; prop_obj = drm_mode_object_find(dev, arg->prop_id, DRM_MODE_OBJECT_PROPERTY); if (!prop_obj) { ret = -ENOENT; - goto out; + goto out_unref; } property = obj_to_property(prop_obj); @@ -5077,6 +5017,8 @@ int drm_mode_obj_set_property_ioctl(struct drm_device *dev, void *data, drm_property_change_valid_put(property, ref); +out_unref: + drm_mode_object_unreference(arg_obj); out: drm_modeset_unlock_all(dev); return ret; @@ -5914,6 +5856,15 @@ void drm_mode_config_cleanup(struct drm_device *dev) drm_property_destroy(dev, property); } + list_for_each_entry_safe(plane, plt, &dev->mode_config.plane_list, + head) { + plane->funcs->destroy(plane); + } + + list_for_each_entry_safe(crtc, ct, &dev->mode_config.crtc_list, head) { + crtc->funcs->destroy(crtc); + } + list_for_each_entry_safe(blob, bt, &dev->mode_config.property_blob_list, head_global) { drm_property_unreference_blob(blob); @@ -5929,16 +5880,7 @@ void drm_mode_config_cleanup(struct drm_device *dev) */ WARN_ON(!list_empty(&dev->mode_config.fb_list)); list_for_each_entry_safe(fb, fbt, &dev->mode_config.fb_list, head) { - drm_framebuffer_free(&fb->refcount); - } - - list_for_each_entry_safe(plane, plt, &dev->mode_config.plane_list, - head) { - plane->funcs->destroy(plane); - } - - list_for_each_entry_safe(crtc, ct, &dev->mode_config.crtc_list, head) { - crtc->funcs->destroy(crtc); + drm_framebuffer_free(&fb->base.refcount); } ida_destroy(&dev->mode_config.connector_ida); diff --git a/drivers/gpu/drm/drm_crtc_helper.c b/drivers/gpu/drm/drm_crtc_helper.c index 79555d2b1b87..66ca31348546 100644 --- a/drivers/gpu/drm/drm_crtc_helper.c +++ b/drivers/gpu/drm/drm_crtc_helper.c @@ -1053,10 +1053,12 @@ int drm_helper_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y, if (plane->funcs->atomic_duplicate_state) plane_state = plane->funcs->atomic_duplicate_state(plane); - else if (plane->state) + else { + if (!plane->state) + drm_atomic_helper_plane_reset(plane); + plane_state = drm_atomic_helper_plane_duplicate_state(plane); - else - plane_state = kzalloc(sizeof(*plane_state), GFP_KERNEL); + } if (!plane_state) return -ENOMEM; plane_state->plane = plane; diff --git a/drivers/gpu/drm/drm_crtc_internal.h b/drivers/gpu/drm/drm_crtc_internal.h index 247dc8b62564..a78c138282ea 100644 --- a/drivers/gpu/drm/drm_crtc_internal.h +++ b/drivers/gpu/drm/drm_crtc_internal.h @@ -33,8 +33,8 @@ int drm_mode_object_get(struct drm_device *dev, struct drm_mode_object *obj, uint32_t obj_type); -void drm_mode_object_put(struct drm_device *dev, - struct drm_mode_object *object); +void drm_mode_object_unregister(struct drm_device *dev, + struct drm_mode_object *object); /* drm_atomic.c */ int drm_atomic_get_property(struct drm_mode_object *obj, diff --git a/drivers/gpu/drm/drm_dp_mst_topology.c b/drivers/gpu/drm/drm_dp_mst_topology.c index 27fbd79d0daf..f487bed33599 100644 --- a/drivers/gpu/drm/drm_dp_mst_topology.c +++ b/drivers/gpu/drm/drm_dp_mst_topology.c @@ -2121,6 +2121,8 @@ int drm_dp_mst_topology_mgr_resume(struct drm_dp_mst_topology_mgr *mgr) if (mgr->mst_primary) { int sret; + u8 guid[16]; + sret = drm_dp_dpcd_read(mgr->aux, DP_DPCD_REV, mgr->dpcd, DP_RECEIVER_CAP_SIZE); if (sret != DP_RECEIVER_CAP_SIZE) { DRM_DEBUG_KMS("dpcd read failed - undocked during suspend?\n"); @@ -2135,6 +2137,16 @@ int drm_dp_mst_topology_mgr_resume(struct drm_dp_mst_topology_mgr *mgr) ret = -1; goto out_unlock; } + + /* Some hubs forget their guids after they resume */ + sret = drm_dp_dpcd_read(mgr->aux, DP_GUID, guid, 16); + if (sret != 16) { + DRM_DEBUG_KMS("dpcd read failed - undocked during suspend?\n"); + ret = -1; + goto out_unlock; + } + drm_dp_check_mstb_guid(mgr->mst_primary, guid); + ret = 0; } else ret = -1; @@ -2729,7 +2741,7 @@ static void drm_dp_mst_dump_mstb(struct seq_file *m, seq_printf(m, "%smst: %p, %d\n", prefix, mstb, mstb->num_ports); list_for_each_entry(port, &mstb->ports, next) { - seq_printf(m, "%sport: %d: ddps: %d ldps: %d, sdp: %d/%d, %p, conn: %p\n", prefix, port->port_num, port->ddps, port->ldps, port->num_sdp_streams, port->num_sdp_stream_sinks, port, port->connector); + seq_printf(m, "%sport: %d: input: %d: pdt: %d, ddps: %d ldps: %d, sdp: %d/%d, %p, conn: %p\n", prefix, port->port_num, port->input, port->pdt, port->ddps, port->ldps, port->num_sdp_streams, port->num_sdp_stream_sinks, port, port->connector); if (port->mstb) drm_dp_mst_dump_mstb(m, port->mstb); } @@ -2750,6 +2762,16 @@ static bool dump_dp_payload_table(struct drm_dp_mst_topology_mgr *mgr, return false; } +static void fetch_monitor_name(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, char *name, + int namelen) +{ + struct edid *mst_edid; + + mst_edid = drm_dp_mst_get_edid(port->connector, mgr, port); + drm_edid_get_monitor_name(mst_edid, name, namelen); +} + /** * drm_dp_mst_dump_topology(): dump topology to seq file. * @m: seq_file to dump output to @@ -2762,6 +2784,7 @@ void drm_dp_mst_dump_topology(struct seq_file *m, { int i; struct drm_dp_mst_port *port; + mutex_lock(&mgr->lock); if (mgr->mst_primary) drm_dp_mst_dump_mstb(m, mgr->mst_primary); @@ -2770,14 +2793,21 @@ void drm_dp_mst_dump_topology(struct seq_file *m, mutex_unlock(&mgr->lock); mutex_lock(&mgr->payload_lock); - seq_printf(m, "vcpi: %lx %lx\n", mgr->payload_mask, mgr->vcpi_mask); + seq_printf(m, "vcpi: %lx %lx %d\n", mgr->payload_mask, mgr->vcpi_mask, + mgr->max_payloads); for (i = 0; i < mgr->max_payloads; i++) { if (mgr->proposed_vcpis[i]) { + char name[14]; + port = container_of(mgr->proposed_vcpis[i], struct drm_dp_mst_port, vcpi); - seq_printf(m, "vcpi %d: %d %d %d\n", i, port->port_num, port->vcpi.vcpi, port->vcpi.num_slots); + fetch_monitor_name(mgr, port, name, sizeof(name)); + seq_printf(m, "vcpi %d: %d %d %d sink name: %s\n", i, + port->port_num, port->vcpi.vcpi, + port->vcpi.num_slots, + (*name != 0) ? name : "Unknown"); } else - seq_printf(m, "vcpi %d:unsed\n", i); + seq_printf(m, "vcpi %d:unused\n", i); } for (i = 0; i < mgr->max_payloads; i++) { seq_printf(m, "payload %d: %d, %d, %d\n", @@ -2817,8 +2847,9 @@ void drm_dp_mst_dump_topology(struct seq_file *m, for (i = 0; i < 0x3; i++) seq_printf(m, "%02x", buf[i]); seq_printf(m, " devid: "); - for (i = 0x3; i < 0x8; i++) + for (i = 0x3; i < 0x8 && buf[i]; i++) seq_printf(m, "%c", buf[i]); + seq_printf(m, " revision: hw: %x.%x sw: %x.%x", buf[0x9] >> 4, buf[0x9] & 0xf, buf[0xa], buf[0xb]); seq_printf(m, "\n"); bret = dump_dp_payload_table(mgr, buf); diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c index 167c8d3d4a31..f8a7a6e66b7e 100644 --- a/drivers/gpu/drm/drm_drv.c +++ b/drivers/gpu/drm/drm_drv.c @@ -37,13 +37,23 @@ #include "drm_legacy.h" #include "drm_internal.h" -unsigned int drm_debug = 0; /* bitmask of DRM_UT_x */ +/* + * drm_debug: Enable debug output. + * Bitmask of DRM_UT_x. See include/drm/drmP.h for details. + */ +unsigned int drm_debug = 0; EXPORT_SYMBOL(drm_debug); MODULE_AUTHOR(CORE_AUTHOR); MODULE_DESCRIPTION(CORE_DESC); MODULE_LICENSE("GPL and additional rights"); -MODULE_PARM_DESC(debug, "Enable debug output"); +MODULE_PARM_DESC(debug, "Enable debug output, where each bit enables a debug category.\n" +"\t\tBit 0 (0x01) will enable CORE messages (drm core code)\n" +"\t\tBit 1 (0x02) will enable DRIVER messages (drm controller code)\n" +"\t\tBit 2 (0x04) will enable KMS messages (modesetting code)\n" +"\t\tBit 3 (0x08) will enable PRIME messages (prime code)\n" +"\t\tBit 4 (0x10) will enable ATOMIC messages (atomic code)\n" +"\t\tBit 5 (0x20) will enable VBL messages (vblank code)"); module_param_named(debug, drm_debug, int, 0600); static DEFINE_SPINLOCK(drm_minor_lock); @@ -715,7 +725,11 @@ EXPORT_SYMBOL(drm_dev_unref); * * Register the DRM device @dev with the system, advertise device to user-space * and start normal device operation. @dev must be allocated via drm_dev_alloc() - * previously. + * previously. Right after drm_dev_register() the driver should call + * drm_connector_register_all() to register all connectors in sysfs. This is + * a separate call for backward compatibility with drivers still using + * the deprecated ->load() callback, where connectors are registered from within + * the ->load() callback. * * Never call this twice on any device! * diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 414d7f61aa05..9a9be9a131de 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -3293,6 +3293,46 @@ monitor_name(struct detailed_timing *t, void *data) *(u8 **)data = t->data.other_data.data.str.str; } +static int get_monitor_name(struct edid *edid, char name[13]) +{ + char *edid_name = NULL; + int mnl; + + if (!edid || !name) + return 0; + + drm_for_each_detailed_block((u8 *)edid, monitor_name, &edid_name); + for (mnl = 0; edid_name && mnl < 13; mnl++) { + if (edid_name[mnl] == 0x0a) + break; + + name[mnl] = edid_name[mnl]; + } + + return mnl; +} + +/** + * drm_edid_get_monitor_name - fetch the monitor name from the edid + * @edid: monitor EDID information + * @name: pointer to a character array to hold the name of the monitor + * @bufsize: The size of the name buffer (should be at least 14 chars.) + * + */ +void drm_edid_get_monitor_name(struct edid *edid, char *name, int bufsize) +{ + int name_length; + char buf[13]; + + if (bufsize <= 0) + return; + + name_length = min(get_monitor_name(edid, buf), bufsize - 1); + memcpy(name, buf, name_length); + name[name_length] = '\0'; +} +EXPORT_SYMBOL(drm_edid_get_monitor_name); + /** * drm_edid_to_eld - build ELD from EDID * @connector: connector corresponding to the HDMI/DP sink @@ -3306,7 +3346,6 @@ void drm_edid_to_eld(struct drm_connector *connector, struct edid *edid) { uint8_t *eld = connector->eld; u8 *cea; - u8 *name; u8 *db; int total_sad_count = 0; int mnl; @@ -3320,14 +3359,8 @@ void drm_edid_to_eld(struct drm_connector *connector, struct edid *edid) return; } - name = NULL; - drm_for_each_detailed_block((u8 *)edid, monitor_name, &name); - /* max: 13 bytes EDID, 16 bytes ELD */ - for (mnl = 0; name && mnl < 13; mnl++) { - if (name[mnl] == 0x0a) - break; - eld[20 + mnl] = name[mnl]; - } + mnl = get_monitor_name(edid, eld + 20); + eld[4] = (cea[1] << 5) | mnl; DRM_DEBUG_KMS("ELD monitor %s\n", eld + 20); diff --git a/drivers/gpu/drm/drm_gem.c b/drivers/gpu/drm/drm_gem.c index da0c5320789f..25dac31eef37 100644 --- a/drivers/gpu/drm/drm_gem.c +++ b/drivers/gpu/drm/drm_gem.c @@ -279,7 +279,6 @@ drm_gem_object_release_handle(int id, void *ptr, void *data) int drm_gem_handle_delete(struct drm_file *filp, u32 handle) { - struct drm_device *dev; struct drm_gem_object *obj; /* This is gross. The idr system doesn't let us try a delete and @@ -294,18 +293,19 @@ drm_gem_handle_delete(struct drm_file *filp, u32 handle) spin_lock(&filp->table_lock); /* Check if we currently have a reference on the object */ - obj = idr_find(&filp->object_idr, handle); - if (obj == NULL) { - spin_unlock(&filp->table_lock); + obj = idr_replace(&filp->object_idr, NULL, handle); + spin_unlock(&filp->table_lock); + if (IS_ERR_OR_NULL(obj)) return -EINVAL; - } - dev = obj->dev; - /* Release reference and decrement refcount. */ + /* Release driver's reference and decrement refcount. */ + drm_gem_object_release_handle(handle, obj, filp); + + /* And finally make the handle available for future allocations. */ + spin_lock(&filp->table_lock); idr_remove(&filp->object_idr, handle); spin_unlock(&filp->table_lock); - drm_gem_object_release_handle(handle, obj, filp); return 0; } EXPORT_SYMBOL(drm_gem_handle_delete); @@ -422,6 +422,10 @@ EXPORT_SYMBOL(drm_gem_handle_create); * @obj: obj in question * * This routine frees fake offsets allocated by drm_gem_create_mmap_offset(). + * + * Note that drm_gem_object_release() already calls this function, so drivers + * don't have to take care of releasing the mmap offset themselves when freeing + * the GEM object. */ void drm_gem_free_mmap_offset(struct drm_gem_object *obj) @@ -445,6 +449,9 @@ EXPORT_SYMBOL(drm_gem_free_mmap_offset); * This routine allocates and attaches a fake offset for @obj, in cases where * the virtual size differs from the physical size (ie. obj->size). Otherwise * just use drm_gem_create_mmap_offset(). + * + * This function is idempotent and handles an already allocated mmap offset + * transparently. Drivers do not need to check for this case. */ int drm_gem_create_mmap_offset_size(struct drm_gem_object *obj, size_t size) @@ -466,6 +473,9 @@ EXPORT_SYMBOL(drm_gem_create_mmap_offset_size); * structures. * * This routine allocates and attaches a fake offset for @obj. + * + * Drivers can call drm_gem_free_mmap_offset() before freeing @obj to release + * the fake offset again. */ int drm_gem_create_mmap_offset(struct drm_gem_object *obj) { @@ -759,6 +769,13 @@ drm_gem_release(struct drm_device *dev, struct drm_file *file_private) idr_destroy(&file_private->object_idr); } +/** + * drm_gem_object_release - release GEM buffer object resources + * @obj: GEM buffer object + * + * This releases any structures and resources used by @obj and is the invers of + * drm_gem_object_init(). + */ void drm_gem_object_release(struct drm_gem_object *obj) { diff --git a/drivers/gpu/drm/drm_irq.c b/drivers/gpu/drm/drm_irq.c index 881c5a6c180c..3c1a6f18e71c 100644 --- a/drivers/gpu/drm/drm_irq.c +++ b/drivers/gpu/drm/drm_irq.c @@ -863,10 +863,7 @@ int drm_calc_vbltimestamp_from_scanoutpos(struct drm_device *dev, /* Subtract time delta from raw timestamp to get final * vblank_time timestamp for end of vblank. */ - if (delta_ns < 0) - etime = ktime_add_ns(etime, -delta_ns); - else - etime = ktime_sub_ns(etime, delta_ns); + etime = ktime_sub_ns(etime, delta_ns); *vblank_time = ktime_to_timeval(etime); DRM_DEBUG_VBL("crtc %u : v 0x%x p(%d,%d)@ %ld.%ld -> %ld.%ld [e %d us, %d rep]\n", diff --git a/drivers/gpu/drm/drm_modes.c b/drivers/gpu/drm/drm_modes.c index f7448a5e95a9..7def3d58da18 100644 --- a/drivers/gpu/drm/drm_modes.c +++ b/drivers/gpu/drm/drm_modes.c @@ -98,7 +98,7 @@ void drm_mode_destroy(struct drm_device *dev, struct drm_display_mode *mode) if (!mode) return; - drm_mode_object_put(dev, &mode->base); + drm_mode_object_unregister(dev, &mode->base); kfree(mode); } diff --git a/drivers/gpu/drm/drm_probe_helper.c b/drivers/gpu/drm/drm_probe_helper.c index e714b5a7955f..0329080d7f7c 100644 --- a/drivers/gpu/drm/drm_probe_helper.c +++ b/drivers/gpu/drm/drm_probe_helper.c @@ -264,10 +264,8 @@ int drm_helper_probe_single_connector_modes(struct drm_connector *connector, count = drm_add_edid_modes(connector, edid); drm_edid_to_eld(connector, edid); } else { -#ifdef CONFIG_DRM_LOAD_EDID_FIRMWARE count = drm_load_edid_firmware(connector); if (count == 0) -#endif count = (*connector_funcs->get_modes)(connector); } diff --git a/drivers/gpu/drm/drm_sysfs.c b/drivers/gpu/drm/drm_sysfs.c index d503f8e8c2d1..d7d8cecfb0e6 100644 --- a/drivers/gpu/drm/drm_sysfs.c +++ b/drivers/gpu/drm/drm_sysfs.c @@ -287,102 +287,6 @@ static ssize_t modes_show(struct device *device, return written; } -static ssize_t tv_subconnector_show(struct device *device, - struct device_attribute *attr, - char *buf) -{ - struct drm_connector *connector = to_drm_connector(device); - struct drm_device *dev = connector->dev; - struct drm_property *prop; - uint64_t subconnector; - int ret; - - prop = dev->mode_config.tv_subconnector_property; - if (!prop) { - DRM_ERROR("Unable to find subconnector property\n"); - return 0; - } - - ret = drm_object_property_get_value(&connector->base, prop, &subconnector); - if (ret) - return 0; - - return snprintf(buf, PAGE_SIZE, "%s", - drm_get_tv_subconnector_name((int)subconnector)); -} - -static ssize_t tv_select_subconnector_show(struct device *device, - struct device_attribute *attr, - char *buf) -{ - struct drm_connector *connector = to_drm_connector(device); - struct drm_device *dev = connector->dev; - struct drm_property *prop; - uint64_t subconnector; - int ret; - - prop = dev->mode_config.tv_select_subconnector_property; - if (!prop) { - DRM_ERROR("Unable to find select subconnector property\n"); - return 0; - } - - ret = drm_object_property_get_value(&connector->base, prop, &subconnector); - if (ret) - return 0; - - return snprintf(buf, PAGE_SIZE, "%s", - drm_get_tv_select_name((int)subconnector)); -} - -static ssize_t dvii_subconnector_show(struct device *device, - struct device_attribute *attr, - char *buf) -{ - struct drm_connector *connector = to_drm_connector(device); - struct drm_device *dev = connector->dev; - struct drm_property *prop; - uint64_t subconnector; - int ret; - - prop = dev->mode_config.dvi_i_subconnector_property; - if (!prop) { - DRM_ERROR("Unable to find subconnector property\n"); - return 0; - } - - ret = drm_object_property_get_value(&connector->base, prop, &subconnector); - if (ret) - return 0; - - return snprintf(buf, PAGE_SIZE, "%s", - drm_get_dvi_i_subconnector_name((int)subconnector)); -} - -static ssize_t dvii_select_subconnector_show(struct device *device, - struct device_attribute *attr, - char *buf) -{ - struct drm_connector *connector = to_drm_connector(device); - struct drm_device *dev = connector->dev; - struct drm_property *prop; - uint64_t subconnector; - int ret; - - prop = dev->mode_config.dvi_i_select_subconnector_property; - if (!prop) { - DRM_ERROR("Unable to find select subconnector property\n"); - return 0; - } - - ret = drm_object_property_get_value(&connector->base, prop, &subconnector); - if (ret) - return 0; - - return snprintf(buf, PAGE_SIZE, "%s", - drm_get_dvi_i_select_name((int)subconnector)); -} - static DEVICE_ATTR_RW(status); static DEVICE_ATTR_RO(enabled); static DEVICE_ATTR_RO(dpms); @@ -396,54 +300,6 @@ static struct attribute *connector_dev_attrs[] = { NULL }; -static DEVICE_ATTR_RO(tv_subconnector); -static DEVICE_ATTR_RO(tv_select_subconnector); - -static struct attribute *connector_tv_dev_attrs[] = { - &dev_attr_tv_subconnector.attr, - &dev_attr_tv_select_subconnector.attr, - NULL -}; - -static DEVICE_ATTR_RO(dvii_subconnector); -static DEVICE_ATTR_RO(dvii_select_subconnector); - -static struct attribute *connector_dvii_dev_attrs[] = { - &dev_attr_dvii_subconnector.attr, - &dev_attr_dvii_select_subconnector.attr, - NULL -}; - -/* Connector type related helpers */ -static int kobj_connector_type(struct kobject *kobj) -{ - struct device *dev = kobj_to_dev(kobj); - struct drm_connector *connector = to_drm_connector(dev); - - return connector->connector_type; -} - -static umode_t connector_is_dvii(struct kobject *kobj, - struct attribute *attr, int idx) -{ - return kobj_connector_type(kobj) == DRM_MODE_CONNECTOR_DVII ? - attr->mode : 0; -} - -static umode_t connector_is_tv(struct kobject *kobj, - struct attribute *attr, int idx) -{ - switch (kobj_connector_type(kobj)) { - case DRM_MODE_CONNECTOR_Composite: - case DRM_MODE_CONNECTOR_SVIDEO: - case DRM_MODE_CONNECTOR_Component: - case DRM_MODE_CONNECTOR_TV: - return attr->mode; - } - - return 0; -} - static struct bin_attribute edid_attr = { .attr.name = "edid", .attr.mode = 0444, @@ -461,20 +317,8 @@ static const struct attribute_group connector_dev_group = { .bin_attrs = connector_bin_attrs, }; -static const struct attribute_group connector_tv_dev_group = { - .attrs = connector_tv_dev_attrs, - .is_visible = connector_is_tv, -}; - -static const struct attribute_group connector_dvii_dev_group = { - .attrs = connector_dvii_dev_attrs, - .is_visible = connector_is_dvii, -}; - static const struct attribute_group *connector_dev_groups[] = { &connector_dev_group, - &connector_tv_dev_group, - &connector_dvii_dev_group, NULL }; diff --git a/drivers/gpu/drm/exynos/Kconfig b/drivers/gpu/drm/exynos/Kconfig index f17d39279596..2fadd8275fa5 100644 --- a/drivers/gpu/drm/exynos/Kconfig +++ b/drivers/gpu/drm/exynos/Kconfig @@ -71,8 +71,9 @@ config DRM_EXYNOS_DSI This enables support for Exynos MIPI-DSI device. config DRM_EXYNOS_DP - bool "Display Port" + bool "EXYNOS specific extensions for Analogix DP driver" depends on DRM_EXYNOS_FIMD || DRM_EXYNOS7_DECON + select DRM_ANALOGIX_DP default DRM_EXYNOS select DRM_PANEL help diff --git a/drivers/gpu/drm/exynos/Makefile b/drivers/gpu/drm/exynos/Makefile index 968b31c522b2..126b0a1915db 100644 --- a/drivers/gpu/drm/exynos/Makefile +++ b/drivers/gpu/drm/exynos/Makefile @@ -12,7 +12,7 @@ exynosdrm-$(CONFIG_DRM_EXYNOS5433_DECON) += exynos5433_drm_decon.o exynosdrm-$(CONFIG_DRM_EXYNOS7_DECON) += exynos7_drm_decon.o exynosdrm-$(CONFIG_DRM_EXYNOS_DPI) += exynos_drm_dpi.o exynosdrm-$(CONFIG_DRM_EXYNOS_DSI) += exynos_drm_dsi.o -exynosdrm-$(CONFIG_DRM_EXYNOS_DP) += exynos_dp_core.o exynos_dp_reg.o +exynosdrm-$(CONFIG_DRM_EXYNOS_DP) += exynos_dp.o exynosdrm-$(CONFIG_DRM_EXYNOS_MIXER) += exynos_mixer.o exynosdrm-$(CONFIG_DRM_EXYNOS_HDMI) += exynos_hdmi.o exynosdrm-$(CONFIG_DRM_EXYNOS_VIDI) += exynos_drm_vidi.o diff --git a/drivers/gpu/drm/exynos/exynos_dp.c b/drivers/gpu/drm/exynos/exynos_dp.c new file mode 100644 index 000000000000..8ae3d51b5b33 --- /dev/null +++ b/drivers/gpu/drm/exynos/exynos_dp.c @@ -0,0 +1,314 @@ +/* + * Samsung SoC DP (Display Port) interface driver. + * + * Copyright (C) 2012 Samsung Electronics Co., Ltd. + * Author: Jingoo Han <jg1.han@samsung.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/of_graph.h> +#include <linux/component.h> +#include <video/of_display_timing.h> +#include <video/of_videomode.h> +#include <video/videomode.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_panel.h> + +#include <drm/bridge/analogix_dp.h> +#include <drm/exynos_drm.h> + +#include "exynos_drm_crtc.h" + +#define to_dp(nm) container_of(nm, struct exynos_dp_device, nm) + +struct exynos_dp_device { + struct drm_encoder encoder; + struct drm_connector connector; + struct drm_bridge *ptn_bridge; + struct drm_device *drm_dev; + struct device *dev; + + struct videomode vm; + struct analogix_dp_plat_data plat_data; +}; + +int exynos_dp_crtc_clock_enable(struct analogix_dp_plat_data *plat_data, + bool enable) +{ + struct exynos_dp_device *dp = to_dp(plat_data); + struct drm_encoder *encoder = &dp->encoder; + struct exynos_drm_crtc *crtc; + + if (!encoder) + return -1; + + crtc = to_exynos_crtc(encoder->crtc); + if (crtc && crtc->ops && crtc->ops->clock_enable) + crtc->ops->clock_enable(crtc, enable); + + return 0; +} + +static int exynos_dp_poweron(struct analogix_dp_plat_data *plat_data) +{ + return exynos_dp_crtc_clock_enable(plat_data, true); +} + +static int exynos_dp_poweroff(struct analogix_dp_plat_data *plat_data) +{ + return exynos_dp_crtc_clock_enable(plat_data, false); +} + +static int exynos_dp_get_modes(struct analogix_dp_plat_data *plat_data) +{ + struct exynos_dp_device *dp = to_dp(plat_data); + struct drm_connector *connector = &dp->connector; + struct drm_display_mode *mode; + int num_modes = 0; + + if (dp->plat_data.panel) + return num_modes; + + mode = drm_mode_create(connector->dev); + if (!mode) { + DRM_ERROR("failed to create a new display mode.\n"); + return num_modes; + } + + drm_display_mode_from_videomode(&dp->vm, mode); + connector->display_info.width_mm = mode->width_mm; + connector->display_info.height_mm = mode->height_mm; + + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + drm_mode_set_name(mode); + drm_mode_probed_add(connector, mode); + + return num_modes + 1; +} + +static int exynos_dp_bridge_attach(struct analogix_dp_plat_data *plat_data, + struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct exynos_dp_device *dp = to_dp(plat_data); + struct drm_encoder *encoder = &dp->encoder; + int ret; + + drm_connector_register(connector); + + /* Pre-empt DP connector creation if there's a bridge */ + if (dp->ptn_bridge) { + bridge->next = dp->ptn_bridge; + dp->ptn_bridge->encoder = encoder; + ret = drm_bridge_attach(encoder->dev, dp->ptn_bridge); + if (ret) { + DRM_ERROR("Failed to attach bridge to drm\n"); + bridge->next = NULL; + return ret; + } + } + + return 0; +} + +static void exynos_dp_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ +} + +static void exynos_dp_nop(struct drm_encoder *encoder) +{ + /* do nothing */ +} + +static const struct drm_encoder_helper_funcs exynos_dp_encoder_helper_funcs = { + .mode_set = exynos_dp_mode_set, + .enable = exynos_dp_nop, + .disable = exynos_dp_nop, +}; + +static const struct drm_encoder_funcs exynos_dp_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + +static int exynos_dp_dt_parse_panel(struct exynos_dp_device *dp) +{ + int ret; + + ret = of_get_videomode(dp->dev->of_node, &dp->vm, OF_USE_NATIVE_MODE); + if (ret) { + DRM_ERROR("failed: of_get_videomode() : %d\n", ret); + return ret; + } + return 0; +} + +static int exynos_dp_bind(struct device *dev, struct device *master, void *data) +{ + struct exynos_dp_device *dp = dev_get_drvdata(dev); + struct drm_encoder *encoder = &dp->encoder; + struct drm_device *drm_dev = data; + int pipe, ret; + + /* + * Just like the probe function said, we don't need the + * device drvrate anymore, we should leave the charge to + * analogix dp driver, set the device drvdata to NULL. + */ + dev_set_drvdata(dev, NULL); + + dp->dev = dev; + dp->drm_dev = drm_dev; + + dp->plat_data.dev_type = EXYNOS_DP; + dp->plat_data.power_on = exynos_dp_poweron; + dp->plat_data.power_off = exynos_dp_poweroff; + dp->plat_data.attach = exynos_dp_bridge_attach; + dp->plat_data.get_modes = exynos_dp_get_modes; + + if (!dp->plat_data.panel && !dp->ptn_bridge) { + ret = exynos_dp_dt_parse_panel(dp); + if (ret) + return ret; + } + + pipe = exynos_drm_crtc_get_pipe_from_type(drm_dev, + EXYNOS_DISPLAY_TYPE_LCD); + if (pipe < 0) + return pipe; + + encoder->possible_crtcs = 1 << pipe; + + DRM_DEBUG_KMS("possible_crtcs = 0x%x\n", encoder->possible_crtcs); + + drm_encoder_init(drm_dev, encoder, &exynos_dp_encoder_funcs, + DRM_MODE_ENCODER_TMDS, NULL); + + drm_encoder_helper_add(encoder, &exynos_dp_encoder_helper_funcs); + + dp->plat_data.encoder = encoder; + + return analogix_dp_bind(dev, dp->drm_dev, &dp->plat_data); +} + +static void exynos_dp_unbind(struct device *dev, struct device *master, + void *data) +{ + return analogix_dp_unbind(dev, master, data); +} + +static const struct component_ops exynos_dp_ops = { + .bind = exynos_dp_bind, + .unbind = exynos_dp_unbind, +}; + +static int exynos_dp_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = NULL, *endpoint = NULL; + struct exynos_dp_device *dp; + + dp = devm_kzalloc(&pdev->dev, sizeof(struct exynos_dp_device), + GFP_KERNEL); + if (!dp) + return -ENOMEM; + + /* + * We just use the drvdata until driver run into component + * add function, and then we would set drvdata to null, so + * that analogix dp driver would take charge of the drvdata. + */ + platform_set_drvdata(pdev, dp); + + /* This is for the backward compatibility. */ + np = of_parse_phandle(dev->of_node, "panel", 0); + if (np) { + dp->plat_data.panel = of_drm_find_panel(np); + of_node_put(np); + if (!dp->plat_data.panel) + return -EPROBE_DEFER; + goto out; + } + + endpoint = of_graph_get_next_endpoint(dev->of_node, NULL); + if (endpoint) { + np = of_graph_get_remote_port_parent(endpoint); + if (np) { + /* The remote port can be either a panel or a bridge */ + dp->plat_data.panel = of_drm_find_panel(np); + if (!dp->plat_data.panel) { + dp->ptn_bridge = of_drm_find_bridge(np); + if (!dp->ptn_bridge) { + of_node_put(np); + return -EPROBE_DEFER; + } + } + of_node_put(np); + } else { + DRM_ERROR("no remote endpoint device node found.\n"); + return -EINVAL; + } + } else { + DRM_ERROR("no port endpoint subnode found.\n"); + return -EINVAL; + } + +out: + return component_add(&pdev->dev, &exynos_dp_ops); +} + +static int exynos_dp_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &exynos_dp_ops); + + return 0; +} + +#ifdef CONFIG_PM +static int exynos_dp_suspend(struct device *dev) +{ + return analogix_dp_suspend(dev); +} + +static int exynos_dp_resume(struct device *dev) +{ + return analogix_dp_resume(dev); +} +#endif + +static const struct dev_pm_ops exynos_dp_pm_ops = { + SET_RUNTIME_PM_OPS(exynos_dp_suspend, exynos_dp_resume, NULL) +}; + +static const struct of_device_id exynos_dp_match[] = { + { .compatible = "samsung,exynos5-dp" }, + {}, +}; +MODULE_DEVICE_TABLE(of, exynos_dp_match); + +struct platform_driver dp_driver = { + .probe = exynos_dp_probe, + .remove = exynos_dp_remove, + .driver = { + .name = "exynos-dp", + .owner = THIS_MODULE, + .pm = &exynos_dp_pm_ops, + .of_match_table = exynos_dp_match, + }, +}; + +MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>"); +MODULE_DESCRIPTION("Samsung Specific Analogix-DP Driver Extension"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/exynos/exynos_dp_core.c b/drivers/gpu/drm/exynos/exynos_dp_core.c deleted file mode 100644 index cff8dc788820..000000000000 --- a/drivers/gpu/drm/exynos/exynos_dp_core.c +++ /dev/null @@ -1,1499 +0,0 @@ -/* - * Samsung SoC DP (Display Port) interface driver. - * - * Copyright (C) 2012 Samsung Electronics Co., Ltd. - * Author: Jingoo Han <jg1.han@samsung.com> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - */ - -#include <linux/module.h> -#include <linux/platform_device.h> -#include <linux/err.h> -#include <linux/clk.h> -#include <linux/io.h> -#include <linux/interrupt.h> -#include <linux/of.h> -#include <linux/of_gpio.h> -#include <linux/of_graph.h> -#include <linux/gpio.h> -#include <linux/component.h> -#include <linux/phy/phy.h> -#include <video/of_display_timing.h> -#include <video/of_videomode.h> - -#include <drm/drmP.h> -#include <drm/drm_crtc.h> -#include <drm/drm_crtc_helper.h> -#include <drm/drm_atomic_helper.h> -#include <drm/drm_panel.h> - -#include "exynos_dp_core.h" -#include "exynos_drm_crtc.h" - -#define ctx_from_connector(c) container_of(c, struct exynos_dp_device, \ - connector) - -static inline struct exynos_drm_crtc *dp_to_crtc(struct exynos_dp_device *dp) -{ - return to_exynos_crtc(dp->encoder.crtc); -} - -static inline struct exynos_dp_device *encoder_to_dp( - struct drm_encoder *e) -{ - return container_of(e, struct exynos_dp_device, encoder); -} - -struct bridge_init { - struct i2c_client *client; - struct device_node *node; -}; - -static void exynos_dp_init_dp(struct exynos_dp_device *dp) -{ - exynos_dp_reset(dp); - - exynos_dp_swreset(dp); - - exynos_dp_init_analog_param(dp); - exynos_dp_init_interrupt(dp); - - /* SW defined function Normal operation */ - exynos_dp_enable_sw_function(dp); - - exynos_dp_config_interrupt(dp); - exynos_dp_init_analog_func(dp); - - exynos_dp_init_hpd(dp); - exynos_dp_init_aux(dp); -} - -static int exynos_dp_detect_hpd(struct exynos_dp_device *dp) -{ - int timeout_loop = 0; - - while (exynos_dp_get_plug_in_status(dp) != 0) { - timeout_loop++; - if (DP_TIMEOUT_LOOP_COUNT < timeout_loop) { - dev_err(dp->dev, "failed to get hpd plug status\n"); - return -ETIMEDOUT; - } - usleep_range(10, 11); - } - - return 0; -} - -static unsigned char exynos_dp_calc_edid_check_sum(unsigned char *edid_data) -{ - int i; - unsigned char sum = 0; - - for (i = 0; i < EDID_BLOCK_LENGTH; i++) - sum = sum + edid_data[i]; - - return sum; -} - -static int exynos_dp_read_edid(struct exynos_dp_device *dp) -{ - unsigned char edid[EDID_BLOCK_LENGTH * 2]; - unsigned int extend_block = 0; - unsigned char sum; - unsigned char test_vector; - int retval; - - /* - * EDID device address is 0x50. - * However, if necessary, you must have set upper address - * into E-EDID in I2C device, 0x30. - */ - - /* Read Extension Flag, Number of 128-byte EDID extension blocks */ - retval = exynos_dp_read_byte_from_i2c(dp, I2C_EDID_DEVICE_ADDR, - EDID_EXTENSION_FLAG, - &extend_block); - if (retval) - return retval; - - if (extend_block > 0) { - dev_dbg(dp->dev, "EDID data includes a single extension!\n"); - - /* Read EDID data */ - retval = exynos_dp_read_bytes_from_i2c(dp, I2C_EDID_DEVICE_ADDR, - EDID_HEADER_PATTERN, - EDID_BLOCK_LENGTH, - &edid[EDID_HEADER_PATTERN]); - if (retval != 0) { - dev_err(dp->dev, "EDID Read failed!\n"); - return -EIO; - } - sum = exynos_dp_calc_edid_check_sum(edid); - if (sum != 0) { - dev_err(dp->dev, "EDID bad checksum!\n"); - return -EIO; - } - - /* Read additional EDID data */ - retval = exynos_dp_read_bytes_from_i2c(dp, - I2C_EDID_DEVICE_ADDR, - EDID_BLOCK_LENGTH, - EDID_BLOCK_LENGTH, - &edid[EDID_BLOCK_LENGTH]); - if (retval != 0) { - dev_err(dp->dev, "EDID Read failed!\n"); - return -EIO; - } - sum = exynos_dp_calc_edid_check_sum(&edid[EDID_BLOCK_LENGTH]); - if (sum != 0) { - dev_err(dp->dev, "EDID bad checksum!\n"); - return -EIO; - } - - exynos_dp_read_byte_from_dpcd(dp, DP_TEST_REQUEST, - &test_vector); - if (test_vector & DP_TEST_LINK_EDID_READ) { - exynos_dp_write_byte_to_dpcd(dp, - DP_TEST_EDID_CHECKSUM, - edid[EDID_BLOCK_LENGTH + EDID_CHECKSUM]); - exynos_dp_write_byte_to_dpcd(dp, - DP_TEST_RESPONSE, - DP_TEST_EDID_CHECKSUM_WRITE); - } - } else { - dev_info(dp->dev, "EDID data does not include any extensions.\n"); - - /* Read EDID data */ - retval = exynos_dp_read_bytes_from_i2c(dp, - I2C_EDID_DEVICE_ADDR, - EDID_HEADER_PATTERN, - EDID_BLOCK_LENGTH, - &edid[EDID_HEADER_PATTERN]); - if (retval != 0) { - dev_err(dp->dev, "EDID Read failed!\n"); - return -EIO; - } - sum = exynos_dp_calc_edid_check_sum(edid); - if (sum != 0) { - dev_err(dp->dev, "EDID bad checksum!\n"); - return -EIO; - } - - exynos_dp_read_byte_from_dpcd(dp, - DP_TEST_REQUEST, - &test_vector); - if (test_vector & DP_TEST_LINK_EDID_READ) { - exynos_dp_write_byte_to_dpcd(dp, - DP_TEST_EDID_CHECKSUM, - edid[EDID_CHECKSUM]); - exynos_dp_write_byte_to_dpcd(dp, - DP_TEST_RESPONSE, - DP_TEST_EDID_CHECKSUM_WRITE); - } - } - - dev_dbg(dp->dev, "EDID Read success!\n"); - return 0; -} - -static int exynos_dp_handle_edid(struct exynos_dp_device *dp) -{ - u8 buf[12]; - int i; - int retval; - - /* Read DPCD DP_DPCD_REV~RECEIVE_PORT1_CAP_1 */ - retval = exynos_dp_read_bytes_from_dpcd(dp, DP_DPCD_REV, - 12, buf); - if (retval) - return retval; - - /* Read EDID */ - for (i = 0; i < 3; i++) { - retval = exynos_dp_read_edid(dp); - if (!retval) - break; - } - - return retval; -} - -static void exynos_dp_enable_rx_to_enhanced_mode(struct exynos_dp_device *dp, - bool enable) -{ - u8 data; - - exynos_dp_read_byte_from_dpcd(dp, DP_LANE_COUNT_SET, &data); - - if (enable) - exynos_dp_write_byte_to_dpcd(dp, DP_LANE_COUNT_SET, - DP_LANE_COUNT_ENHANCED_FRAME_EN | - DPCD_LANE_COUNT_SET(data)); - else - exynos_dp_write_byte_to_dpcd(dp, DP_LANE_COUNT_SET, - DPCD_LANE_COUNT_SET(data)); -} - -static int exynos_dp_is_enhanced_mode_available(struct exynos_dp_device *dp) -{ - u8 data; - int retval; - - exynos_dp_read_byte_from_dpcd(dp, DP_MAX_LANE_COUNT, &data); - retval = DPCD_ENHANCED_FRAME_CAP(data); - - return retval; -} - -static void exynos_dp_set_enhanced_mode(struct exynos_dp_device *dp) -{ - u8 data; - - data = exynos_dp_is_enhanced_mode_available(dp); - exynos_dp_enable_rx_to_enhanced_mode(dp, data); - exynos_dp_enable_enhanced_mode(dp, data); -} - -static void exynos_dp_training_pattern_dis(struct exynos_dp_device *dp) -{ - exynos_dp_set_training_pattern(dp, DP_NONE); - - exynos_dp_write_byte_to_dpcd(dp, - DP_TRAINING_PATTERN_SET, - DP_TRAINING_PATTERN_DISABLE); -} - -static void exynos_dp_set_lane_lane_pre_emphasis(struct exynos_dp_device *dp, - int pre_emphasis, int lane) -{ - switch (lane) { - case 0: - exynos_dp_set_lane0_pre_emphasis(dp, pre_emphasis); - break; - case 1: - exynos_dp_set_lane1_pre_emphasis(dp, pre_emphasis); - break; - - case 2: - exynos_dp_set_lane2_pre_emphasis(dp, pre_emphasis); - break; - - case 3: - exynos_dp_set_lane3_pre_emphasis(dp, pre_emphasis); - break; - } -} - -static int exynos_dp_link_start(struct exynos_dp_device *dp) -{ - u8 buf[4]; - int lane, lane_count, pll_tries, retval; - - lane_count = dp->link_train.lane_count; - - dp->link_train.lt_state = CLOCK_RECOVERY; - dp->link_train.eq_loop = 0; - - for (lane = 0; lane < lane_count; lane++) - dp->link_train.cr_loop[lane] = 0; - - /* Set link rate and count as you want to establish*/ - exynos_dp_set_link_bandwidth(dp, dp->link_train.link_rate); - exynos_dp_set_lane_count(dp, dp->link_train.lane_count); - - /* Setup RX configuration */ - buf[0] = dp->link_train.link_rate; - buf[1] = dp->link_train.lane_count; - retval = exynos_dp_write_bytes_to_dpcd(dp, DP_LINK_BW_SET, - 2, buf); - if (retval) - return retval; - - /* Set TX pre-emphasis to minimum */ - for (lane = 0; lane < lane_count; lane++) - exynos_dp_set_lane_lane_pre_emphasis(dp, - PRE_EMPHASIS_LEVEL_0, lane); - - /* Wait for PLL lock */ - pll_tries = 0; - while (exynos_dp_get_pll_lock_status(dp) == PLL_UNLOCKED) { - if (pll_tries == DP_TIMEOUT_LOOP_COUNT) { - dev_err(dp->dev, "Wait for PLL lock timed out\n"); - return -ETIMEDOUT; - } - - pll_tries++; - usleep_range(90, 120); - } - - /* Set training pattern 1 */ - exynos_dp_set_training_pattern(dp, TRAINING_PTN1); - - /* Set RX training pattern */ - retval = exynos_dp_write_byte_to_dpcd(dp, - DP_TRAINING_PATTERN_SET, - DP_LINK_SCRAMBLING_DISABLE | DP_TRAINING_PATTERN_1); - if (retval) - return retval; - - for (lane = 0; lane < lane_count; lane++) - buf[lane] = DP_TRAIN_PRE_EMPH_LEVEL_0 | - DP_TRAIN_VOLTAGE_SWING_LEVEL_0; - - retval = exynos_dp_write_bytes_to_dpcd(dp, DP_TRAINING_LANE0_SET, - lane_count, buf); - - return retval; -} - -static unsigned char exynos_dp_get_lane_status(u8 link_status[2], int lane) -{ - int shift = (lane & 1) * 4; - u8 link_value = link_status[lane>>1]; - - return (link_value >> shift) & 0xf; -} - -static int exynos_dp_clock_recovery_ok(u8 link_status[2], int lane_count) -{ - int lane; - u8 lane_status; - - for (lane = 0; lane < lane_count; lane++) { - lane_status = exynos_dp_get_lane_status(link_status, lane); - if ((lane_status & DP_LANE_CR_DONE) == 0) - return -EINVAL; - } - return 0; -} - -static int exynos_dp_channel_eq_ok(u8 link_status[2], u8 link_align, - int lane_count) -{ - int lane; - u8 lane_status; - - if ((link_align & DP_INTERLANE_ALIGN_DONE) == 0) - return -EINVAL; - - for (lane = 0; lane < lane_count; lane++) { - lane_status = exynos_dp_get_lane_status(link_status, lane); - lane_status &= DP_CHANNEL_EQ_BITS; - if (lane_status != DP_CHANNEL_EQ_BITS) - return -EINVAL; - } - - return 0; -} - -static unsigned char exynos_dp_get_adjust_request_voltage(u8 adjust_request[2], - int lane) -{ - int shift = (lane & 1) * 4; - u8 link_value = adjust_request[lane>>1]; - - return (link_value >> shift) & 0x3; -} - -static unsigned char exynos_dp_get_adjust_request_pre_emphasis( - u8 adjust_request[2], - int lane) -{ - int shift = (lane & 1) * 4; - u8 link_value = adjust_request[lane>>1]; - - return ((link_value >> shift) & 0xc) >> 2; -} - -static void exynos_dp_set_lane_link_training(struct exynos_dp_device *dp, - u8 training_lane_set, int lane) -{ - switch (lane) { - case 0: - exynos_dp_set_lane0_link_training(dp, training_lane_set); - break; - case 1: - exynos_dp_set_lane1_link_training(dp, training_lane_set); - break; - - case 2: - exynos_dp_set_lane2_link_training(dp, training_lane_set); - break; - - case 3: - exynos_dp_set_lane3_link_training(dp, training_lane_set); - break; - } -} - -static unsigned int exynos_dp_get_lane_link_training( - struct exynos_dp_device *dp, - int lane) -{ - u32 reg; - - switch (lane) { - case 0: - reg = exynos_dp_get_lane0_link_training(dp); - break; - case 1: - reg = exynos_dp_get_lane1_link_training(dp); - break; - case 2: - reg = exynos_dp_get_lane2_link_training(dp); - break; - case 3: - reg = exynos_dp_get_lane3_link_training(dp); - break; - default: - WARN_ON(1); - return 0; - } - - return reg; -} - -static void exynos_dp_reduce_link_rate(struct exynos_dp_device *dp) -{ - exynos_dp_training_pattern_dis(dp); - exynos_dp_set_enhanced_mode(dp); - - dp->link_train.lt_state = FAILED; -} - -static void exynos_dp_get_adjust_training_lane(struct exynos_dp_device *dp, - u8 adjust_request[2]) -{ - int lane, lane_count; - u8 voltage_swing, pre_emphasis, training_lane; - - lane_count = dp->link_train.lane_count; - for (lane = 0; lane < lane_count; lane++) { - voltage_swing = exynos_dp_get_adjust_request_voltage( - adjust_request, lane); - pre_emphasis = exynos_dp_get_adjust_request_pre_emphasis( - adjust_request, lane); - training_lane = DPCD_VOLTAGE_SWING_SET(voltage_swing) | - DPCD_PRE_EMPHASIS_SET(pre_emphasis); - - if (voltage_swing == VOLTAGE_LEVEL_3) - training_lane |= DP_TRAIN_MAX_SWING_REACHED; - if (pre_emphasis == PRE_EMPHASIS_LEVEL_3) - training_lane |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED; - - dp->link_train.training_lane[lane] = training_lane; - } -} - -static int exynos_dp_process_clock_recovery(struct exynos_dp_device *dp) -{ - int lane, lane_count, retval; - u8 voltage_swing, pre_emphasis, training_lane; - u8 link_status[2], adjust_request[2]; - - usleep_range(100, 101); - - lane_count = dp->link_train.lane_count; - - retval = exynos_dp_read_bytes_from_dpcd(dp, - DP_LANE0_1_STATUS, 2, link_status); - if (retval) - return retval; - - retval = exynos_dp_read_bytes_from_dpcd(dp, - DP_ADJUST_REQUEST_LANE0_1, 2, adjust_request); - if (retval) - return retval; - - if (exynos_dp_clock_recovery_ok(link_status, lane_count) == 0) { - /* set training pattern 2 for EQ */ - exynos_dp_set_training_pattern(dp, TRAINING_PTN2); - - retval = exynos_dp_write_byte_to_dpcd(dp, - DP_TRAINING_PATTERN_SET, - DP_LINK_SCRAMBLING_DISABLE | - DP_TRAINING_PATTERN_2); - if (retval) - return retval; - - dev_info(dp->dev, "Link Training Clock Recovery success\n"); - dp->link_train.lt_state = EQUALIZER_TRAINING; - } else { - for (lane = 0; lane < lane_count; lane++) { - training_lane = exynos_dp_get_lane_link_training( - dp, lane); - voltage_swing = exynos_dp_get_adjust_request_voltage( - adjust_request, lane); - pre_emphasis = exynos_dp_get_adjust_request_pre_emphasis( - adjust_request, lane); - - if (DPCD_VOLTAGE_SWING_GET(training_lane) == - voltage_swing && - DPCD_PRE_EMPHASIS_GET(training_lane) == - pre_emphasis) - dp->link_train.cr_loop[lane]++; - - if (dp->link_train.cr_loop[lane] == MAX_CR_LOOP || - voltage_swing == VOLTAGE_LEVEL_3 || - pre_emphasis == PRE_EMPHASIS_LEVEL_3) { - dev_err(dp->dev, "CR Max reached (%d,%d,%d)\n", - dp->link_train.cr_loop[lane], - voltage_swing, pre_emphasis); - exynos_dp_reduce_link_rate(dp); - return -EIO; - } - } - } - - exynos_dp_get_adjust_training_lane(dp, adjust_request); - - for (lane = 0; lane < lane_count; lane++) - exynos_dp_set_lane_link_training(dp, - dp->link_train.training_lane[lane], lane); - - retval = exynos_dp_write_bytes_to_dpcd(dp, - DP_TRAINING_LANE0_SET, lane_count, - dp->link_train.training_lane); - if (retval) - return retval; - - return retval; -} - -static int exynos_dp_process_equalizer_training(struct exynos_dp_device *dp) -{ - int lane, lane_count, retval; - u32 reg; - u8 link_align, link_status[2], adjust_request[2]; - - usleep_range(400, 401); - - lane_count = dp->link_train.lane_count; - - retval = exynos_dp_read_bytes_from_dpcd(dp, - DP_LANE0_1_STATUS, 2, link_status); - if (retval) - return retval; - - if (exynos_dp_clock_recovery_ok(link_status, lane_count)) { - exynos_dp_reduce_link_rate(dp); - return -EIO; - } - - retval = exynos_dp_read_bytes_from_dpcd(dp, - DP_ADJUST_REQUEST_LANE0_1, 2, adjust_request); - if (retval) - return retval; - - retval = exynos_dp_read_byte_from_dpcd(dp, - DP_LANE_ALIGN_STATUS_UPDATED, &link_align); - if (retval) - return retval; - - exynos_dp_get_adjust_training_lane(dp, adjust_request); - - if (!exynos_dp_channel_eq_ok(link_status, link_align, lane_count)) { - /* traing pattern Set to Normal */ - exynos_dp_training_pattern_dis(dp); - - dev_info(dp->dev, "Link Training success!\n"); - - exynos_dp_get_link_bandwidth(dp, ®); - dp->link_train.link_rate = reg; - dev_dbg(dp->dev, "final bandwidth = %.2x\n", - dp->link_train.link_rate); - - exynos_dp_get_lane_count(dp, ®); - dp->link_train.lane_count = reg; - dev_dbg(dp->dev, "final lane count = %.2x\n", - dp->link_train.lane_count); - - /* set enhanced mode if available */ - exynos_dp_set_enhanced_mode(dp); - dp->link_train.lt_state = FINISHED; - - return 0; - } - - /* not all locked */ - dp->link_train.eq_loop++; - - if (dp->link_train.eq_loop > MAX_EQ_LOOP) { - dev_err(dp->dev, "EQ Max loop\n"); - exynos_dp_reduce_link_rate(dp); - return -EIO; - } - - for (lane = 0; lane < lane_count; lane++) - exynos_dp_set_lane_link_training(dp, - dp->link_train.training_lane[lane], lane); - - retval = exynos_dp_write_bytes_to_dpcd(dp, DP_TRAINING_LANE0_SET, - lane_count, dp->link_train.training_lane); - - return retval; -} - -static void exynos_dp_get_max_rx_bandwidth(struct exynos_dp_device *dp, - u8 *bandwidth) -{ - u8 data; - - /* - * For DP rev.1.1, Maximum link rate of Main Link lanes - * 0x06 = 1.62 Gbps, 0x0a = 2.7 Gbps - */ - exynos_dp_read_byte_from_dpcd(dp, DP_MAX_LINK_RATE, &data); - *bandwidth = data; -} - -static void exynos_dp_get_max_rx_lane_count(struct exynos_dp_device *dp, - u8 *lane_count) -{ - u8 data; - - /* - * For DP rev.1.1, Maximum number of Main Link lanes - * 0x01 = 1 lane, 0x02 = 2 lanes, 0x04 = 4 lanes - */ - exynos_dp_read_byte_from_dpcd(dp, DP_MAX_LANE_COUNT, &data); - *lane_count = DPCD_MAX_LANE_COUNT(data); -} - -static void exynos_dp_init_training(struct exynos_dp_device *dp, - enum link_lane_count_type max_lane, - enum link_rate_type max_rate) -{ - /* - * MACRO_RST must be applied after the PLL_LOCK to avoid - * the DP inter pair skew issue for at least 10 us - */ - exynos_dp_reset_macro(dp); - - /* Initialize by reading RX's DPCD */ - exynos_dp_get_max_rx_bandwidth(dp, &dp->link_train.link_rate); - exynos_dp_get_max_rx_lane_count(dp, &dp->link_train.lane_count); - - if ((dp->link_train.link_rate != LINK_RATE_1_62GBPS) && - (dp->link_train.link_rate != LINK_RATE_2_70GBPS)) { - dev_err(dp->dev, "Rx Max Link Rate is abnormal :%x !\n", - dp->link_train.link_rate); - dp->link_train.link_rate = LINK_RATE_1_62GBPS; - } - - if (dp->link_train.lane_count == 0) { - dev_err(dp->dev, "Rx Max Lane count is abnormal :%x !\n", - dp->link_train.lane_count); - dp->link_train.lane_count = (u8)LANE_COUNT1; - } - - /* Setup TX lane count & rate */ - if (dp->link_train.lane_count > max_lane) - dp->link_train.lane_count = max_lane; - if (dp->link_train.link_rate > max_rate) - dp->link_train.link_rate = max_rate; - - /* All DP analog module power up */ - exynos_dp_set_analog_power_down(dp, POWER_ALL, 0); -} - -static int exynos_dp_sw_link_training(struct exynos_dp_device *dp) -{ - int retval = 0, training_finished = 0; - - dp->link_train.lt_state = START; - - /* Process here */ - while (!retval && !training_finished) { - switch (dp->link_train.lt_state) { - case START: - retval = exynos_dp_link_start(dp); - if (retval) - dev_err(dp->dev, "LT link start failed!\n"); - break; - case CLOCK_RECOVERY: - retval = exynos_dp_process_clock_recovery(dp); - if (retval) - dev_err(dp->dev, "LT CR failed!\n"); - break; - case EQUALIZER_TRAINING: - retval = exynos_dp_process_equalizer_training(dp); - if (retval) - dev_err(dp->dev, "LT EQ failed!\n"); - break; - case FINISHED: - training_finished = 1; - break; - case FAILED: - return -EREMOTEIO; - } - } - if (retval) - dev_err(dp->dev, "eDP link training failed (%d)\n", retval); - - return retval; -} - -static int exynos_dp_set_link_train(struct exynos_dp_device *dp, - u32 count, - u32 bwtype) -{ - int i; - int retval; - - for (i = 0; i < DP_TIMEOUT_LOOP_COUNT; i++) { - exynos_dp_init_training(dp, count, bwtype); - retval = exynos_dp_sw_link_training(dp); - if (retval == 0) - break; - - usleep_range(100, 110); - } - - return retval; -} - -static int exynos_dp_config_video(struct exynos_dp_device *dp) -{ - int retval = 0; - int timeout_loop = 0; - int done_count = 0; - - exynos_dp_config_video_slave_mode(dp); - - exynos_dp_set_video_color_format(dp); - - if (exynos_dp_get_pll_lock_status(dp) == PLL_UNLOCKED) { - dev_err(dp->dev, "PLL is not locked yet.\n"); - return -EINVAL; - } - - for (;;) { - timeout_loop++; - if (exynos_dp_is_slave_video_stream_clock_on(dp) == 0) - break; - if (DP_TIMEOUT_LOOP_COUNT < timeout_loop) { - dev_err(dp->dev, "Timeout of video streamclk ok\n"); - return -ETIMEDOUT; - } - - usleep_range(1, 2); - } - - /* Set to use the register calculated M/N video */ - exynos_dp_set_video_cr_mn(dp, CALCULATED_M, 0, 0); - - /* For video bist, Video timing must be generated by register */ - exynos_dp_set_video_timing_mode(dp, VIDEO_TIMING_FROM_CAPTURE); - - /* Disable video mute */ - exynos_dp_enable_video_mute(dp, 0); - - /* Configure video slave mode */ - exynos_dp_enable_video_master(dp, 0); - - timeout_loop = 0; - - for (;;) { - timeout_loop++; - if (exynos_dp_is_video_stream_on(dp) == 0) { - done_count++; - if (done_count > 10) - break; - } else if (done_count) { - done_count = 0; - } - if (DP_TIMEOUT_LOOP_COUNT < timeout_loop) { - dev_err(dp->dev, "Timeout of video streamclk ok\n"); - return -ETIMEDOUT; - } - - usleep_range(1000, 1001); - } - - if (retval != 0) - dev_err(dp->dev, "Video stream is not detected!\n"); - - return retval; -} - -static void exynos_dp_enable_scramble(struct exynos_dp_device *dp, bool enable) -{ - u8 data; - - if (enable) { - exynos_dp_enable_scrambling(dp); - - exynos_dp_read_byte_from_dpcd(dp, - DP_TRAINING_PATTERN_SET, - &data); - exynos_dp_write_byte_to_dpcd(dp, - DP_TRAINING_PATTERN_SET, - (u8)(data & ~DP_LINK_SCRAMBLING_DISABLE)); - } else { - exynos_dp_disable_scrambling(dp); - - exynos_dp_read_byte_from_dpcd(dp, - DP_TRAINING_PATTERN_SET, - &data); - exynos_dp_write_byte_to_dpcd(dp, - DP_TRAINING_PATTERN_SET, - (u8)(data | DP_LINK_SCRAMBLING_DISABLE)); - } -} - -static irqreturn_t exynos_dp_irq_handler(int irq, void *arg) -{ - struct exynos_dp_device *dp = arg; - - enum dp_irq_type irq_type; - - irq_type = exynos_dp_get_irq_type(dp); - switch (irq_type) { - case DP_IRQ_TYPE_HP_CABLE_IN: - dev_dbg(dp->dev, "Received irq - cable in\n"); - schedule_work(&dp->hotplug_work); - exynos_dp_clear_hotplug_interrupts(dp); - break; - case DP_IRQ_TYPE_HP_CABLE_OUT: - dev_dbg(dp->dev, "Received irq - cable out\n"); - exynos_dp_clear_hotplug_interrupts(dp); - break; - case DP_IRQ_TYPE_HP_CHANGE: - /* - * We get these change notifications once in a while, but there - * is nothing we can do with them. Just ignore it for now and - * only handle cable changes. - */ - dev_dbg(dp->dev, "Received irq - hotplug change; ignoring.\n"); - exynos_dp_clear_hotplug_interrupts(dp); - break; - default: - dev_err(dp->dev, "Received irq - unknown type!\n"); - break; - } - return IRQ_HANDLED; -} - -static void exynos_dp_hotplug(struct work_struct *work) -{ - struct exynos_dp_device *dp; - - dp = container_of(work, struct exynos_dp_device, hotplug_work); - - if (dp->drm_dev) - drm_helper_hpd_irq_event(dp->drm_dev); -} - -static void exynos_dp_commit(struct drm_encoder *encoder) -{ - struct exynos_dp_device *dp = encoder_to_dp(encoder); - int ret; - - /* Keep the panel disabled while we configure video */ - if (dp->panel) { - if (drm_panel_disable(dp->panel)) - DRM_ERROR("failed to disable the panel\n"); - } - - ret = exynos_dp_detect_hpd(dp); - if (ret) { - /* Cable has been disconnected, we're done */ - return; - } - - ret = exynos_dp_handle_edid(dp); - if (ret) { - dev_err(dp->dev, "unable to handle edid\n"); - return; - } - - ret = exynos_dp_set_link_train(dp, dp->video_info->lane_count, - dp->video_info->link_rate); - if (ret) { - dev_err(dp->dev, "unable to do link train\n"); - return; - } - - exynos_dp_enable_scramble(dp, 1); - exynos_dp_enable_rx_to_enhanced_mode(dp, 1); - exynos_dp_enable_enhanced_mode(dp, 1); - - exynos_dp_set_lane_count(dp, dp->video_info->lane_count); - exynos_dp_set_link_bandwidth(dp, dp->video_info->link_rate); - - exynos_dp_init_video(dp); - ret = exynos_dp_config_video(dp); - if (ret) - dev_err(dp->dev, "unable to config video\n"); - - /* Safe to enable the panel now */ - if (dp->panel) { - if (drm_panel_enable(dp->panel)) - DRM_ERROR("failed to enable the panel\n"); - } - - /* Enable video */ - exynos_dp_start_video(dp); -} - -static enum drm_connector_status exynos_dp_detect( - struct drm_connector *connector, bool force) -{ - return connector_status_connected; -} - -static void exynos_dp_connector_destroy(struct drm_connector *connector) -{ - drm_connector_unregister(connector); - drm_connector_cleanup(connector); -} - -static const struct drm_connector_funcs exynos_dp_connector_funcs = { - .dpms = drm_atomic_helper_connector_dpms, - .fill_modes = drm_helper_probe_single_connector_modes, - .detect = exynos_dp_detect, - .destroy = exynos_dp_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 int exynos_dp_get_modes(struct drm_connector *connector) -{ - struct exynos_dp_device *dp = ctx_from_connector(connector); - struct drm_display_mode *mode; - - if (dp->panel) - return drm_panel_get_modes(dp->panel); - - mode = drm_mode_create(connector->dev); - if (!mode) { - DRM_ERROR("failed to create a new display mode.\n"); - return 0; - } - - drm_display_mode_from_videomode(&dp->vm, mode); - connector->display_info.width_mm = mode->width_mm; - connector->display_info.height_mm = mode->height_mm; - - mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; - drm_mode_set_name(mode); - drm_mode_probed_add(connector, mode); - - return 1; -} - -static struct drm_encoder *exynos_dp_best_encoder( - struct drm_connector *connector) -{ - struct exynos_dp_device *dp = ctx_from_connector(connector); - - return &dp->encoder; -} - -static const struct drm_connector_helper_funcs exynos_dp_connector_helper_funcs = { - .get_modes = exynos_dp_get_modes, - .best_encoder = exynos_dp_best_encoder, -}; - -/* returns the number of bridges attached */ -static int exynos_drm_attach_lcd_bridge(struct exynos_dp_device *dp, - struct drm_encoder *encoder) -{ - int ret; - - encoder->bridge->next = dp->ptn_bridge; - dp->ptn_bridge->encoder = encoder; - ret = drm_bridge_attach(encoder->dev, dp->ptn_bridge); - if (ret) { - DRM_ERROR("Failed to attach bridge to drm\n"); - return ret; - } - - return 0; -} - -static int exynos_dp_bridge_attach(struct drm_bridge *bridge) -{ - struct exynos_dp_device *dp = bridge->driver_private; - struct drm_encoder *encoder = &dp->encoder; - struct drm_connector *connector = &dp->connector; - int ret; - - /* Pre-empt DP connector creation if there's a bridge */ - if (dp->ptn_bridge) { - ret = exynos_drm_attach_lcd_bridge(dp, encoder); - if (!ret) - return 0; - } - - connector->polled = DRM_CONNECTOR_POLL_HPD; - - ret = drm_connector_init(dp->drm_dev, connector, - &exynos_dp_connector_funcs, DRM_MODE_CONNECTOR_eDP); - if (ret) { - DRM_ERROR("Failed to initialize connector with drm\n"); - return ret; - } - - drm_connector_helper_add(connector, &exynos_dp_connector_helper_funcs); - drm_connector_register(connector); - drm_mode_connector_attach_encoder(connector, encoder); - - if (dp->panel) - ret = drm_panel_attach(dp->panel, &dp->connector); - - return ret; -} - -static void exynos_dp_bridge_enable(struct drm_bridge *bridge) -{ - struct exynos_dp_device *dp = bridge->driver_private; - struct exynos_drm_crtc *crtc = dp_to_crtc(dp); - - if (dp->dpms_mode == DRM_MODE_DPMS_ON) - return; - - pm_runtime_get_sync(dp->dev); - - if (dp->panel) { - if (drm_panel_prepare(dp->panel)) { - DRM_ERROR("failed to setup the panel\n"); - return; - } - } - - if (crtc->ops->clock_enable) - crtc->ops->clock_enable(dp_to_crtc(dp), true); - - phy_power_on(dp->phy); - exynos_dp_init_dp(dp); - enable_irq(dp->irq); - exynos_dp_commit(&dp->encoder); - - dp->dpms_mode = DRM_MODE_DPMS_ON; -} - -static void exynos_dp_bridge_disable(struct drm_bridge *bridge) -{ - struct exynos_dp_device *dp = bridge->driver_private; - struct exynos_drm_crtc *crtc = dp_to_crtc(dp); - - if (dp->dpms_mode != DRM_MODE_DPMS_ON) - return; - - if (dp->panel) { - if (drm_panel_disable(dp->panel)) { - DRM_ERROR("failed to disable the panel\n"); - return; - } - } - - disable_irq(dp->irq); - flush_work(&dp->hotplug_work); - phy_power_off(dp->phy); - - if (crtc->ops->clock_enable) - crtc->ops->clock_enable(dp_to_crtc(dp), false); - - if (dp->panel) { - if (drm_panel_unprepare(dp->panel)) - DRM_ERROR("failed to turnoff the panel\n"); - } - - pm_runtime_put_sync(dp->dev); - - dp->dpms_mode = DRM_MODE_DPMS_OFF; -} - -static void exynos_dp_bridge_nop(struct drm_bridge *bridge) -{ - /* do nothing */ -} - -static const struct drm_bridge_funcs exynos_dp_bridge_funcs = { - .enable = exynos_dp_bridge_enable, - .disable = exynos_dp_bridge_disable, - .pre_enable = exynos_dp_bridge_nop, - .post_disable = exynos_dp_bridge_nop, - .attach = exynos_dp_bridge_attach, -}; - -static int exynos_dp_create_connector(struct drm_encoder *encoder) -{ - struct exynos_dp_device *dp = encoder_to_dp(encoder); - struct drm_device *drm_dev = dp->drm_dev; - struct drm_bridge *bridge; - int ret; - - bridge = devm_kzalloc(drm_dev->dev, sizeof(*bridge), GFP_KERNEL); - if (!bridge) { - DRM_ERROR("failed to allocate for drm bridge\n"); - return -ENOMEM; - } - - dp->bridge = bridge; - - encoder->bridge = bridge; - bridge->driver_private = dp; - bridge->encoder = encoder; - bridge->funcs = &exynos_dp_bridge_funcs; - - ret = drm_bridge_attach(drm_dev, bridge); - if (ret) { - DRM_ERROR("failed to attach drm bridge\n"); - return -EINVAL; - } - - return 0; -} - -static void exynos_dp_mode_set(struct drm_encoder *encoder, - struct drm_display_mode *mode, - struct drm_display_mode *adjusted_mode) -{ -} - -static void exynos_dp_enable(struct drm_encoder *encoder) -{ -} - -static void exynos_dp_disable(struct drm_encoder *encoder) -{ -} - -static const struct drm_encoder_helper_funcs exynos_dp_encoder_helper_funcs = { - .mode_set = exynos_dp_mode_set, - .enable = exynos_dp_enable, - .disable = exynos_dp_disable, -}; - -static const struct drm_encoder_funcs exynos_dp_encoder_funcs = { - .destroy = drm_encoder_cleanup, -}; - -static struct video_info *exynos_dp_dt_parse_pdata(struct device *dev) -{ - struct device_node *dp_node = dev->of_node; - struct video_info *dp_video_config; - - dp_video_config = devm_kzalloc(dev, - sizeof(*dp_video_config), GFP_KERNEL); - if (!dp_video_config) - return ERR_PTR(-ENOMEM); - - dp_video_config->h_sync_polarity = - of_property_read_bool(dp_node, "hsync-active-high"); - - dp_video_config->v_sync_polarity = - of_property_read_bool(dp_node, "vsync-active-high"); - - dp_video_config->interlaced = - of_property_read_bool(dp_node, "interlaced"); - - if (of_property_read_u32(dp_node, "samsung,color-space", - &dp_video_config->color_space)) { - dev_err(dev, "failed to get color-space\n"); - return ERR_PTR(-EINVAL); - } - - if (of_property_read_u32(dp_node, "samsung,dynamic-range", - &dp_video_config->dynamic_range)) { - dev_err(dev, "failed to get dynamic-range\n"); - return ERR_PTR(-EINVAL); - } - - if (of_property_read_u32(dp_node, "samsung,ycbcr-coeff", - &dp_video_config->ycbcr_coeff)) { - dev_err(dev, "failed to get ycbcr-coeff\n"); - return ERR_PTR(-EINVAL); - } - - if (of_property_read_u32(dp_node, "samsung,color-depth", - &dp_video_config->color_depth)) { - dev_err(dev, "failed to get color-depth\n"); - return ERR_PTR(-EINVAL); - } - - if (of_property_read_u32(dp_node, "samsung,link-rate", - &dp_video_config->link_rate)) { - dev_err(dev, "failed to get link-rate\n"); - return ERR_PTR(-EINVAL); - } - - if (of_property_read_u32(dp_node, "samsung,lane-count", - &dp_video_config->lane_count)) { - dev_err(dev, "failed to get lane-count\n"); - return ERR_PTR(-EINVAL); - } - - return dp_video_config; -} - -static int exynos_dp_dt_parse_panel(struct exynos_dp_device *dp) -{ - int ret; - - ret = of_get_videomode(dp->dev->of_node, &dp->vm, OF_USE_NATIVE_MODE); - if (ret) { - DRM_ERROR("failed: of_get_videomode() : %d\n", ret); - return ret; - } - return 0; -} - -static int exynos_dp_bind(struct device *dev, struct device *master, void *data) -{ - struct exynos_dp_device *dp = dev_get_drvdata(dev); - struct platform_device *pdev = to_platform_device(dev); - struct drm_device *drm_dev = data; - struct drm_encoder *encoder = &dp->encoder; - struct resource *res; - unsigned int irq_flags; - int pipe, ret = 0; - - dp->dev = &pdev->dev; - dp->dpms_mode = DRM_MODE_DPMS_OFF; - - dp->video_info = exynos_dp_dt_parse_pdata(&pdev->dev); - if (IS_ERR(dp->video_info)) - return PTR_ERR(dp->video_info); - - dp->phy = devm_phy_get(dp->dev, "dp"); - if (IS_ERR(dp->phy)) { - dev_err(dp->dev, "no DP phy configured\n"); - ret = PTR_ERR(dp->phy); - if (ret) { - /* - * phy itself is not enabled, so we can move forward - * assigning NULL to phy pointer. - */ - if (ret == -ENOSYS || ret == -ENODEV) - dp->phy = NULL; - else - return ret; - } - } - - if (!dp->panel && !dp->ptn_bridge) { - ret = exynos_dp_dt_parse_panel(dp); - if (ret) - return ret; - } - - dp->clock = devm_clk_get(&pdev->dev, "dp"); - if (IS_ERR(dp->clock)) { - dev_err(&pdev->dev, "failed to get clock\n"); - return PTR_ERR(dp->clock); - } - - clk_prepare_enable(dp->clock); - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - - dp->reg_base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(dp->reg_base)) - return PTR_ERR(dp->reg_base); - - dp->hpd_gpio = of_get_named_gpio(dev->of_node, "samsung,hpd-gpio", 0); - - if (gpio_is_valid(dp->hpd_gpio)) { - /* - * Set up the hotplug GPIO from the device tree as an interrupt. - * Simply specifying a different interrupt in the device tree - * doesn't work since we handle hotplug rather differently when - * using a GPIO. We also need the actual GPIO specifier so - * that we can get the current state of the GPIO. - */ - ret = devm_gpio_request_one(&pdev->dev, dp->hpd_gpio, GPIOF_IN, - "hpd_gpio"); - if (ret) { - dev_err(&pdev->dev, "failed to get hpd gpio\n"); - return ret; - } - dp->irq = gpio_to_irq(dp->hpd_gpio); - irq_flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING; - } else { - dp->hpd_gpio = -ENODEV; - dp->irq = platform_get_irq(pdev, 0); - irq_flags = 0; - } - - if (dp->irq == -ENXIO) { - dev_err(&pdev->dev, "failed to get irq\n"); - return -ENODEV; - } - - INIT_WORK(&dp->hotplug_work, exynos_dp_hotplug); - - ret = devm_request_irq(&pdev->dev, dp->irq, exynos_dp_irq_handler, - irq_flags, "exynos-dp", dp); - if (ret) { - dev_err(&pdev->dev, "failed to request irq\n"); - return ret; - } - disable_irq(dp->irq); - - dp->drm_dev = drm_dev; - - pipe = exynos_drm_crtc_get_pipe_from_type(drm_dev, - EXYNOS_DISPLAY_TYPE_LCD); - if (pipe < 0) - return pipe; - - encoder->possible_crtcs = 1 << pipe; - - DRM_DEBUG_KMS("possible_crtcs = 0x%x\n", encoder->possible_crtcs); - - drm_encoder_init(drm_dev, encoder, &exynos_dp_encoder_funcs, - DRM_MODE_ENCODER_TMDS, NULL); - - drm_encoder_helper_add(encoder, &exynos_dp_encoder_helper_funcs); - - ret = exynos_dp_create_connector(encoder); - if (ret) { - DRM_ERROR("failed to create connector ret = %d\n", ret); - drm_encoder_cleanup(encoder); - return ret; - } - - return 0; -} - -static void exynos_dp_unbind(struct device *dev, struct device *master, - void *data) -{ - struct exynos_dp_device *dp = dev_get_drvdata(dev); - - exynos_dp_disable(&dp->encoder); -} - -static const struct component_ops exynos_dp_ops = { - .bind = exynos_dp_bind, - .unbind = exynos_dp_unbind, -}; - -static int exynos_dp_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct device_node *np = NULL, *endpoint = NULL; - struct exynos_dp_device *dp; - int ret; - - dp = devm_kzalloc(&pdev->dev, sizeof(struct exynos_dp_device), - GFP_KERNEL); - if (!dp) - return -ENOMEM; - - platform_set_drvdata(pdev, dp); - - /* This is for the backward compatibility. */ - np = of_parse_phandle(dev->of_node, "panel", 0); - if (np) { - dp->panel = of_drm_find_panel(np); - of_node_put(np); - if (!dp->panel) - return -EPROBE_DEFER; - goto out; - } - - endpoint = of_graph_get_next_endpoint(dev->of_node, NULL); - if (endpoint) { - np = of_graph_get_remote_port_parent(endpoint); - if (np) { - /* The remote port can be either a panel or a bridge */ - dp->panel = of_drm_find_panel(np); - if (!dp->panel) { - dp->ptn_bridge = of_drm_find_bridge(np); - if (!dp->ptn_bridge) { - of_node_put(np); - return -EPROBE_DEFER; - } - } - of_node_put(np); - } else { - DRM_ERROR("no remote endpoint device node found.\n"); - return -EINVAL; - } - } else { - DRM_ERROR("no port endpoint subnode found.\n"); - return -EINVAL; - } - -out: - pm_runtime_enable(dev); - - ret = component_add(&pdev->dev, &exynos_dp_ops); - if (ret) - goto err_disable_pm_runtime; - - return ret; - -err_disable_pm_runtime: - pm_runtime_disable(dev); - - return ret; -} - -static int exynos_dp_remove(struct platform_device *pdev) -{ - pm_runtime_disable(&pdev->dev); - component_del(&pdev->dev, &exynos_dp_ops); - - return 0; -} - -#ifdef CONFIG_PM -static int exynos_dp_suspend(struct device *dev) -{ - struct exynos_dp_device *dp = dev_get_drvdata(dev); - - clk_disable_unprepare(dp->clock); - - return 0; -} - -static int exynos_dp_resume(struct device *dev) -{ - struct exynos_dp_device *dp = dev_get_drvdata(dev); - int ret; - - ret = clk_prepare_enable(dp->clock); - if (ret < 0) { - DRM_ERROR("Failed to prepare_enable the clock clk [%d]\n", ret); - return ret; - } - - return 0; -} -#endif - -static const struct dev_pm_ops exynos_dp_pm_ops = { - SET_RUNTIME_PM_OPS(exynos_dp_suspend, exynos_dp_resume, NULL) -}; - -static const struct of_device_id exynos_dp_match[] = { - { .compatible = "samsung,exynos5-dp" }, - {}, -}; -MODULE_DEVICE_TABLE(of, exynos_dp_match); - -struct platform_driver dp_driver = { - .probe = exynos_dp_probe, - .remove = exynos_dp_remove, - .driver = { - .name = "exynos-dp", - .owner = THIS_MODULE, - .pm = &exynos_dp_pm_ops, - .of_match_table = exynos_dp_match, - }, -}; - -MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>"); -MODULE_DESCRIPTION("Samsung SoC DP Driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/exynos/exynos_dp_core.h b/drivers/gpu/drm/exynos/exynos_dp_core.h deleted file mode 100644 index b5c2d8f47f9c..000000000000 --- a/drivers/gpu/drm/exynos/exynos_dp_core.h +++ /dev/null @@ -1,282 +0,0 @@ -/* - * Header file for Samsung DP (Display Port) interface driver. - * - * Copyright (C) 2012 Samsung Electronics Co., Ltd. - * Author: Jingoo Han <jg1.han@samsung.com> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - */ - -#ifndef _EXYNOS_DP_CORE_H -#define _EXYNOS_DP_CORE_H - -#include <drm/drm_crtc.h> -#include <drm/drm_dp_helper.h> -#include <drm/exynos_drm.h> -#include <video/videomode.h> - -#include "exynos_drm_drv.h" - -#define DP_TIMEOUT_LOOP_COUNT 100 -#define MAX_CR_LOOP 5 -#define MAX_EQ_LOOP 5 - -enum link_rate_type { - LINK_RATE_1_62GBPS = 0x06, - LINK_RATE_2_70GBPS = 0x0a -}; - -enum link_lane_count_type { - LANE_COUNT1 = 1, - LANE_COUNT2 = 2, - LANE_COUNT4 = 4 -}; - -enum link_training_state { - START, - CLOCK_RECOVERY, - EQUALIZER_TRAINING, - FINISHED, - FAILED -}; - -enum voltage_swing_level { - VOLTAGE_LEVEL_0, - VOLTAGE_LEVEL_1, - VOLTAGE_LEVEL_2, - VOLTAGE_LEVEL_3, -}; - -enum pre_emphasis_level { - PRE_EMPHASIS_LEVEL_0, - PRE_EMPHASIS_LEVEL_1, - PRE_EMPHASIS_LEVEL_2, - PRE_EMPHASIS_LEVEL_3, -}; - -enum pattern_set { - PRBS7, - D10_2, - TRAINING_PTN1, - TRAINING_PTN2, - DP_NONE -}; - -enum color_space { - COLOR_RGB, - COLOR_YCBCR422, - COLOR_YCBCR444 -}; - -enum color_depth { - COLOR_6, - COLOR_8, - COLOR_10, - COLOR_12 -}; - -enum color_coefficient { - COLOR_YCBCR601, - COLOR_YCBCR709 -}; - -enum dynamic_range { - VESA, - CEA -}; - -enum pll_status { - PLL_UNLOCKED, - PLL_LOCKED -}; - -enum clock_recovery_m_value_type { - CALCULATED_M, - REGISTER_M -}; - -enum video_timing_recognition_type { - VIDEO_TIMING_FROM_CAPTURE, - VIDEO_TIMING_FROM_REGISTER -}; - -enum analog_power_block { - AUX_BLOCK, - CH0_BLOCK, - CH1_BLOCK, - CH2_BLOCK, - CH3_BLOCK, - ANALOG_TOTAL, - POWER_ALL -}; - -enum dp_irq_type { - DP_IRQ_TYPE_HP_CABLE_IN, - DP_IRQ_TYPE_HP_CABLE_OUT, - DP_IRQ_TYPE_HP_CHANGE, - DP_IRQ_TYPE_UNKNOWN, -}; - -struct video_info { - char *name; - - bool h_sync_polarity; - bool v_sync_polarity; - bool interlaced; - - enum color_space color_space; - enum dynamic_range dynamic_range; - enum color_coefficient ycbcr_coeff; - enum color_depth color_depth; - - enum link_rate_type link_rate; - enum link_lane_count_type lane_count; -}; - -struct link_train { - int eq_loop; - int cr_loop[4]; - - u8 link_rate; - u8 lane_count; - u8 training_lane[4]; - - enum link_training_state lt_state; -}; - -struct exynos_dp_device { - struct drm_encoder encoder; - struct device *dev; - struct drm_device *drm_dev; - struct drm_connector connector; - struct drm_panel *panel; - struct drm_bridge *bridge; - struct drm_bridge *ptn_bridge; - struct clk *clock; - unsigned int irq; - void __iomem *reg_base; - - struct video_info *video_info; - struct link_train link_train; - struct work_struct hotplug_work; - struct phy *phy; - int dpms_mode; - int hpd_gpio; - struct videomode vm; -}; - -/* exynos_dp_reg.c */ -void exynos_dp_enable_video_mute(struct exynos_dp_device *dp, bool enable); -void exynos_dp_stop_video(struct exynos_dp_device *dp); -void exynos_dp_lane_swap(struct exynos_dp_device *dp, bool enable); -void exynos_dp_init_analog_param(struct exynos_dp_device *dp); -void exynos_dp_init_interrupt(struct exynos_dp_device *dp); -void exynos_dp_reset(struct exynos_dp_device *dp); -void exynos_dp_swreset(struct exynos_dp_device *dp); -void exynos_dp_config_interrupt(struct exynos_dp_device *dp); -enum pll_status exynos_dp_get_pll_lock_status(struct exynos_dp_device *dp); -void exynos_dp_set_pll_power_down(struct exynos_dp_device *dp, bool enable); -void exynos_dp_set_analog_power_down(struct exynos_dp_device *dp, - enum analog_power_block block, - bool enable); -void exynos_dp_init_analog_func(struct exynos_dp_device *dp); -void exynos_dp_init_hpd(struct exynos_dp_device *dp); -enum dp_irq_type exynos_dp_get_irq_type(struct exynos_dp_device *dp); -void exynos_dp_clear_hotplug_interrupts(struct exynos_dp_device *dp); -void exynos_dp_reset_aux(struct exynos_dp_device *dp); -void exynos_dp_init_aux(struct exynos_dp_device *dp); -int exynos_dp_get_plug_in_status(struct exynos_dp_device *dp); -void exynos_dp_enable_sw_function(struct exynos_dp_device *dp); -int exynos_dp_start_aux_transaction(struct exynos_dp_device *dp); -int exynos_dp_write_byte_to_dpcd(struct exynos_dp_device *dp, - unsigned int reg_addr, - unsigned char data); -int exynos_dp_read_byte_from_dpcd(struct exynos_dp_device *dp, - unsigned int reg_addr, - unsigned char *data); -int exynos_dp_write_bytes_to_dpcd(struct exynos_dp_device *dp, - unsigned int reg_addr, - unsigned int count, - unsigned char data[]); -int exynos_dp_read_bytes_from_dpcd(struct exynos_dp_device *dp, - unsigned int reg_addr, - unsigned int count, - unsigned char data[]); -int exynos_dp_select_i2c_device(struct exynos_dp_device *dp, - unsigned int device_addr, - unsigned int reg_addr); -int exynos_dp_read_byte_from_i2c(struct exynos_dp_device *dp, - unsigned int device_addr, - unsigned int reg_addr, - unsigned int *data); -int exynos_dp_read_bytes_from_i2c(struct exynos_dp_device *dp, - unsigned int device_addr, - unsigned int reg_addr, - unsigned int count, - unsigned char edid[]); -void exynos_dp_set_link_bandwidth(struct exynos_dp_device *dp, u32 bwtype); -void exynos_dp_get_link_bandwidth(struct exynos_dp_device *dp, u32 *bwtype); -void exynos_dp_set_lane_count(struct exynos_dp_device *dp, u32 count); -void exynos_dp_get_lane_count(struct exynos_dp_device *dp, u32 *count); -void exynos_dp_enable_enhanced_mode(struct exynos_dp_device *dp, bool enable); -void exynos_dp_set_training_pattern(struct exynos_dp_device *dp, - enum pattern_set pattern); -void exynos_dp_set_lane0_pre_emphasis(struct exynos_dp_device *dp, u32 level); -void exynos_dp_set_lane1_pre_emphasis(struct exynos_dp_device *dp, u32 level); -void exynos_dp_set_lane2_pre_emphasis(struct exynos_dp_device *dp, u32 level); -void exynos_dp_set_lane3_pre_emphasis(struct exynos_dp_device *dp, u32 level); -void exynos_dp_set_lane0_link_training(struct exynos_dp_device *dp, - u32 training_lane); -void exynos_dp_set_lane1_link_training(struct exynos_dp_device *dp, - u32 training_lane); -void exynos_dp_set_lane2_link_training(struct exynos_dp_device *dp, - u32 training_lane); -void exynos_dp_set_lane3_link_training(struct exynos_dp_device *dp, - u32 training_lane); -u32 exynos_dp_get_lane0_link_training(struct exynos_dp_device *dp); -u32 exynos_dp_get_lane1_link_training(struct exynos_dp_device *dp); -u32 exynos_dp_get_lane2_link_training(struct exynos_dp_device *dp); -u32 exynos_dp_get_lane3_link_training(struct exynos_dp_device *dp); -void exynos_dp_reset_macro(struct exynos_dp_device *dp); -void exynos_dp_init_video(struct exynos_dp_device *dp); - -void exynos_dp_set_video_color_format(struct exynos_dp_device *dp); -int exynos_dp_is_slave_video_stream_clock_on(struct exynos_dp_device *dp); -void exynos_dp_set_video_cr_mn(struct exynos_dp_device *dp, - enum clock_recovery_m_value_type type, - u32 m_value, - u32 n_value); -void exynos_dp_set_video_timing_mode(struct exynos_dp_device *dp, u32 type); -void exynos_dp_enable_video_master(struct exynos_dp_device *dp, bool enable); -void exynos_dp_start_video(struct exynos_dp_device *dp); -int exynos_dp_is_video_stream_on(struct exynos_dp_device *dp); -void exynos_dp_config_video_slave_mode(struct exynos_dp_device *dp); -void exynos_dp_enable_scrambling(struct exynos_dp_device *dp); -void exynos_dp_disable_scrambling(struct exynos_dp_device *dp); - -/* I2C EDID Chip ID, Slave Address */ -#define I2C_EDID_DEVICE_ADDR 0x50 -#define I2C_E_EDID_DEVICE_ADDR 0x30 - -#define EDID_BLOCK_LENGTH 0x80 -#define EDID_HEADER_PATTERN 0x00 -#define EDID_EXTENSION_FLAG 0x7e -#define EDID_CHECKSUM 0x7f - -/* DP_MAX_LANE_COUNT */ -#define DPCD_ENHANCED_FRAME_CAP(x) (((x) >> 7) & 0x1) -#define DPCD_MAX_LANE_COUNT(x) ((x) & 0x1f) - -/* DP_LANE_COUNT_SET */ -#define DPCD_LANE_COUNT_SET(x) ((x) & 0x1f) - -/* DP_TRAINING_LANE0_SET */ -#define DPCD_PRE_EMPHASIS_SET(x) (((x) & 0x3) << 3) -#define DPCD_PRE_EMPHASIS_GET(x) (((x) >> 3) & 0x3) -#define DPCD_VOLTAGE_SWING_SET(x) (((x) & 0x3) << 0) -#define DPCD_VOLTAGE_SWING_GET(x) (((x) >> 0) & 0x3) - -#endif /* _EXYNOS_DP_CORE_H */ diff --git a/drivers/gpu/drm/exynos/exynos_dp_reg.c b/drivers/gpu/drm/exynos/exynos_dp_reg.c deleted file mode 100644 index c1f87a2a9284..000000000000 --- a/drivers/gpu/drm/exynos/exynos_dp_reg.c +++ /dev/null @@ -1,1263 +0,0 @@ -/* - * Samsung DP (Display port) register interface driver. - * - * Copyright (C) 2012 Samsung Electronics Co., Ltd. - * Author: Jingoo Han <jg1.han@samsung.com> - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - */ - -#include <linux/device.h> -#include <linux/io.h> -#include <linux/delay.h> -#include <linux/gpio.h> - -#include "exynos_dp_core.h" -#include "exynos_dp_reg.h" - -#define COMMON_INT_MASK_1 0 -#define COMMON_INT_MASK_2 0 -#define COMMON_INT_MASK_3 0 -#define COMMON_INT_MASK_4 (HOTPLUG_CHG | HPD_LOST | PLUG) -#define INT_STA_MASK INT_HPD - -void exynos_dp_enable_video_mute(struct exynos_dp_device *dp, bool enable) -{ - u32 reg; - - if (enable) { - reg = readl(dp->reg_base + EXYNOS_DP_VIDEO_CTL_1); - reg |= HDCP_VIDEO_MUTE; - writel(reg, dp->reg_base + EXYNOS_DP_VIDEO_CTL_1); - } else { - reg = readl(dp->reg_base + EXYNOS_DP_VIDEO_CTL_1); - reg &= ~HDCP_VIDEO_MUTE; - writel(reg, dp->reg_base + EXYNOS_DP_VIDEO_CTL_1); - } -} - -void exynos_dp_stop_video(struct exynos_dp_device *dp) -{ - u32 reg; - - reg = readl(dp->reg_base + EXYNOS_DP_VIDEO_CTL_1); - reg &= ~VIDEO_EN; - writel(reg, dp->reg_base + EXYNOS_DP_VIDEO_CTL_1); -} - -void exynos_dp_lane_swap(struct exynos_dp_device *dp, bool enable) -{ - u32 reg; - - if (enable) - reg = LANE3_MAP_LOGIC_LANE_0 | LANE2_MAP_LOGIC_LANE_1 | - LANE1_MAP_LOGIC_LANE_2 | LANE0_MAP_LOGIC_LANE_3; - else - reg = LANE3_MAP_LOGIC_LANE_3 | LANE2_MAP_LOGIC_LANE_2 | - LANE1_MAP_LOGIC_LANE_1 | LANE0_MAP_LOGIC_LANE_0; - - writel(reg, dp->reg_base + EXYNOS_DP_LANE_MAP); -} - -void exynos_dp_init_analog_param(struct exynos_dp_device *dp) -{ - u32 reg; - - reg = TX_TERMINAL_CTRL_50_OHM; - writel(reg, dp->reg_base + EXYNOS_DP_ANALOG_CTL_1); - - reg = SEL_24M | TX_DVDD_BIT_1_0625V; - writel(reg, dp->reg_base + EXYNOS_DP_ANALOG_CTL_2); - - reg = DRIVE_DVDD_BIT_1_0625V | VCO_BIT_600_MICRO; - writel(reg, dp->reg_base + EXYNOS_DP_ANALOG_CTL_3); - - reg = PD_RING_OSC | AUX_TERMINAL_CTRL_50_OHM | - TX_CUR1_2X | TX_CUR_16_MA; - writel(reg, dp->reg_base + EXYNOS_DP_PLL_FILTER_CTL_1); - - reg = CH3_AMP_400_MV | CH2_AMP_400_MV | - CH1_AMP_400_MV | CH0_AMP_400_MV; - writel(reg, dp->reg_base + EXYNOS_DP_TX_AMP_TUNING_CTL); -} - -void exynos_dp_init_interrupt(struct exynos_dp_device *dp) -{ - /* Set interrupt pin assertion polarity as high */ - writel(INT_POL1 | INT_POL0, dp->reg_base + EXYNOS_DP_INT_CTL); - - /* Clear pending regisers */ - writel(0xff, dp->reg_base + EXYNOS_DP_COMMON_INT_STA_1); - writel(0x4f, dp->reg_base + EXYNOS_DP_COMMON_INT_STA_2); - writel(0xe0, dp->reg_base + EXYNOS_DP_COMMON_INT_STA_3); - writel(0xe7, dp->reg_base + EXYNOS_DP_COMMON_INT_STA_4); - writel(0x63, dp->reg_base + EXYNOS_DP_INT_STA); - - /* 0:mask,1: unmask */ - writel(0x00, dp->reg_base + EXYNOS_DP_COMMON_INT_MASK_1); - writel(0x00, dp->reg_base + EXYNOS_DP_COMMON_INT_MASK_2); - writel(0x00, dp->reg_base + EXYNOS_DP_COMMON_INT_MASK_3); - writel(0x00, dp->reg_base + EXYNOS_DP_COMMON_INT_MASK_4); - writel(0x00, dp->reg_base + EXYNOS_DP_INT_STA_MASK); -} - -void exynos_dp_reset(struct exynos_dp_device *dp) -{ - u32 reg; - - exynos_dp_stop_video(dp); - exynos_dp_enable_video_mute(dp, 0); - - reg = MASTER_VID_FUNC_EN_N | SLAVE_VID_FUNC_EN_N | - AUD_FIFO_FUNC_EN_N | AUD_FUNC_EN_N | - HDCP_FUNC_EN_N | SW_FUNC_EN_N; - writel(reg, dp->reg_base + EXYNOS_DP_FUNC_EN_1); - - reg = SSC_FUNC_EN_N | AUX_FUNC_EN_N | - SERDES_FIFO_FUNC_EN_N | - LS_CLK_DOMAIN_FUNC_EN_N; - writel(reg, dp->reg_base + EXYNOS_DP_FUNC_EN_2); - - usleep_range(20, 30); - - exynos_dp_lane_swap(dp, 0); - - writel(0x0, dp->reg_base + EXYNOS_DP_SYS_CTL_1); - writel(0x40, dp->reg_base + EXYNOS_DP_SYS_CTL_2); - writel(0x0, dp->reg_base + EXYNOS_DP_SYS_CTL_3); - writel(0x0, dp->reg_base + EXYNOS_DP_SYS_CTL_4); - - writel(0x0, dp->reg_base + EXYNOS_DP_PKT_SEND_CTL); - writel(0x0, dp->reg_base + EXYNOS_DP_HDCP_CTL); - - writel(0x5e, dp->reg_base + EXYNOS_DP_HPD_DEGLITCH_L); - writel(0x1a, dp->reg_base + EXYNOS_DP_HPD_DEGLITCH_H); - - writel(0x10, dp->reg_base + EXYNOS_DP_LINK_DEBUG_CTL); - - writel(0x0, dp->reg_base + EXYNOS_DP_PHY_TEST); - - writel(0x0, dp->reg_base + EXYNOS_DP_VIDEO_FIFO_THRD); - writel(0x20, dp->reg_base + EXYNOS_DP_AUDIO_MARGIN); - - writel(0x4, dp->reg_base + EXYNOS_DP_M_VID_GEN_FILTER_TH); - writel(0x2, dp->reg_base + EXYNOS_DP_M_AUD_GEN_FILTER_TH); - - writel(0x00000101, dp->reg_base + EXYNOS_DP_SOC_GENERAL_CTL); -} - -void exynos_dp_swreset(struct exynos_dp_device *dp) -{ - writel(RESET_DP_TX, dp->reg_base + EXYNOS_DP_TX_SW_RESET); -} - -void exynos_dp_config_interrupt(struct exynos_dp_device *dp) -{ - u32 reg; - - /* 0: mask, 1: unmask */ - reg = COMMON_INT_MASK_1; - writel(reg, dp->reg_base + EXYNOS_DP_COMMON_INT_MASK_1); - - reg = COMMON_INT_MASK_2; - writel(reg, dp->reg_base + EXYNOS_DP_COMMON_INT_MASK_2); - - reg = COMMON_INT_MASK_3; - writel(reg, dp->reg_base + EXYNOS_DP_COMMON_INT_MASK_3); - - reg = COMMON_INT_MASK_4; - writel(reg, dp->reg_base + EXYNOS_DP_COMMON_INT_MASK_4); - - reg = INT_STA_MASK; - writel(reg, dp->reg_base + EXYNOS_DP_INT_STA_MASK); -} - -enum pll_status exynos_dp_get_pll_lock_status(struct exynos_dp_device *dp) -{ - u32 reg; - - reg = readl(dp->reg_base + EXYNOS_DP_DEBUG_CTL); - if (reg & PLL_LOCK) - return PLL_LOCKED; - else - return PLL_UNLOCKED; -} - -void exynos_dp_set_pll_power_down(struct exynos_dp_device *dp, bool enable) -{ - u32 reg; - - if (enable) { - reg = readl(dp->reg_base + EXYNOS_DP_PLL_CTL); - reg |= DP_PLL_PD; - writel(reg, dp->reg_base + EXYNOS_DP_PLL_CTL); - } else { - reg = readl(dp->reg_base + EXYNOS_DP_PLL_CTL); - reg &= ~DP_PLL_PD; - writel(reg, dp->reg_base + EXYNOS_DP_PLL_CTL); - } -} - -void exynos_dp_set_analog_power_down(struct exynos_dp_device *dp, - enum analog_power_block block, - bool enable) -{ - u32 reg; - - switch (block) { - case AUX_BLOCK: - if (enable) { - reg = readl(dp->reg_base + EXYNOS_DP_PHY_PD); - reg |= AUX_PD; - writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); - } else { - reg = readl(dp->reg_base + EXYNOS_DP_PHY_PD); - reg &= ~AUX_PD; - writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); - } - break; - case CH0_BLOCK: - if (enable) { - reg = readl(dp->reg_base + EXYNOS_DP_PHY_PD); - reg |= CH0_PD; - writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); - } else { - reg = readl(dp->reg_base + EXYNOS_DP_PHY_PD); - reg &= ~CH0_PD; - writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); - } - break; - case CH1_BLOCK: - if (enable) { - reg = readl(dp->reg_base + EXYNOS_DP_PHY_PD); - reg |= CH1_PD; - writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); - } else { - reg = readl(dp->reg_base + EXYNOS_DP_PHY_PD); - reg &= ~CH1_PD; - writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); - } - break; - case CH2_BLOCK: - if (enable) { - reg = readl(dp->reg_base + EXYNOS_DP_PHY_PD); - reg |= CH2_PD; - writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); - } else { - reg = readl(dp->reg_base + EXYNOS_DP_PHY_PD); - reg &= ~CH2_PD; - writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); - } - break; - case CH3_BLOCK: - if (enable) { - reg = readl(dp->reg_base + EXYNOS_DP_PHY_PD); - reg |= CH3_PD; - writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); - } else { - reg = readl(dp->reg_base + EXYNOS_DP_PHY_PD); - reg &= ~CH3_PD; - writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); - } - break; - case ANALOG_TOTAL: - if (enable) { - reg = readl(dp->reg_base + EXYNOS_DP_PHY_PD); - reg |= DP_PHY_PD; - writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); - } else { - reg = readl(dp->reg_base + EXYNOS_DP_PHY_PD); - reg &= ~DP_PHY_PD; - writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); - } - break; - case POWER_ALL: - if (enable) { - reg = DP_PHY_PD | AUX_PD | CH3_PD | CH2_PD | - CH1_PD | CH0_PD; - writel(reg, dp->reg_base + EXYNOS_DP_PHY_PD); - } else { - writel(0x00, dp->reg_base + EXYNOS_DP_PHY_PD); - } - break; - default: - break; - } -} - -void exynos_dp_init_analog_func(struct exynos_dp_device *dp) -{ - u32 reg; - int timeout_loop = 0; - - exynos_dp_set_analog_power_down(dp, POWER_ALL, 0); - - reg = PLL_LOCK_CHG; - writel(reg, dp->reg_base + EXYNOS_DP_COMMON_INT_STA_1); - - reg = readl(dp->reg_base + EXYNOS_DP_DEBUG_CTL); - reg &= ~(F_PLL_LOCK | PLL_LOCK_CTRL); - writel(reg, dp->reg_base + EXYNOS_DP_DEBUG_CTL); - - /* Power up PLL */ - if (exynos_dp_get_pll_lock_status(dp) == PLL_UNLOCKED) { - exynos_dp_set_pll_power_down(dp, 0); - - while (exynos_dp_get_pll_lock_status(dp) == PLL_UNLOCKED) { - timeout_loop++; - if (DP_TIMEOUT_LOOP_COUNT < timeout_loop) { - dev_err(dp->dev, "failed to get pll lock status\n"); - return; - } - usleep_range(10, 20); - } - } - - /* Enable Serdes FIFO function and Link symbol clock domain module */ - reg = readl(dp->reg_base + EXYNOS_DP_FUNC_EN_2); - reg &= ~(SERDES_FIFO_FUNC_EN_N | LS_CLK_DOMAIN_FUNC_EN_N - | AUX_FUNC_EN_N); - writel(reg, dp->reg_base + EXYNOS_DP_FUNC_EN_2); -} - -void exynos_dp_clear_hotplug_interrupts(struct exynos_dp_device *dp) -{ - u32 reg; - - if (gpio_is_valid(dp->hpd_gpio)) - return; - - reg = HOTPLUG_CHG | HPD_LOST | PLUG; - writel(reg, dp->reg_base + EXYNOS_DP_COMMON_INT_STA_4); - - reg = INT_HPD; - writel(reg, dp->reg_base + EXYNOS_DP_INT_STA); -} - -void exynos_dp_init_hpd(struct exynos_dp_device *dp) -{ - u32 reg; - - if (gpio_is_valid(dp->hpd_gpio)) - return; - - exynos_dp_clear_hotplug_interrupts(dp); - - reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_3); - reg &= ~(F_HPD | HPD_CTRL); - writel(reg, dp->reg_base + EXYNOS_DP_SYS_CTL_3); -} - -enum dp_irq_type exynos_dp_get_irq_type(struct exynos_dp_device *dp) -{ - u32 reg; - - if (gpio_is_valid(dp->hpd_gpio)) { - reg = gpio_get_value(dp->hpd_gpio); - if (reg) - return DP_IRQ_TYPE_HP_CABLE_IN; - else - return DP_IRQ_TYPE_HP_CABLE_OUT; - } else { - /* Parse hotplug interrupt status register */ - reg = readl(dp->reg_base + EXYNOS_DP_COMMON_INT_STA_4); - - if (reg & PLUG) - return DP_IRQ_TYPE_HP_CABLE_IN; - - if (reg & HPD_LOST) - return DP_IRQ_TYPE_HP_CABLE_OUT; - - if (reg & HOTPLUG_CHG) - return DP_IRQ_TYPE_HP_CHANGE; - - return DP_IRQ_TYPE_UNKNOWN; - } -} - -void exynos_dp_reset_aux(struct exynos_dp_device *dp) -{ - u32 reg; - - /* Disable AUX channel module */ - reg = readl(dp->reg_base + EXYNOS_DP_FUNC_EN_2); - reg |= AUX_FUNC_EN_N; - writel(reg, dp->reg_base + EXYNOS_DP_FUNC_EN_2); -} - -void exynos_dp_init_aux(struct exynos_dp_device *dp) -{ - u32 reg; - - /* Clear inerrupts related to AUX channel */ - reg = RPLY_RECEIV | AUX_ERR; - writel(reg, dp->reg_base + EXYNOS_DP_INT_STA); - - exynos_dp_reset_aux(dp); - - /* Disable AUX transaction H/W retry */ - reg = AUX_BIT_PERIOD_EXPECTED_DELAY(3) | AUX_HW_RETRY_COUNT_SEL(0)| - AUX_HW_RETRY_INTERVAL_600_MICROSECONDS; - writel(reg, dp->reg_base + EXYNOS_DP_AUX_HW_RETRY_CTL); - - /* Receive AUX Channel DEFER commands equal to DEFFER_COUNT*64 */ - reg = DEFER_CTRL_EN | DEFER_COUNT(1); - writel(reg, dp->reg_base + EXYNOS_DP_AUX_CH_DEFER_CTL); - - /* Enable AUX channel module */ - reg = readl(dp->reg_base + EXYNOS_DP_FUNC_EN_2); - reg &= ~AUX_FUNC_EN_N; - writel(reg, dp->reg_base + EXYNOS_DP_FUNC_EN_2); -} - -int exynos_dp_get_plug_in_status(struct exynos_dp_device *dp) -{ - u32 reg; - - if (gpio_is_valid(dp->hpd_gpio)) { - if (gpio_get_value(dp->hpd_gpio)) - return 0; - } else { - reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_3); - if (reg & HPD_STATUS) - return 0; - } - - return -EINVAL; -} - -void exynos_dp_enable_sw_function(struct exynos_dp_device *dp) -{ - u32 reg; - - reg = readl(dp->reg_base + EXYNOS_DP_FUNC_EN_1); - reg &= ~SW_FUNC_EN_N; - writel(reg, dp->reg_base + EXYNOS_DP_FUNC_EN_1); -} - -int exynos_dp_start_aux_transaction(struct exynos_dp_device *dp) -{ - int reg; - int retval = 0; - int timeout_loop = 0; - - /* Enable AUX CH operation */ - reg = readl(dp->reg_base + EXYNOS_DP_AUX_CH_CTL_2); - reg |= AUX_EN; - writel(reg, dp->reg_base + EXYNOS_DP_AUX_CH_CTL_2); - - /* Is AUX CH command reply received? */ - reg = readl(dp->reg_base + EXYNOS_DP_INT_STA); - while (!(reg & RPLY_RECEIV)) { - timeout_loop++; - if (DP_TIMEOUT_LOOP_COUNT < timeout_loop) { - dev_err(dp->dev, "AUX CH command reply failed!\n"); - return -ETIMEDOUT; - } - reg = readl(dp->reg_base + EXYNOS_DP_INT_STA); - usleep_range(10, 11); - } - - /* Clear interrupt source for AUX CH command reply */ - writel(RPLY_RECEIV, dp->reg_base + EXYNOS_DP_INT_STA); - - /* Clear interrupt source for AUX CH access error */ - reg = readl(dp->reg_base + EXYNOS_DP_INT_STA); - if (reg & AUX_ERR) { - writel(AUX_ERR, dp->reg_base + EXYNOS_DP_INT_STA); - return -EREMOTEIO; - } - - /* Check AUX CH error access status */ - reg = readl(dp->reg_base + EXYNOS_DP_AUX_CH_STA); - if ((reg & AUX_STATUS_MASK) != 0) { - dev_err(dp->dev, "AUX CH error happens: %d\n\n", - reg & AUX_STATUS_MASK); - return -EREMOTEIO; - } - - return retval; -} - -int exynos_dp_write_byte_to_dpcd(struct exynos_dp_device *dp, - unsigned int reg_addr, - unsigned char data) -{ - u32 reg; - int i; - int retval; - - for (i = 0; i < 3; i++) { - /* Clear AUX CH data buffer */ - reg = BUF_CLR; - writel(reg, dp->reg_base + EXYNOS_DP_BUFFER_DATA_CTL); - - /* Select DPCD device address */ - reg = AUX_ADDR_7_0(reg_addr); - writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_7_0); - reg = AUX_ADDR_15_8(reg_addr); - writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_15_8); - reg = AUX_ADDR_19_16(reg_addr); - writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_19_16); - - /* Write data buffer */ - reg = (unsigned int)data; - writel(reg, dp->reg_base + EXYNOS_DP_BUF_DATA_0); - - /* - * Set DisplayPort transaction and write 1 byte - * If bit 3 is 1, DisplayPort transaction. - * If Bit 3 is 0, I2C transaction. - */ - reg = AUX_TX_COMM_DP_TRANSACTION | AUX_TX_COMM_WRITE; - writel(reg, dp->reg_base + EXYNOS_DP_AUX_CH_CTL_1); - - /* Start AUX transaction */ - retval = exynos_dp_start_aux_transaction(dp); - if (retval == 0) - break; - else - dev_dbg(dp->dev, "%s: Aux Transaction fail!\n", - __func__); - } - - return retval; -} - -int exynos_dp_read_byte_from_dpcd(struct exynos_dp_device *dp, - unsigned int reg_addr, - unsigned char *data) -{ - u32 reg; - int i; - int retval; - - for (i = 0; i < 3; i++) { - /* Clear AUX CH data buffer */ - reg = BUF_CLR; - writel(reg, dp->reg_base + EXYNOS_DP_BUFFER_DATA_CTL); - - /* Select DPCD device address */ - reg = AUX_ADDR_7_0(reg_addr); - writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_7_0); - reg = AUX_ADDR_15_8(reg_addr); - writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_15_8); - reg = AUX_ADDR_19_16(reg_addr); - writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_19_16); - - /* - * Set DisplayPort transaction and read 1 byte - * If bit 3 is 1, DisplayPort transaction. - * If Bit 3 is 0, I2C transaction. - */ - reg = AUX_TX_COMM_DP_TRANSACTION | AUX_TX_COMM_READ; - writel(reg, dp->reg_base + EXYNOS_DP_AUX_CH_CTL_1); - - /* Start AUX transaction */ - retval = exynos_dp_start_aux_transaction(dp); - if (retval == 0) - break; - else - dev_dbg(dp->dev, "%s: Aux Transaction fail!\n", - __func__); - } - - /* Read data buffer */ - reg = readl(dp->reg_base + EXYNOS_DP_BUF_DATA_0); - *data = (unsigned char)(reg & 0xff); - - return retval; -} - -int exynos_dp_write_bytes_to_dpcd(struct exynos_dp_device *dp, - unsigned int reg_addr, - unsigned int count, - unsigned char data[]) -{ - u32 reg; - unsigned int start_offset; - unsigned int cur_data_count; - unsigned int cur_data_idx; - int i; - int retval = 0; - - /* Clear AUX CH data buffer */ - reg = BUF_CLR; - writel(reg, dp->reg_base + EXYNOS_DP_BUFFER_DATA_CTL); - - start_offset = 0; - while (start_offset < count) { - /* Buffer size of AUX CH is 16 * 4bytes */ - if ((count - start_offset) > 16) - cur_data_count = 16; - else - cur_data_count = count - start_offset; - - for (i = 0; i < 3; i++) { - /* Select DPCD device address */ - reg = AUX_ADDR_7_0(reg_addr + start_offset); - writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_7_0); - reg = AUX_ADDR_15_8(reg_addr + start_offset); - writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_15_8); - reg = AUX_ADDR_19_16(reg_addr + start_offset); - writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_19_16); - - for (cur_data_idx = 0; cur_data_idx < cur_data_count; - cur_data_idx++) { - reg = data[start_offset + cur_data_idx]; - writel(reg, dp->reg_base + EXYNOS_DP_BUF_DATA_0 - + 4 * cur_data_idx); - } - - /* - * Set DisplayPort transaction and write - * If bit 3 is 1, DisplayPort transaction. - * If Bit 3 is 0, I2C transaction. - */ - reg = AUX_LENGTH(cur_data_count) | - AUX_TX_COMM_DP_TRANSACTION | AUX_TX_COMM_WRITE; - writel(reg, dp->reg_base + EXYNOS_DP_AUX_CH_CTL_1); - - /* Start AUX transaction */ - retval = exynos_dp_start_aux_transaction(dp); - if (retval == 0) - break; - else - dev_dbg(dp->dev, "%s: Aux Transaction fail!\n", - __func__); - } - - start_offset += cur_data_count; - } - - return retval; -} - -int exynos_dp_read_bytes_from_dpcd(struct exynos_dp_device *dp, - unsigned int reg_addr, - unsigned int count, - unsigned char data[]) -{ - u32 reg; - unsigned int start_offset; - unsigned int cur_data_count; - unsigned int cur_data_idx; - int i; - int retval = 0; - - /* Clear AUX CH data buffer */ - reg = BUF_CLR; - writel(reg, dp->reg_base + EXYNOS_DP_BUFFER_DATA_CTL); - - start_offset = 0; - while (start_offset < count) { - /* Buffer size of AUX CH is 16 * 4bytes */ - if ((count - start_offset) > 16) - cur_data_count = 16; - else - cur_data_count = count - start_offset; - - /* AUX CH Request Transaction process */ - for (i = 0; i < 3; i++) { - /* Select DPCD device address */ - reg = AUX_ADDR_7_0(reg_addr + start_offset); - writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_7_0); - reg = AUX_ADDR_15_8(reg_addr + start_offset); - writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_15_8); - reg = AUX_ADDR_19_16(reg_addr + start_offset); - writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_19_16); - - /* - * Set DisplayPort transaction and read - * If bit 3 is 1, DisplayPort transaction. - * If Bit 3 is 0, I2C transaction. - */ - reg = AUX_LENGTH(cur_data_count) | - AUX_TX_COMM_DP_TRANSACTION | AUX_TX_COMM_READ; - writel(reg, dp->reg_base + EXYNOS_DP_AUX_CH_CTL_1); - - /* Start AUX transaction */ - retval = exynos_dp_start_aux_transaction(dp); - if (retval == 0) - break; - else - dev_dbg(dp->dev, "%s: Aux Transaction fail!\n", - __func__); - } - - for (cur_data_idx = 0; cur_data_idx < cur_data_count; - cur_data_idx++) { - reg = readl(dp->reg_base + EXYNOS_DP_BUF_DATA_0 - + 4 * cur_data_idx); - data[start_offset + cur_data_idx] = - (unsigned char)reg; - } - - start_offset += cur_data_count; - } - - return retval; -} - -int exynos_dp_select_i2c_device(struct exynos_dp_device *dp, - unsigned int device_addr, - unsigned int reg_addr) -{ - u32 reg; - int retval; - - /* Set EDID device address */ - reg = device_addr; - writel(reg, dp->reg_base + EXYNOS_DP_AUX_ADDR_7_0); - writel(0x0, dp->reg_base + EXYNOS_DP_AUX_ADDR_15_8); - writel(0x0, dp->reg_base + EXYNOS_DP_AUX_ADDR_19_16); - - /* Set offset from base address of EDID device */ - writel(reg_addr, dp->reg_base + EXYNOS_DP_BUF_DATA_0); - - /* - * Set I2C transaction and write address - * If bit 3 is 1, DisplayPort transaction. - * If Bit 3 is 0, I2C transaction. - */ - reg = AUX_TX_COMM_I2C_TRANSACTION | AUX_TX_COMM_MOT | - AUX_TX_COMM_WRITE; - writel(reg, dp->reg_base + EXYNOS_DP_AUX_CH_CTL_1); - - /* Start AUX transaction */ - retval = exynos_dp_start_aux_transaction(dp); - if (retval != 0) - dev_dbg(dp->dev, "%s: Aux Transaction fail!\n", __func__); - - return retval; -} - -int exynos_dp_read_byte_from_i2c(struct exynos_dp_device *dp, - unsigned int device_addr, - unsigned int reg_addr, - unsigned int *data) -{ - u32 reg; - int i; - int retval; - - for (i = 0; i < 3; i++) { - /* Clear AUX CH data buffer */ - reg = BUF_CLR; - writel(reg, dp->reg_base + EXYNOS_DP_BUFFER_DATA_CTL); - - /* Select EDID device */ - retval = exynos_dp_select_i2c_device(dp, device_addr, reg_addr); - if (retval != 0) - continue; - - /* - * Set I2C transaction and read data - * If bit 3 is 1, DisplayPort transaction. - * If Bit 3 is 0, I2C transaction. - */ - reg = AUX_TX_COMM_I2C_TRANSACTION | - AUX_TX_COMM_READ; - writel(reg, dp->reg_base + EXYNOS_DP_AUX_CH_CTL_1); - - /* Start AUX transaction */ - retval = exynos_dp_start_aux_transaction(dp); - if (retval == 0) - break; - else - dev_dbg(dp->dev, "%s: Aux Transaction fail!\n", - __func__); - } - - /* Read data */ - if (retval == 0) - *data = readl(dp->reg_base + EXYNOS_DP_BUF_DATA_0); - - return retval; -} - -int exynos_dp_read_bytes_from_i2c(struct exynos_dp_device *dp, - unsigned int device_addr, - unsigned int reg_addr, - unsigned int count, - unsigned char edid[]) -{ - u32 reg; - unsigned int i, j; - unsigned int cur_data_idx; - unsigned int defer = 0; - int retval = 0; - - for (i = 0; i < count; i += 16) { - for (j = 0; j < 3; j++) { - /* Clear AUX CH data buffer */ - reg = BUF_CLR; - writel(reg, dp->reg_base + EXYNOS_DP_BUFFER_DATA_CTL); - - /* Set normal AUX CH command */ - reg = readl(dp->reg_base + EXYNOS_DP_AUX_CH_CTL_2); - reg &= ~ADDR_ONLY; - writel(reg, dp->reg_base + EXYNOS_DP_AUX_CH_CTL_2); - - /* - * If Rx sends defer, Tx sends only reads - * request without sending address - */ - if (!defer) - retval = exynos_dp_select_i2c_device(dp, - device_addr, reg_addr + i); - else - defer = 0; - - if (retval == 0) { - /* - * Set I2C transaction and write data - * If bit 3 is 1, DisplayPort transaction. - * If Bit 3 is 0, I2C transaction. - */ - reg = AUX_LENGTH(16) | - AUX_TX_COMM_I2C_TRANSACTION | - AUX_TX_COMM_READ; - writel(reg, dp->reg_base + - EXYNOS_DP_AUX_CH_CTL_1); - - /* Start AUX transaction */ - retval = exynos_dp_start_aux_transaction(dp); - if (retval == 0) - break; - else - dev_dbg(dp->dev, - "%s: Aux Transaction fail!\n", - __func__); - } - /* Check if Rx sends defer */ - reg = readl(dp->reg_base + EXYNOS_DP_AUX_RX_COMM); - if (reg == AUX_RX_COMM_AUX_DEFER || - reg == AUX_RX_COMM_I2C_DEFER) { - dev_err(dp->dev, "Defer: %d\n\n", reg); - defer = 1; - } - } - - for (cur_data_idx = 0; cur_data_idx < 16; cur_data_idx++) { - reg = readl(dp->reg_base + EXYNOS_DP_BUF_DATA_0 - + 4 * cur_data_idx); - edid[i + cur_data_idx] = (unsigned char)reg; - } - } - - return retval; -} - -void exynos_dp_set_link_bandwidth(struct exynos_dp_device *dp, u32 bwtype) -{ - u32 reg; - - reg = bwtype; - if ((bwtype == LINK_RATE_2_70GBPS) || (bwtype == LINK_RATE_1_62GBPS)) - writel(reg, dp->reg_base + EXYNOS_DP_LINK_BW_SET); -} - -void exynos_dp_get_link_bandwidth(struct exynos_dp_device *dp, u32 *bwtype) -{ - u32 reg; - - reg = readl(dp->reg_base + EXYNOS_DP_LINK_BW_SET); - *bwtype = reg; -} - -void exynos_dp_set_lane_count(struct exynos_dp_device *dp, u32 count) -{ - u32 reg; - - reg = count; - writel(reg, dp->reg_base + EXYNOS_DP_LANE_COUNT_SET); -} - -void exynos_dp_get_lane_count(struct exynos_dp_device *dp, u32 *count) -{ - u32 reg; - - reg = readl(dp->reg_base + EXYNOS_DP_LANE_COUNT_SET); - *count = reg; -} - -void exynos_dp_enable_enhanced_mode(struct exynos_dp_device *dp, bool enable) -{ - u32 reg; - - if (enable) { - reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_4); - reg |= ENHANCED; - writel(reg, dp->reg_base + EXYNOS_DP_SYS_CTL_4); - } else { - reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_4); - reg &= ~ENHANCED; - writel(reg, dp->reg_base + EXYNOS_DP_SYS_CTL_4); - } -} - -void exynos_dp_set_training_pattern(struct exynos_dp_device *dp, - enum pattern_set pattern) -{ - u32 reg; - - switch (pattern) { - case PRBS7: - reg = SCRAMBLING_ENABLE | LINK_QUAL_PATTERN_SET_PRBS7; - writel(reg, dp->reg_base + EXYNOS_DP_TRAINING_PTN_SET); - break; - case D10_2: - reg = SCRAMBLING_ENABLE | LINK_QUAL_PATTERN_SET_D10_2; - writel(reg, dp->reg_base + EXYNOS_DP_TRAINING_PTN_SET); - break; - case TRAINING_PTN1: - reg = SCRAMBLING_DISABLE | SW_TRAINING_PATTERN_SET_PTN1; - writel(reg, dp->reg_base + EXYNOS_DP_TRAINING_PTN_SET); - break; - case TRAINING_PTN2: - reg = SCRAMBLING_DISABLE | SW_TRAINING_PATTERN_SET_PTN2; - writel(reg, dp->reg_base + EXYNOS_DP_TRAINING_PTN_SET); - break; - case DP_NONE: - reg = SCRAMBLING_ENABLE | - LINK_QUAL_PATTERN_SET_DISABLE | - SW_TRAINING_PATTERN_SET_NORMAL; - writel(reg, dp->reg_base + EXYNOS_DP_TRAINING_PTN_SET); - break; - default: - break; - } -} - -void exynos_dp_set_lane0_pre_emphasis(struct exynos_dp_device *dp, u32 level) -{ - u32 reg; - - reg = readl(dp->reg_base + EXYNOS_DP_LN0_LINK_TRAINING_CTL); - reg &= ~PRE_EMPHASIS_SET_MASK; - reg |= level << PRE_EMPHASIS_SET_SHIFT; - writel(reg, dp->reg_base + EXYNOS_DP_LN0_LINK_TRAINING_CTL); -} - -void exynos_dp_set_lane1_pre_emphasis(struct exynos_dp_device *dp, u32 level) -{ - u32 reg; - - reg = readl(dp->reg_base + EXYNOS_DP_LN1_LINK_TRAINING_CTL); - reg &= ~PRE_EMPHASIS_SET_MASK; - reg |= level << PRE_EMPHASIS_SET_SHIFT; - writel(reg, dp->reg_base + EXYNOS_DP_LN1_LINK_TRAINING_CTL); -} - -void exynos_dp_set_lane2_pre_emphasis(struct exynos_dp_device *dp, u32 level) -{ - u32 reg; - - reg = readl(dp->reg_base + EXYNOS_DP_LN2_LINK_TRAINING_CTL); - reg &= ~PRE_EMPHASIS_SET_MASK; - reg |= level << PRE_EMPHASIS_SET_SHIFT; - writel(reg, dp->reg_base + EXYNOS_DP_LN2_LINK_TRAINING_CTL); -} - -void exynos_dp_set_lane3_pre_emphasis(struct exynos_dp_device *dp, u32 level) -{ - u32 reg; - - reg = readl(dp->reg_base + EXYNOS_DP_LN3_LINK_TRAINING_CTL); - reg &= ~PRE_EMPHASIS_SET_MASK; - reg |= level << PRE_EMPHASIS_SET_SHIFT; - writel(reg, dp->reg_base + EXYNOS_DP_LN3_LINK_TRAINING_CTL); -} - -void exynos_dp_set_lane0_link_training(struct exynos_dp_device *dp, - u32 training_lane) -{ - u32 reg; - - reg = training_lane; - writel(reg, dp->reg_base + EXYNOS_DP_LN0_LINK_TRAINING_CTL); -} - -void exynos_dp_set_lane1_link_training(struct exynos_dp_device *dp, - u32 training_lane) -{ - u32 reg; - - reg = training_lane; - writel(reg, dp->reg_base + EXYNOS_DP_LN1_LINK_TRAINING_CTL); -} - -void exynos_dp_set_lane2_link_training(struct exynos_dp_device *dp, - u32 training_lane) -{ - u32 reg; - - reg = training_lane; - writel(reg, dp->reg_base + EXYNOS_DP_LN2_LINK_TRAINING_CTL); -} - -void exynos_dp_set_lane3_link_training(struct exynos_dp_device *dp, - u32 training_lane) -{ - u32 reg; - - reg = training_lane; - writel(reg, dp->reg_base + EXYNOS_DP_LN3_LINK_TRAINING_CTL); -} - -u32 exynos_dp_get_lane0_link_training(struct exynos_dp_device *dp) -{ - u32 reg; - - reg = readl(dp->reg_base + EXYNOS_DP_LN0_LINK_TRAINING_CTL); - return reg; -} - -u32 exynos_dp_get_lane1_link_training(struct exynos_dp_device *dp) -{ - u32 reg; - - reg = readl(dp->reg_base + EXYNOS_DP_LN1_LINK_TRAINING_CTL); - return reg; -} - -u32 exynos_dp_get_lane2_link_training(struct exynos_dp_device *dp) -{ - u32 reg; - - reg = readl(dp->reg_base + EXYNOS_DP_LN2_LINK_TRAINING_CTL); - return reg; -} - -u32 exynos_dp_get_lane3_link_training(struct exynos_dp_device *dp) -{ - u32 reg; - - reg = readl(dp->reg_base + EXYNOS_DP_LN3_LINK_TRAINING_CTL); - return reg; -} - -void exynos_dp_reset_macro(struct exynos_dp_device *dp) -{ - u32 reg; - - reg = readl(dp->reg_base + EXYNOS_DP_PHY_TEST); - reg |= MACRO_RST; - writel(reg, dp->reg_base + EXYNOS_DP_PHY_TEST); - - /* 10 us is the minimum reset time. */ - usleep_range(10, 20); - - reg &= ~MACRO_RST; - writel(reg, dp->reg_base + EXYNOS_DP_PHY_TEST); -} - -void exynos_dp_init_video(struct exynos_dp_device *dp) -{ - u32 reg; - - reg = VSYNC_DET | VID_FORMAT_CHG | VID_CLK_CHG; - writel(reg, dp->reg_base + EXYNOS_DP_COMMON_INT_STA_1); - - reg = 0x0; - writel(reg, dp->reg_base + EXYNOS_DP_SYS_CTL_1); - - reg = CHA_CRI(4) | CHA_CTRL; - writel(reg, dp->reg_base + EXYNOS_DP_SYS_CTL_2); - - reg = 0x0; - writel(reg, dp->reg_base + EXYNOS_DP_SYS_CTL_3); - - reg = VID_HRES_TH(2) | VID_VRES_TH(0); - writel(reg, dp->reg_base + EXYNOS_DP_VIDEO_CTL_8); -} - -void exynos_dp_set_video_color_format(struct exynos_dp_device *dp) -{ - u32 reg; - - /* Configure the input color depth, color space, dynamic range */ - reg = (dp->video_info->dynamic_range << IN_D_RANGE_SHIFT) | - (dp->video_info->color_depth << IN_BPC_SHIFT) | - (dp->video_info->color_space << IN_COLOR_F_SHIFT); - writel(reg, dp->reg_base + EXYNOS_DP_VIDEO_CTL_2); - - /* Set Input Color YCbCr Coefficients to ITU601 or ITU709 */ - reg = readl(dp->reg_base + EXYNOS_DP_VIDEO_CTL_3); - reg &= ~IN_YC_COEFFI_MASK; - if (dp->video_info->ycbcr_coeff) - reg |= IN_YC_COEFFI_ITU709; - else - reg |= IN_YC_COEFFI_ITU601; - writel(reg, dp->reg_base + EXYNOS_DP_VIDEO_CTL_3); -} - -int exynos_dp_is_slave_video_stream_clock_on(struct exynos_dp_device *dp) -{ - u32 reg; - - reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_1); - writel(reg, dp->reg_base + EXYNOS_DP_SYS_CTL_1); - - reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_1); - - if (!(reg & DET_STA)) { - dev_dbg(dp->dev, "Input stream clock not detected.\n"); - return -EINVAL; - } - - reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_2); - writel(reg, dp->reg_base + EXYNOS_DP_SYS_CTL_2); - - reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_2); - dev_dbg(dp->dev, "wait SYS_CTL_2.\n"); - - if (reg & CHA_STA) { - dev_dbg(dp->dev, "Input stream clk is changing\n"); - return -EINVAL; - } - - return 0; -} - -void exynos_dp_set_video_cr_mn(struct exynos_dp_device *dp, - enum clock_recovery_m_value_type type, - u32 m_value, - u32 n_value) -{ - u32 reg; - - if (type == REGISTER_M) { - reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_4); - reg |= FIX_M_VID; - writel(reg, dp->reg_base + EXYNOS_DP_SYS_CTL_4); - reg = m_value & 0xff; - writel(reg, dp->reg_base + EXYNOS_DP_M_VID_0); - reg = (m_value >> 8) & 0xff; - writel(reg, dp->reg_base + EXYNOS_DP_M_VID_1); - reg = (m_value >> 16) & 0xff; - writel(reg, dp->reg_base + EXYNOS_DP_M_VID_2); - - reg = n_value & 0xff; - writel(reg, dp->reg_base + EXYNOS_DP_N_VID_0); - reg = (n_value >> 8) & 0xff; - writel(reg, dp->reg_base + EXYNOS_DP_N_VID_1); - reg = (n_value >> 16) & 0xff; - writel(reg, dp->reg_base + EXYNOS_DP_N_VID_2); - } else { - reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_4); - reg &= ~FIX_M_VID; - writel(reg, dp->reg_base + EXYNOS_DP_SYS_CTL_4); - - writel(0x00, dp->reg_base + EXYNOS_DP_N_VID_0); - writel(0x80, dp->reg_base + EXYNOS_DP_N_VID_1); - writel(0x00, dp->reg_base + EXYNOS_DP_N_VID_2); - } -} - -void exynos_dp_set_video_timing_mode(struct exynos_dp_device *dp, u32 type) -{ - u32 reg; - - if (type == VIDEO_TIMING_FROM_CAPTURE) { - reg = readl(dp->reg_base + EXYNOS_DP_VIDEO_CTL_10); - reg &= ~FORMAT_SEL; - writel(reg, dp->reg_base + EXYNOS_DP_VIDEO_CTL_10); - } else { - reg = readl(dp->reg_base + EXYNOS_DP_VIDEO_CTL_10); - reg |= FORMAT_SEL; - writel(reg, dp->reg_base + EXYNOS_DP_VIDEO_CTL_10); - } -} - -void exynos_dp_enable_video_master(struct exynos_dp_device *dp, bool enable) -{ - u32 reg; - - if (enable) { - reg = readl(dp->reg_base + EXYNOS_DP_SOC_GENERAL_CTL); - reg &= ~VIDEO_MODE_MASK; - reg |= VIDEO_MASTER_MODE_EN | VIDEO_MODE_MASTER_MODE; - writel(reg, dp->reg_base + EXYNOS_DP_SOC_GENERAL_CTL); - } else { - reg = readl(dp->reg_base + EXYNOS_DP_SOC_GENERAL_CTL); - reg &= ~VIDEO_MODE_MASK; - reg |= VIDEO_MODE_SLAVE_MODE; - writel(reg, dp->reg_base + EXYNOS_DP_SOC_GENERAL_CTL); - } -} - -void exynos_dp_start_video(struct exynos_dp_device *dp) -{ - u32 reg; - - reg = readl(dp->reg_base + EXYNOS_DP_VIDEO_CTL_1); - reg |= VIDEO_EN; - writel(reg, dp->reg_base + EXYNOS_DP_VIDEO_CTL_1); -} - -int exynos_dp_is_video_stream_on(struct exynos_dp_device *dp) -{ - u32 reg; - - reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_3); - writel(reg, dp->reg_base + EXYNOS_DP_SYS_CTL_3); - - reg = readl(dp->reg_base + EXYNOS_DP_SYS_CTL_3); - if (!(reg & STRM_VALID)) { - dev_dbg(dp->dev, "Input video stream is not detected.\n"); - return -EINVAL; - } - - return 0; -} - -void exynos_dp_config_video_slave_mode(struct exynos_dp_device *dp) -{ - u32 reg; - - reg = readl(dp->reg_base + EXYNOS_DP_FUNC_EN_1); - reg &= ~(MASTER_VID_FUNC_EN_N|SLAVE_VID_FUNC_EN_N); - reg |= MASTER_VID_FUNC_EN_N; - writel(reg, dp->reg_base + EXYNOS_DP_FUNC_EN_1); - - reg = readl(dp->reg_base + EXYNOS_DP_VIDEO_CTL_10); - reg &= ~INTERACE_SCAN_CFG; - reg |= (dp->video_info->interlaced << 2); - writel(reg, dp->reg_base + EXYNOS_DP_VIDEO_CTL_10); - - reg = readl(dp->reg_base + EXYNOS_DP_VIDEO_CTL_10); - reg &= ~VSYNC_POLARITY_CFG; - reg |= (dp->video_info->v_sync_polarity << 1); - writel(reg, dp->reg_base + EXYNOS_DP_VIDEO_CTL_10); - - reg = readl(dp->reg_base + EXYNOS_DP_VIDEO_CTL_10); - reg &= ~HSYNC_POLARITY_CFG; - reg |= (dp->video_info->h_sync_polarity << 0); - writel(reg, dp->reg_base + EXYNOS_DP_VIDEO_CTL_10); - - reg = AUDIO_MODE_SPDIF_MODE | VIDEO_MODE_SLAVE_MODE; - writel(reg, dp->reg_base + EXYNOS_DP_SOC_GENERAL_CTL); -} - -void exynos_dp_enable_scrambling(struct exynos_dp_device *dp) -{ - u32 reg; - - reg = readl(dp->reg_base + EXYNOS_DP_TRAINING_PTN_SET); - reg &= ~SCRAMBLING_DISABLE; - writel(reg, dp->reg_base + EXYNOS_DP_TRAINING_PTN_SET); -} - -void exynos_dp_disable_scrambling(struct exynos_dp_device *dp) -{ - u32 reg; - - reg = readl(dp->reg_base + EXYNOS_DP_TRAINING_PTN_SET); - reg |= SCRAMBLING_DISABLE; - writel(reg, dp->reg_base + EXYNOS_DP_TRAINING_PTN_SET); -} diff --git a/drivers/gpu/drm/exynos/exynos_drm_fbdev.c b/drivers/gpu/drm/exynos/exynos_drm_fbdev.c index 4ae860c44f1d..4656cd6e7083 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_fbdev.c +++ b/drivers/gpu/drm/exynos/exynos_drm_fbdev.c @@ -138,8 +138,6 @@ static int exynos_drm_fbdev_create(struct drm_fb_helper *helper, mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, sizes->surface_depth); - mutex_lock(&dev->struct_mutex); - size = mode_cmd.pitches[0] * mode_cmd.height; exynos_gem = exynos_drm_gem_create(dev, EXYNOS_BO_CONTIG, size); @@ -154,10 +152,8 @@ static int exynos_drm_fbdev_create(struct drm_fb_helper *helper, size); } - if (IS_ERR(exynos_gem)) { - ret = PTR_ERR(exynos_gem); - goto out; - } + if (IS_ERR(exynos_gem)) + return PTR_ERR(exynos_gem); exynos_fbdev->exynos_gem = exynos_gem; @@ -173,7 +169,6 @@ static int exynos_drm_fbdev_create(struct drm_fb_helper *helper, if (ret < 0) goto err_destroy_framebuffer; - mutex_unlock(&dev->struct_mutex); return ret; err_destroy_framebuffer: @@ -181,13 +176,12 @@ err_destroy_framebuffer: err_destroy_gem: exynos_drm_gem_destroy(exynos_gem); -/* - * if failed, all resources allocated above would be released by - * drm_mode_config_cleanup() when drm_load() had been called prior - * to any specific driver such as fimd or hdmi driver. - */ -out: - mutex_unlock(&dev->struct_mutex); + /* + * if failed, all resources allocated above would be released by + * drm_mode_config_cleanup() when drm_load() had been called prior + * to any specific driver such as fimd or hdmi driver. + */ + return ret; } diff --git a/drivers/gpu/drm/exynos/exynos_drm_gem.c b/drivers/gpu/drm/exynos/exynos_drm_gem.c index 2914d62d0d80..6fb98f4c3544 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_gem.c +++ b/drivers/gpu/drm/exynos/exynos_drm_gem.c @@ -362,12 +362,9 @@ int exynos_drm_gem_get_ioctl(struct drm_device *dev, void *data, struct drm_exynos_gem_info *args = data; struct drm_gem_object *obj; - mutex_lock(&dev->struct_mutex); - obj = drm_gem_object_lookup(dev, file_priv, args->handle); if (!obj) { DRM_ERROR("failed to lookup gem object.\n"); - mutex_unlock(&dev->struct_mutex); return -EINVAL; } @@ -376,8 +373,7 @@ int exynos_drm_gem_get_ioctl(struct drm_device *dev, void *data, args->flags = exynos_gem->flags; args->size = exynos_gem->size; - drm_gem_object_unreference(obj); - mutex_unlock(&dev->struct_mutex); + drm_gem_object_unreference_unlocked(obj); return 0; } @@ -388,16 +384,12 @@ int exynos_gem_map_sgt_with_dma(struct drm_device *drm_dev, { int nents; - mutex_lock(&drm_dev->struct_mutex); - nents = dma_map_sg(to_dma_dev(drm_dev), sgt->sgl, sgt->nents, dir); if (!nents) { DRM_ERROR("failed to map sgl with dma.\n"); - mutex_unlock(&drm_dev->struct_mutex); return nents; } - mutex_unlock(&drm_dev->struct_mutex); return 0; } @@ -458,8 +450,6 @@ int exynos_drm_gem_dumb_map_offset(struct drm_file *file_priv, struct drm_gem_object *obj; int ret = 0; - mutex_lock(&dev->struct_mutex); - /* * get offset of memory allocated for drm framebuffer. * - this callback would be called by user application @@ -469,16 +459,13 @@ int exynos_drm_gem_dumb_map_offset(struct drm_file *file_priv, obj = drm_gem_object_lookup(dev, file_priv, handle); if (!obj) { DRM_ERROR("failed to lookup gem object.\n"); - ret = -EINVAL; - goto unlock; + return -EINVAL; } *offset = drm_vma_node_offset_addr(&obj->vma_node); DRM_DEBUG_KMS("offset = 0x%lx\n", (unsigned long)*offset); - drm_gem_object_unreference(obj); -unlock: - mutex_unlock(&dev->struct_mutex); + drm_gem_object_unreference_unlocked(obj); return ret; } diff --git a/drivers/gpu/drm/fsl-dcu/Makefile b/drivers/gpu/drm/fsl-dcu/Makefile index 6ea1523ae6ec..b35a292287f3 100644 --- a/drivers/gpu/drm/fsl-dcu/Makefile +++ b/drivers/gpu/drm/fsl-dcu/Makefile @@ -3,5 +3,6 @@ fsl-dcu-drm-y := fsl_dcu_drm_drv.o \ fsl_dcu_drm_rgb.o \ fsl_dcu_drm_plane.o \ fsl_dcu_drm_crtc.o \ - fsl_dcu_drm_fbdev.o + fsl_dcu_drm_fbdev.o \ + fsl_tcon.o obj-$(CONFIG_DRM_FSL_DCU) += fsl-dcu-drm.o diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_crtc.c b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_crtc.c index 4ed7798533f9..365809edf29a 100644 --- a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_crtc.c +++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_crtc.c @@ -67,12 +67,10 @@ static void fsl_dcu_drm_crtc_mode_set_nofb(struct drm_crtc *crtc) struct drm_device *dev = crtc->dev; struct fsl_dcu_drm_device *fsl_dev = dev->dev_private; struct drm_display_mode *mode = &crtc->state->mode; - unsigned int hbp, hfp, hsw, vbp, vfp, vsw, div, index, pol = 0; - unsigned long dcuclk; + unsigned int hbp, hfp, hsw, vbp, vfp, vsw, index, pol = 0; index = drm_crtc_index(crtc); - dcuclk = clk_get_rate(fsl_dev->clk); - div = dcuclk / mode->clock / 1000; + clk_set_rate(fsl_dev->pix_clk, mode->clock * 1000); /* Configure timings: */ hbp = mode->htotal - mode->hsync_end; @@ -99,7 +97,6 @@ static void fsl_dcu_drm_crtc_mode_set_nofb(struct drm_crtc *crtc) regmap_write(fsl_dev->regmap, DCU_DISP_SIZE, DCU_DISP_SIZE_DELTA_Y(mode->vdisplay) | DCU_DISP_SIZE_DELTA_X(mode->hdisplay)); - regmap_write(fsl_dev->regmap, DCU_DIV_RATIO, div); regmap_write(fsl_dev->regmap, DCU_SYN_POL, pol); regmap_write(fsl_dev->regmap, DCU_BGND, DCU_BGND_R(0) | DCU_BGND_G(0) | DCU_BGND_B(0)); diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.c b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.c index e8d9337a66d8..44f6f262d75a 100644 --- a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.c +++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.c @@ -23,10 +23,12 @@ #include <drm/drmP.h> #include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_cma_helper.h> #include <drm/drm_gem_cma_helper.h> #include "fsl_dcu_drm_crtc.h" #include "fsl_dcu_drm_drv.h" +#include "fsl_tcon.h" static bool fsl_dcu_drm_is_volatile_reg(struct device *dev, unsigned int reg) { @@ -62,46 +64,55 @@ static int fsl_dcu_drm_irq_init(struct drm_device *dev) return ret; } -static int fsl_dcu_load(struct drm_device *drm, unsigned long flags) +static int fsl_dcu_load(struct drm_device *dev, unsigned long flags) { - struct device *dev = drm->dev; - struct fsl_dcu_drm_device *fsl_dev = drm->dev_private; + struct fsl_dcu_drm_device *fsl_dev = dev->dev_private; int ret; ret = fsl_dcu_drm_modeset_init(fsl_dev); if (ret < 0) { - dev_err(dev, "failed to initialize mode setting\n"); + dev_err(dev->dev, "failed to initialize mode setting\n"); return ret; } - ret = drm_vblank_init(drm, drm->mode_config.num_crtc); + ret = drm_vblank_init(dev, dev->mode_config.num_crtc); if (ret < 0) { - dev_err(dev, "failed to initialize vblank\n"); + dev_err(dev->dev, "failed to initialize vblank\n"); goto done; } - drm->vblank_disable_allowed = true; + dev->vblank_disable_allowed = true; - ret = fsl_dcu_drm_irq_init(drm); + ret = fsl_dcu_drm_irq_init(dev); if (ret < 0) goto done; - drm->irq_enabled = true; + dev->irq_enabled = true; - fsl_dcu_fbdev_init(drm); + fsl_dcu_fbdev_init(dev); return 0; done: - if (ret) { - drm_mode_config_cleanup(drm); - drm_vblank_cleanup(drm); - drm_irq_uninstall(drm); - drm->dev_private = NULL; - } + drm_kms_helper_poll_fini(dev); + + if (fsl_dev->fbdev) + drm_fbdev_cma_fini(fsl_dev->fbdev); + + drm_mode_config_cleanup(dev); + drm_vblank_cleanup(dev); + drm_irq_uninstall(dev); + dev->dev_private = NULL; return ret; } static int fsl_dcu_unload(struct drm_device *dev) { + struct fsl_dcu_drm_device *fsl_dev = dev->dev_private; + + drm_kms_helper_poll_fini(dev); + + if (fsl_dev->fbdev) + drm_fbdev_cma_fini(fsl_dev->fbdev); + drm_mode_config_cleanup(dev); drm_vblank_cleanup(dev); drm_irq_uninstall(dev); @@ -157,6 +168,13 @@ static void fsl_dcu_drm_disable_vblank(struct drm_device *dev, regmap_write(fsl_dev->regmap, DCU_INT_MASK, value); } +static void fsl_dcu_drm_lastclose(struct drm_device *dev) +{ + struct fsl_dcu_drm_device *fsl_dev = dev->dev_private; + + drm_fbdev_cma_restore_mode(fsl_dev->fbdev); +} + static const struct file_operations fsl_dcu_drm_fops = { .owner = THIS_MODULE, .open = drm_open, @@ -174,6 +192,7 @@ static const struct file_operations fsl_dcu_drm_fops = { static struct drm_driver fsl_dcu_drm_driver = { .driver_features = DRIVER_HAVE_IRQ | DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME | DRIVER_ATOMIC, + .lastclose = fsl_dcu_drm_lastclose, .load = fsl_dcu_load, .unload = fsl_dcu_unload, .irq_handler = fsl_dcu_drm_irq, @@ -197,9 +216,9 @@ static struct drm_driver fsl_dcu_drm_driver = { .fops = &fsl_dcu_drm_fops, .name = "fsl-dcu-drm", .desc = "Freescale DCU DRM", - .date = "20150213", + .date = "20160425", .major = 1, - .minor = 0, + .minor = 1, }; #ifdef CONFIG_PM_SLEEP @@ -283,6 +302,9 @@ static int fsl_dcu_drm_probe(struct platform_device *pdev) struct resource *res; void __iomem *base; struct drm_driver *driver = &fsl_dcu_drm_driver; + struct clk *pix_clk_in; + char pix_clk_name[32]; + const char *pix_clk_in_name; const struct of_device_id *id; int ret; @@ -290,6 +312,11 @@ static int fsl_dcu_drm_probe(struct platform_device *pdev) if (!fsl_dev) return -ENOMEM; + id = of_match_node(fsl_dcu_of_match, pdev->dev.of_node); + if (!id) + return -ENODEV; + fsl_dev->soc = id->data; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(dev, "could not get memory IO resource\n"); @@ -308,39 +335,54 @@ static int fsl_dcu_drm_probe(struct platform_device *pdev) return -ENXIO; } + fsl_dev->regmap = devm_regmap_init_mmio(dev, base, + &fsl_dcu_regmap_config); + if (IS_ERR(fsl_dev->regmap)) { + dev_err(dev, "regmap init failed\n"); + return PTR_ERR(fsl_dev->regmap); + } + fsl_dev->clk = devm_clk_get(dev, "dcu"); if (IS_ERR(fsl_dev->clk)) { - ret = PTR_ERR(fsl_dev->clk); dev_err(dev, "failed to get dcu clock\n"); - return ret; - } - ret = clk_prepare(fsl_dev->clk); - if (ret < 0) { - dev_err(dev, "failed to prepare dcu clk\n"); - return ret; + return PTR_ERR(fsl_dev->clk); } - ret = clk_enable(fsl_dev->clk); + ret = clk_prepare_enable(fsl_dev->clk); if (ret < 0) { dev_err(dev, "failed to enable dcu clk\n"); - clk_unprepare(fsl_dev->clk); return ret; } - fsl_dev->regmap = devm_regmap_init_mmio(dev, base, - &fsl_dcu_regmap_config); - if (IS_ERR(fsl_dev->regmap)) { - dev_err(dev, "regmap init failed\n"); - return PTR_ERR(fsl_dev->regmap); + pix_clk_in = devm_clk_get(dev, "pix"); + if (IS_ERR(pix_clk_in)) { + /* legancy binding, use dcu clock as pixel clock input */ + pix_clk_in = fsl_dev->clk; } - id = of_match_node(fsl_dcu_of_match, pdev->dev.of_node); - if (!id) - return -ENODEV; - fsl_dev->soc = id->data; + pix_clk_in_name = __clk_get_name(pix_clk_in); + snprintf(pix_clk_name, sizeof(pix_clk_name), "%s_pix", pix_clk_in_name); + fsl_dev->pix_clk = clk_register_divider(dev, pix_clk_name, + pix_clk_in_name, 0, base + DCU_DIV_RATIO, + 0, 8, CLK_DIVIDER_ROUND_CLOSEST, NULL); + if (IS_ERR(fsl_dev->pix_clk)) { + dev_err(dev, "failed to register pix clk\n"); + ret = PTR_ERR(fsl_dev->pix_clk); + goto disable_clk; + } + + ret = clk_prepare_enable(fsl_dev->pix_clk); + if (ret < 0) { + dev_err(dev, "failed to enable pix clk\n"); + goto unregister_pix_clk; + } + + fsl_dev->tcon = fsl_tcon_init(dev); drm = drm_dev_alloc(driver, dev); - if (!drm) - return -ENOMEM; + if (!drm) { + ret = -ENOMEM; + goto disable_pix_clk; + } fsl_dev->dev = dev; fsl_dev->drm = drm; @@ -360,6 +402,12 @@ static int fsl_dcu_drm_probe(struct platform_device *pdev) unref: drm_dev_unref(drm); +disable_pix_clk: + clk_disable_unprepare(fsl_dev->pix_clk); +unregister_pix_clk: + clk_unregister(fsl_dev->pix_clk); +disable_clk: + clk_disable_unprepare(fsl_dev->clk); return ret; } @@ -367,6 +415,9 @@ static int fsl_dcu_drm_remove(struct platform_device *pdev) { struct fsl_dcu_drm_device *fsl_dev = platform_get_drvdata(pdev); + clk_disable_unprepare(fsl_dev->clk); + clk_disable_unprepare(fsl_dev->pix_clk); + clk_unregister(fsl_dev->pix_clk); drm_put_dev(fsl_dev->drm); return 0; diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.h b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.h index 6413ac9e4769..5bb7c261fe95 100644 --- a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.h +++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_drv.h @@ -183,6 +183,8 @@ struct fsl_dcu_drm_device { struct regmap *regmap; int irq; struct clk *clk; + struct clk *pix_clk; + struct fsl_tcon *tcon; /*protects hardware register*/ spinlock_t irq_lock; struct drm_device *drm; diff --git a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c index 8780deba5e8a..98c998da91eb 100644 --- a/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c +++ b/drivers/gpu/drm/fsl-dcu/fsl_dcu_drm_rgb.c @@ -17,6 +17,7 @@ #include <drm/drm_panel.h> #include "fsl_dcu_drm_drv.h" +#include "fsl_tcon.h" static int fsl_dcu_drm_encoder_atomic_check(struct drm_encoder *encoder, @@ -28,10 +29,20 @@ fsl_dcu_drm_encoder_atomic_check(struct drm_encoder *encoder, static void fsl_dcu_drm_encoder_disable(struct drm_encoder *encoder) { + struct drm_device *dev = encoder->dev; + struct fsl_dcu_drm_device *fsl_dev = dev->dev_private; + + if (fsl_dev->tcon) + fsl_tcon_bypass_disable(fsl_dev->tcon); } static void fsl_dcu_drm_encoder_enable(struct drm_encoder *encoder) { + struct drm_device *dev = encoder->dev; + struct fsl_dcu_drm_device *fsl_dev = dev->dev_private; + + if (fsl_dev->tcon) + fsl_tcon_bypass_enable(fsl_dev->tcon); } static const struct drm_encoder_helper_funcs encoder_helper_funcs = { @@ -68,7 +79,10 @@ int fsl_dcu_drm_encoder_create(struct fsl_dcu_drm_device *fsl_dev, static void fsl_dcu_drm_connector_destroy(struct drm_connector *connector) { + struct fsl_dcu_drm_connector *fsl_con = to_fsl_dcu_connector(connector); + drm_connector_unregister(connector); + drm_panel_detach(fsl_con->panel); drm_connector_cleanup(connector); } @@ -131,7 +145,7 @@ int fsl_dcu_drm_connector_create(struct fsl_dcu_drm_device *fsl_dev, struct drm_encoder *encoder) { struct drm_connector *connector = &fsl_dev->connector.base; - struct drm_mode_config mode_config = fsl_dev->drm->mode_config; + struct drm_mode_config *mode_config = &fsl_dev->drm->mode_config; struct device_node *panel_node; int ret; @@ -153,19 +167,23 @@ int fsl_dcu_drm_connector_create(struct fsl_dcu_drm_device *fsl_dev, goto err_sysfs; drm_object_property_set_value(&connector->base, - mode_config.dpms_property, + mode_config->dpms_property, DRM_MODE_DPMS_OFF); panel_node = of_parse_phandle(fsl_dev->np, "fsl,panel", 0); - if (panel_node) { - fsl_dev->connector.panel = of_drm_find_panel(panel_node); - if (!fsl_dev->connector.panel) { - ret = -EPROBE_DEFER; - goto err_sysfs; - } - of_node_put(panel_node); + if (!panel_node) { + dev_err(fsl_dev->dev, "fsl,panel property not found\n"); + ret = -ENODEV; + goto err_sysfs; } + fsl_dev->connector.panel = of_drm_find_panel(panel_node); + if (!fsl_dev->connector.panel) { + ret = -EPROBE_DEFER; + goto err_panel; + } + of_node_put(panel_node); + ret = drm_panel_attach(fsl_dev->connector.panel, connector); if (ret) { dev_err(fsl_dev->dev, "failed to attach panel\n"); @@ -174,6 +192,8 @@ int fsl_dcu_drm_connector_create(struct fsl_dcu_drm_device *fsl_dev, return 0; +err_panel: + of_node_put(panel_node); err_sysfs: drm_connector_unregister(connector); err_cleanup: diff --git a/drivers/gpu/drm/fsl-dcu/fsl_tcon.c b/drivers/gpu/drm/fsl-dcu/fsl_tcon.c new file mode 100644 index 000000000000..bbe34f1c0505 --- /dev/null +++ b/drivers/gpu/drm/fsl-dcu/fsl_tcon.c @@ -0,0 +1,111 @@ +/* + * Copyright 2015 Toradex AG + * + * Stefan Agner <stefan@agner.ch> + * + * Freescale TCON device driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/mm.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#include "fsl_tcon.h" + +void fsl_tcon_bypass_disable(struct fsl_tcon *tcon) +{ + regmap_update_bits(tcon->regs, FSL_TCON_CTRL1, + FSL_TCON_CTRL1_TCON_BYPASS, 0); +} + +void fsl_tcon_bypass_enable(struct fsl_tcon *tcon) +{ + regmap_update_bits(tcon->regs, FSL_TCON_CTRL1, + FSL_TCON_CTRL1_TCON_BYPASS, + FSL_TCON_CTRL1_TCON_BYPASS); +} + +static struct regmap_config fsl_tcon_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + + .name = "tcon", +}; + +static int fsl_tcon_init_regmap(struct device *dev, + struct fsl_tcon *tcon, + struct device_node *np) +{ + struct resource res; + void __iomem *regs; + + if (of_address_to_resource(np, 0, &res)) + return -EINVAL; + + regs = devm_ioremap_resource(dev, &res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + tcon->regs = devm_regmap_init_mmio(dev, regs, + &fsl_tcon_regmap_config); + if (IS_ERR(tcon->regs)) + return PTR_ERR(tcon->regs); + + return 0; +} + +struct fsl_tcon *fsl_tcon_init(struct device *dev) +{ + struct fsl_tcon *tcon; + struct device_node *np; + int ret; + + /* TCON node is not mandatory, some devices do not provide TCON */ + np = of_parse_phandle(dev->of_node, "fsl,tcon", 0); + if (!np) + return NULL; + + tcon = devm_kzalloc(dev, sizeof(*tcon), GFP_KERNEL); + if (!tcon) { + ret = -ENOMEM; + goto err_node_put; + } + + ret = fsl_tcon_init_regmap(dev, tcon, np); + if (ret) { + dev_err(dev, "Couldn't create the TCON regmap\n"); + goto err_node_put; + } + + tcon->ipg_clk = of_clk_get_by_name(np, "ipg"); + if (IS_ERR(tcon->ipg_clk)) { + dev_err(dev, "Couldn't get the TCON bus clock\n"); + goto err_node_put; + } + + clk_prepare_enable(tcon->ipg_clk); + + dev_info(dev, "Using TCON in bypass mode\n"); + + return tcon; + +err_node_put: + of_node_put(np); + return NULL; +} + +void fsl_tcon_free(struct fsl_tcon *tcon) +{ + clk_disable_unprepare(tcon->ipg_clk); + clk_put(tcon->ipg_clk); +} + diff --git a/drivers/gpu/drm/fsl-dcu/fsl_tcon.h b/drivers/gpu/drm/fsl-dcu/fsl_tcon.h new file mode 100644 index 000000000000..80a7617de58f --- /dev/null +++ b/drivers/gpu/drm/fsl-dcu/fsl_tcon.h @@ -0,0 +1,33 @@ +/* + * Copyright 2015 Toradex AG + * + * Stefan Agner <stefan@agner.ch> + * + * Freescale TCON device driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __FSL_TCON_H__ +#define __FSL_TCON_H__ + +#include <linux/bitops.h> + +#define FSL_TCON_CTRL1 0x0 +#define FSL_TCON_CTRL1_TCON_BYPASS BIT(29) + +struct fsl_tcon { + struct regmap *regs; + struct clk *ipg_clk; +}; + +struct fsl_tcon *fsl_tcon_init(struct device *dev); +void fsl_tcon_free(struct fsl_tcon *tcon); + +void fsl_tcon_bypass_disable(struct fsl_tcon *tcon); +void fsl_tcon_bypass_enable(struct fsl_tcon *tcon); + +#endif /* __FSL_TCON_H__ */ diff --git a/drivers/gpu/drm/gma500/framebuffer.c b/drivers/gpu/drm/gma500/framebuffer.c index 033d894d030e..ec2bc769742a 100644 --- a/drivers/gpu/drm/gma500/framebuffer.c +++ b/drivers/gpu/drm/gma500/framebuffer.c @@ -411,7 +411,7 @@ static int psbfb_create(struct psb_fbdev *fbdev, info = drm_fb_helper_alloc_fbi(&fbdev->psb_fb_helper); if (IS_ERR(info)) { ret = PTR_ERR(info); - goto out_err1; + goto err_free_range; } info->par = fbdev; @@ -419,7 +419,7 @@ static int psbfb_create(struct psb_fbdev *fbdev, ret = psb_framebuffer_init(dev, psbfb, &mode_cmd, backing); if (ret) - goto out_unref; + goto err_release; fb = &psbfb->base; psbfb->fbdev = info; @@ -464,14 +464,9 @@ static int psbfb_create(struct psb_fbdev *fbdev, psbfb->base.width, psbfb->base.height); return 0; -out_unref: - if (backing->stolen) - psb_gtt_free_range(dev, backing); - else - drm_gem_object_unreference_unlocked(&backing->gem); - +err_release: drm_fb_helper_release_fbi(&fbdev->psb_fb_helper); -out_err1: +err_free_range: psb_gtt_free_range(dev, backing); return ret; } diff --git a/drivers/gpu/drm/gma500/mdfld_dsi_dpi.c b/drivers/gpu/drm/gma500/mdfld_dsi_dpi.c index 7cd87a0c2385..a05c020602bd 100644 --- a/drivers/gpu/drm/gma500/mdfld_dsi_dpi.c +++ b/drivers/gpu/drm/gma500/mdfld_dsi_dpi.c @@ -979,11 +979,7 @@ struct mdfld_dsi_encoder *mdfld_dsi_dpi_init(struct drm_device *dev, return NULL; } - if (dsi_connector->pipe) - dpi_output->panel_on = 0; - else - dpi_output->panel_on = 0; - + dpi_output->panel_on = 0; dpi_output->dev = dev; if (mdfld_get_panel_type(dev, pipe) != TC35876X) dpi_output->p_funcs = p_funcs; diff --git a/drivers/gpu/drm/gma500/mdfld_dsi_pkg_sender.c b/drivers/gpu/drm/gma500/mdfld_dsi_pkg_sender.c index 6b43ae3ffd73..1616af209bfc 100644 --- a/drivers/gpu/drm/gma500/mdfld_dsi_pkg_sender.c +++ b/drivers/gpu/drm/gma500/mdfld_dsi_pkg_sender.c @@ -72,7 +72,7 @@ static const char *const dsi_errors[] = { "RX Prot Violation", "HS Generic Write FIFO Full", "LP Generic Write FIFO Full", - "Generic Read Data Avail" + "Generic Read Data Avail", "Special Packet Sent", "Tearing Effect", }; diff --git a/drivers/gpu/drm/i915/i915_debugfs.c b/drivers/gpu/drm/i915/i915_debugfs.c index e1bc5ec04a92..4950d05d2948 100644 --- a/drivers/gpu/drm/i915/i915_debugfs.c +++ b/drivers/gpu/drm/i915/i915_debugfs.c @@ -1946,7 +1946,7 @@ static int i915_gem_framebuffer_info(struct seq_file *m, void *data) fbdev_fb->base.depth, fbdev_fb->base.bits_per_pixel, fbdev_fb->base.modifier[0], - atomic_read(&fbdev_fb->base.refcount.refcount)); + drm_framebuffer_read_refcount(&fbdev_fb->base)); describe_obj(m, fbdev_fb->obj); seq_putc(m, '\n'); } @@ -1964,7 +1964,7 @@ static int i915_gem_framebuffer_info(struct seq_file *m, void *data) fb->base.depth, fb->base.bits_per_pixel, fb->base.modifier[0], - atomic_read(&fb->base.refcount.refcount)); + drm_framebuffer_read_refcount(&fb->base)); describe_obj(m, fb->obj); seq_putc(m, '\n'); } @@ -3476,7 +3476,8 @@ static int i915_dp_mst_info(struct seq_file *m, void *unused) intel_dig_port = enc_to_dig_port(encoder); if (!intel_dig_port->dp.can_mst) continue; - + seq_printf(m, "MST Source Port %c\n", + port_name(intel_dig_port->port)); drm_dp_mst_dump_topology(m, &intel_dig_port->dp.mst_mgr); } drm_modeset_unlock_all(dev); diff --git a/drivers/gpu/drm/i915/i915_drv.c b/drivers/gpu/drm/i915/i915_drv.c index 7a0e4d6c71e2..d37c0a671eed 100644 --- a/drivers/gpu/drm/i915/i915_drv.c +++ b/drivers/gpu/drm/i915/i915_drv.c @@ -1759,10 +1759,8 @@ static int __init i915_init(void) if (i915.modeset == 0) driver.driver_features &= ~DRIVER_MODESET; -#ifdef CONFIG_VGA_CONSOLE if (vgacon_text_force() && i915.modeset == -1) driver.driver_features &= ~DRIVER_MODESET; -#endif if (!(driver.driver_features & DRIVER_MODESET)) { /* Silently fail loading to not upset userspace. */ diff --git a/drivers/gpu/drm/i915/i915_gem.c b/drivers/gpu/drm/i915/i915_gem.c index a14a98341a4b..261a3ef72828 100644 --- a/drivers/gpu/drm/i915/i915_gem.c +++ b/drivers/gpu/drm/i915/i915_gem.c @@ -2017,9 +2017,6 @@ static int i915_gem_object_create_mmap_offset(struct drm_i915_gem_object *obj) struct drm_i915_private *dev_priv = obj->base.dev->dev_private; int ret; - if (drm_vma_node_has_offset(&obj->base.vma_node)) - return 0; - dev_priv->mm.shrinker_no_lock_stealing = true; ret = drm_gem_create_mmap_offset(&obj->base); diff --git a/drivers/gpu/drm/mgag200/mgag200_drv.c b/drivers/gpu/drm/mgag200/mgag200_drv.c index b0af77454d52..ebb470ff7200 100644 --- a/drivers/gpu/drm/mgag200/mgag200_drv.c +++ b/drivers/gpu/drm/mgag200/mgag200_drv.c @@ -116,10 +116,8 @@ static struct pci_driver mgag200_pci_driver = { static int __init mgag200_init(void) { -#ifdef CONFIG_VGA_CONSOLE if (vgacon_text_force() && mgag200_modeset == -1) return -EINVAL; -#endif if (mgag200_modeset == 0) return -EINVAL; diff --git a/drivers/gpu/drm/msm/msm_fb.c b/drivers/gpu/drm/msm/msm_fb.c index a474d6cf5d9f..17e0c9eb1900 100644 --- a/drivers/gpu/drm/msm/msm_fb.c +++ b/drivers/gpu/drm/msm/msm_fb.c @@ -77,7 +77,7 @@ void msm_framebuffer_describe(struct drm_framebuffer *fb, struct seq_file *m) seq_printf(m, "fb: %dx%d@%4.4s (%2d, ID:%d)\n", fb->width, fb->height, (char *)&fb->pixel_format, - fb->refcount.refcount.counter, fb->base.id); + drm_framebuffer_read_refcount(fb), fb->base.id); for (i = 0; i < n; i++) { seq_printf(m, " %d: offset=%d pitch=%d, obj: ", diff --git a/drivers/gpu/drm/nouveau/nouveau_display.c b/drivers/gpu/drm/nouveau/nouveau_display.c index 7ce7fa5cb5e6..816342645f42 100644 --- a/drivers/gpu/drm/nouveau/nouveau_display.c +++ b/drivers/gpu/drm/nouveau/nouveau_display.c @@ -296,7 +296,7 @@ nouveau_user_framebuffer_create(struct drm_device *dev, err: kfree(nouveau_fb); err_unref: - drm_gem_object_unreference(gem); + drm_gem_object_unreference_unlocked(gem); return ERR_PTR(ret); } diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c index d06877d9c1ed..db5c7d0cc25c 100644 --- a/drivers/gpu/drm/nouveau/nouveau_drm.c +++ b/drivers/gpu/drm/nouveau/nouveau_drm.c @@ -1083,10 +1083,8 @@ nouveau_drm_init(void) nouveau_display_options(); if (nouveau_modeset == -1) { -#ifdef CONFIG_VGA_CONSOLE if (vgacon_text_force()) nouveau_modeset = 0; -#endif } if (!nouveau_modeset) diff --git a/drivers/gpu/drm/nouveau/nouveau_fbcon.c b/drivers/gpu/drm/nouveau/nouveau_fbcon.c index 59f27e774acb..3bae706126bd 100644 --- a/drivers/gpu/drm/nouveau/nouveau_fbcon.c +++ b/drivers/gpu/drm/nouveau/nouveau_fbcon.c @@ -386,8 +386,6 @@ nouveau_fbcon_create(struct drm_fb_helper *helper, } } - mutex_lock(&dev->struct_mutex); - info = drm_fb_helper_alloc_fbi(helper); if (IS_ERR(info)) { ret = PTR_ERR(info); @@ -426,8 +424,6 @@ nouveau_fbcon_create(struct drm_fb_helper *helper, /* Use default scratch pixmap (info->pixmap.flags = FB_PIXMAP_SYSTEM) */ - mutex_unlock(&dev->struct_mutex); - if (chan) nouveau_fbcon_accel_init(dev); nouveau_fbcon_zfill(dev, fbcon); @@ -441,7 +437,6 @@ nouveau_fbcon_create(struct drm_fb_helper *helper, return 0; out_unlock: - mutex_unlock(&dev->struct_mutex); if (chan) nouveau_bo_vma_del(nvbo, &fbcon->nouveau_fb.vma); nouveau_bo_unmap(nvbo); diff --git a/drivers/gpu/drm/omapdrm/omap_fbdev.c b/drivers/gpu/drm/omapdrm/omap_fbdev.c index 3cb16f0cf381..89da41ac64d2 100644 --- a/drivers/gpu/drm/omapdrm/omap_fbdev.c +++ b/drivers/gpu/drm/omapdrm/omap_fbdev.c @@ -153,7 +153,7 @@ static int omap_fbdev_create(struct drm_fb_helper *helper, /* note: if fb creation failed, we can't rely on fb destroy * to unref the bo: */ - drm_gem_object_unreference(fbdev->bo); + drm_gem_object_unreference_unlocked(fbdev->bo); ret = PTR_ERR(fb); goto fail; } diff --git a/drivers/gpu/drm/qxl/qxl_drv.c b/drivers/gpu/drm/qxl/qxl_drv.c index 7307b07fe06b..dc9df5fe50ba 100644 --- a/drivers/gpu/drm/qxl/qxl_drv.c +++ b/drivers/gpu/drm/qxl/qxl_drv.c @@ -272,10 +272,8 @@ static struct drm_driver qxl_driver = { static int __init qxl_init(void) { -#ifdef CONFIG_VGA_CONSOLE if (vgacon_text_force() && qxl_modeset == -1) return -EINVAL; -#endif if (qxl_modeset == 0) return -EINVAL; diff --git a/drivers/gpu/drm/qxl/qxl_fb.c b/drivers/gpu/drm/qxl/qxl_fb.c index 7136e521e6db..bb7ce07b788b 100644 --- a/drivers/gpu/drm/qxl/qxl_fb.c +++ b/drivers/gpu/drm/qxl/qxl_fb.c @@ -443,11 +443,11 @@ out_unref: } } if (fb && ret) { - drm_gem_object_unreference(gobj); + drm_gem_object_unreference_unlocked(gobj); drm_framebuffer_cleanup(fb); kfree(fb); } - drm_gem_object_unreference(gobj); + drm_gem_object_unreference_unlocked(gobj); return ret; } diff --git a/drivers/gpu/drm/radeon/radeon_drv.c b/drivers/gpu/drm/radeon/radeon_drv.c index ccd4ad4ee592..5d44ed0d104a 100644 --- a/drivers/gpu/drm/radeon/radeon_drv.c +++ b/drivers/gpu/drm/radeon/radeon_drv.c @@ -566,12 +566,10 @@ static struct pci_driver radeon_kms_pci_driver = { static int __init radeon_init(void) { -#ifdef CONFIG_VGA_CONSOLE if (vgacon_text_force() && radeon_modeset == -1) { DRM_INFO("VGACON disable radeon kernel modesetting.\n"); radeon_modeset = 0; } -#endif /* set to modesetting by default if not nomodeset */ if (radeon_modeset == -1) radeon_modeset = 1; diff --git a/drivers/gpu/drm/rcar-du/Kconfig b/drivers/gpu/drm/rcar-du/Kconfig index 1f10fa0928b4..7fc3ca5ce6c7 100644 --- a/drivers/gpu/drm/rcar-du/Kconfig +++ b/drivers/gpu/drm/rcar-du/Kconfig @@ -2,7 +2,7 @@ config DRM_RCAR_DU tristate "DRM Support for R-Car Display Unit" depends on DRM && OF depends on ARM || ARM64 - depends on ARCH_SHMOBILE || COMPILE_TEST + depends on ARCH_RENESAS || COMPILE_TEST select DRM_KMS_HELPER select DRM_KMS_CMA_HELPER select DRM_GEM_CMA_HELPER @@ -27,6 +27,6 @@ config DRM_RCAR_LVDS config DRM_RCAR_VSP bool "R-Car DU VSP Compositor Support" depends on DRM_RCAR_DU - depends on VIDEO_RENESAS_VSP1 + depends on VIDEO_RENESAS_VSP1=y || (VIDEO_RENESAS_VSP1 && DRM_RCAR_DU=m) help Enable support to expose the R-Car VSP Compositor as KMS planes. diff --git a/drivers/gpu/drm/rcar-du/rcar_du_drv.c b/drivers/gpu/drm/rcar-du/rcar_du_drv.c index ed6006bf6bd8..fb9242d27883 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_drv.c +++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.c @@ -278,10 +278,7 @@ static int rcar_du_remove(struct platform_device *pdev) struct rcar_du_device *rcdu = platform_get_drvdata(pdev); struct drm_device *ddev = rcdu->ddev; - mutex_lock(&ddev->mode_config.mutex); - drm_connector_unplug_all(ddev); - mutex_unlock(&ddev->mode_config.mutex); - + drm_connector_unregister_all(ddev); drm_dev_unregister(ddev); if (rcdu->fbdev) @@ -300,7 +297,6 @@ static int rcar_du_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; struct rcar_du_device *rcdu; - struct drm_connector *connector; struct drm_device *ddev; struct resource *mem; int ret; @@ -364,14 +360,7 @@ static int rcar_du_probe(struct platform_device *pdev) if (ret) goto error; - mutex_lock(&ddev->mode_config.mutex); - drm_for_each_connector(connector, ddev) { - ret = drm_connector_register(connector); - if (ret < 0) - break; - } - mutex_unlock(&ddev->mode_config.mutex); - + ret = drm_connector_register_all(ddev); if (ret < 0) goto error; diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig index 76b3362c5e59..d30bdc38a760 100644 --- a/drivers/gpu/drm/rockchip/Kconfig +++ b/drivers/gpu/drm/rockchip/Kconfig @@ -16,6 +16,15 @@ config DRM_ROCKCHIP 2D or 3D acceleration; acceleration is performed by other IP found on the SoC. +config ROCKCHIP_ANALOGIX_DP + tristate "Rockchip specific extensions for Analogix DP driver" + depends on DRM_ROCKCHIP + select DRM_ANALOGIX_DP + help + This selects support for Rockchip SoC specific extensions + for the Analogix Core DP driver. If you want to enable DP + on RK3288 based SoC, you should selet this option. + config ROCKCHIP_DW_HDMI tristate "Rockchip specific extensions for Synopsys DW HDMI" depends on DRM_ROCKCHIP diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile index df8fbef17791..05d07138a2b2 100644 --- a/drivers/gpu/drm/rockchip/Makefile +++ b/drivers/gpu/drm/rockchip/Makefile @@ -6,6 +6,7 @@ rockchipdrm-y := rockchip_drm_drv.o rockchip_drm_fb.o \ rockchip_drm_gem.o rockchip_drm_vop.o rockchipdrm-$(CONFIG_DRM_FBDEV_EMULATION) += rockchip_drm_fbdev.o +obj-$(CONFIG_ROCKCHIP_ANALOGIX_DP) += analogix_dp-rockchip.o obj-$(CONFIG_ROCKCHIP_DW_HDMI) += dw_hdmi-rockchip.o obj-$(CONFIG_ROCKCHIP_DW_MIPI_DSI) += dw-mipi-dsi.o obj-$(CONFIG_ROCKCHIP_INNO_HDMI) += inno_hdmi.o diff --git a/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c new file mode 100644 index 000000000000..a1d94d8d9443 --- /dev/null +++ b/drivers/gpu/drm/rockchip/analogix_dp-rockchip.c @@ -0,0 +1,384 @@ +/* + * Rockchip SoC DP (Display Port) interface driver. + * + * Copyright (C) Fuzhou Rockchip Electronics Co., Ltd. + * Author: Andy Yan <andy.yan@rock-chips.com> + * Yakir Yang <ykk@rock-chips.com> + * Jeff Chen <jeff.chen@rock-chips.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/component.h> +#include <linux/mfd/syscon.h> +#include <linux/of_graph.h> +#include <linux/regmap.h> +#include <linux/reset.h> +#include <linux/clk.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_dp_helper.h> +#include <drm/drm_of.h> +#include <drm/drm_panel.h> + +#include <video/of_videomode.h> +#include <video/videomode.h> + +#include <drm/bridge/analogix_dp.h> + +#include "rockchip_drm_drv.h" +#include "rockchip_drm_vop.h" + +#define to_dp(nm) container_of(nm, struct rockchip_dp_device, nm) + +/* dp grf register offset */ +#define GRF_SOC_CON6 0x025c +#define GRF_EDP_LCD_SEL_MASK BIT(5) +#define GRF_EDP_SEL_VOP_LIT BIT(5) +#define GRF_EDP_SEL_VOP_BIG 0 + +struct rockchip_dp_device { + struct drm_device *drm_dev; + struct device *dev; + struct drm_encoder encoder; + struct drm_display_mode mode; + + struct clk *pclk; + struct regmap *grf; + struct reset_control *rst; + + struct analogix_dp_plat_data plat_data; +}; + +static int rockchip_dp_pre_init(struct rockchip_dp_device *dp) +{ + reset_control_assert(dp->rst); + usleep_range(10, 20); + reset_control_deassert(dp->rst); + + return 0; +} + +static int rockchip_dp_poweron(struct analogix_dp_plat_data *plat_data) +{ + struct rockchip_dp_device *dp = to_dp(plat_data); + int ret; + + ret = clk_prepare_enable(dp->pclk); + if (ret < 0) { + dev_err(dp->dev, "failed to enable pclk %d\n", ret); + return ret; + } + + ret = rockchip_dp_pre_init(dp); + if (ret < 0) { + dev_err(dp->dev, "failed to dp pre init %d\n", ret); + return ret; + } + + return 0; +} + +static int rockchip_dp_powerdown(struct analogix_dp_plat_data *plat_data) +{ + struct rockchip_dp_device *dp = to_dp(plat_data); + + clk_disable_unprepare(dp->pclk); + + return 0; +} + +static bool +rockchip_dp_drm_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + /* do nothing */ + return true; +} + +static void rockchip_dp_drm_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted) +{ + /* do nothing */ +} + +static void rockchip_dp_drm_encoder_enable(struct drm_encoder *encoder) +{ + struct rockchip_dp_device *dp = to_dp(encoder); + int ret; + u32 val; + + /* + * FIXME(Yakir): driver should configure the CRTC output video + * mode with the display information which indicated the monitor + * support colorimetry. + * + * But don't know why the CRTC driver seems could only output the + * RGBaaa rightly. For example, if connect the "innolux,n116bge" + * eDP screen, EDID would indicated that screen only accepted the + * 6bpc mode. But if I configure CRTC to RGB666 output, then eDP + * screen would show a blue picture (RGB888 show a green picture). + * But if I configure CTRC to RGBaaa, and eDP driver still keep + * RGB666 input video mode, then screen would works prefect. + */ + ret = rockchip_drm_crtc_mode_config(encoder->crtc, + DRM_MODE_CONNECTOR_eDP, + ROCKCHIP_OUT_MODE_AAAA); + if (ret < 0) { + dev_err(dp->dev, "Could not set crtc mode config (%d)\n", ret); + return; + } + + ret = drm_of_encoder_active_endpoint_id(dp->dev->of_node, encoder); + if (ret < 0) + return; + + if (ret) + val = GRF_EDP_SEL_VOP_LIT | (GRF_EDP_LCD_SEL_MASK << 16); + else + val = GRF_EDP_SEL_VOP_BIG | (GRF_EDP_LCD_SEL_MASK << 16); + + dev_dbg(dp->dev, "vop %s output to dp\n", (ret) ? "LIT" : "BIG"); + + ret = regmap_write(dp->grf, GRF_SOC_CON6, val); + if (ret != 0) { + dev_err(dp->dev, "Could not write to GRF: %d\n", ret); + return; + } +} + +static void rockchip_dp_drm_encoder_nop(struct drm_encoder *encoder) +{ + /* do nothing */ +} + +static struct drm_encoder_helper_funcs rockchip_dp_encoder_helper_funcs = { + .mode_fixup = rockchip_dp_drm_encoder_mode_fixup, + .mode_set = rockchip_dp_drm_encoder_mode_set, + .enable = rockchip_dp_drm_encoder_enable, + .disable = rockchip_dp_drm_encoder_nop, +}; + +static void rockchip_dp_drm_encoder_destroy(struct drm_encoder *encoder) +{ + drm_encoder_cleanup(encoder); +} + +static struct drm_encoder_funcs rockchip_dp_encoder_funcs = { + .destroy = rockchip_dp_drm_encoder_destroy, +}; + +static int rockchip_dp_init(struct rockchip_dp_device *dp) +{ + struct device *dev = dp->dev; + struct device_node *np = dev->of_node; + int ret; + + dp->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf"); + if (IS_ERR(dp->grf)) { + dev_err(dev, "failed to get rockchip,grf property\n"); + return PTR_ERR(dp->grf); + } + + dp->pclk = devm_clk_get(dev, "pclk"); + if (IS_ERR(dp->pclk)) { + dev_err(dev, "failed to get pclk property\n"); + return PTR_ERR(dp->pclk); + } + + dp->rst = devm_reset_control_get(dev, "dp"); + if (IS_ERR(dp->rst)) { + dev_err(dev, "failed to get dp reset control\n"); + return PTR_ERR(dp->rst); + } + + ret = clk_prepare_enable(dp->pclk); + if (ret < 0) { + dev_err(dp->dev, "failed to enable pclk %d\n", ret); + return ret; + } + + ret = rockchip_dp_pre_init(dp); + if (ret < 0) { + dev_err(dp->dev, "failed to pre init %d\n", ret); + return ret; + } + + return 0; +} + +static int rockchip_dp_drm_create_encoder(struct rockchip_dp_device *dp) +{ + struct drm_encoder *encoder = &dp->encoder; + struct drm_device *drm_dev = dp->drm_dev; + struct device *dev = dp->dev; + int ret; + + encoder->possible_crtcs = drm_of_find_possible_crtcs(drm_dev, + dev->of_node); + DRM_DEBUG_KMS("possible_crtcs = 0x%x\n", encoder->possible_crtcs); + + ret = drm_encoder_init(drm_dev, encoder, &rockchip_dp_encoder_funcs, + DRM_MODE_ENCODER_TMDS, NULL); + if (ret) { + DRM_ERROR("failed to initialize encoder with drm\n"); + return ret; + } + + drm_encoder_helper_add(encoder, &rockchip_dp_encoder_helper_funcs); + + return 0; +} + +static int rockchip_dp_bind(struct device *dev, struct device *master, + void *data) +{ + struct rockchip_dp_device *dp = dev_get_drvdata(dev); + struct drm_device *drm_dev = data; + int ret; + + /* + * Just like the probe function said, we don't need the + * device drvrate anymore, we should leave the charge to + * analogix dp driver, set the device drvdata to NULL. + */ + dev_set_drvdata(dev, NULL); + + ret = rockchip_dp_init(dp); + if (ret < 0) + return ret; + + dp->drm_dev = drm_dev; + + ret = rockchip_dp_drm_create_encoder(dp); + if (ret) { + DRM_ERROR("failed to create drm encoder\n"); + return ret; + } + + dp->plat_data.encoder = &dp->encoder; + + dp->plat_data.dev_type = RK3288_DP; + dp->plat_data.power_on = rockchip_dp_poweron; + dp->plat_data.power_off = rockchip_dp_powerdown; + + return analogix_dp_bind(dev, dp->drm_dev, &dp->plat_data); +} + +static void rockchip_dp_unbind(struct device *dev, struct device *master, + void *data) +{ + return analogix_dp_unbind(dev, master, data); +} + +static const struct component_ops rockchip_dp_component_ops = { + .bind = rockchip_dp_bind, + .unbind = rockchip_dp_unbind, +}; + +static int rockchip_dp_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *panel_node, *port, *endpoint; + struct rockchip_dp_device *dp; + struct drm_panel *panel; + + port = of_graph_get_port_by_id(dev->of_node, 1); + if (!port) { + dev_err(dev, "can't find output port\n"); + return -EINVAL; + } + + endpoint = of_get_child_by_name(port, "endpoint"); + of_node_put(port); + if (!endpoint) { + dev_err(dev, "no output endpoint found\n"); + return -EINVAL; + } + + panel_node = of_graph_get_remote_port_parent(endpoint); + of_node_put(endpoint); + if (!panel_node) { + dev_err(dev, "no output node found\n"); + return -EINVAL; + } + + panel = of_drm_find_panel(panel_node); + if (!panel) { + DRM_ERROR("failed to find panel\n"); + of_node_put(panel_node); + return -EPROBE_DEFER; + } + + of_node_put(panel_node); + + dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL); + if (!dp) + return -ENOMEM; + + dp->dev = dev; + + dp->plat_data.panel = panel; + + /* + * We just use the drvdata until driver run into component + * add function, and then we would set drvdata to null, so + * that analogix dp driver could take charge of the drvdata. + */ + platform_set_drvdata(pdev, dp); + + return component_add(dev, &rockchip_dp_component_ops); +} + +static int rockchip_dp_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &rockchip_dp_component_ops); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int rockchip_dp_suspend(struct device *dev) +{ + return analogix_dp_suspend(dev); +} + +static int rockchip_dp_resume(struct device *dev) +{ + return analogix_dp_resume(dev); +} +#endif + +static const struct dev_pm_ops rockchip_dp_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(rockchip_dp_suspend, rockchip_dp_resume) +}; + +static const struct of_device_id rockchip_dp_dt_ids[] = { + {.compatible = "rockchip,rk3288-dp",}, + {} +}; +MODULE_DEVICE_TABLE(of, rockchip_dp_dt_ids); + +static struct platform_driver rockchip_dp_driver = { + .probe = rockchip_dp_probe, + .remove = rockchip_dp_remove, + .driver = { + .name = "rockchip-dp", + .owner = THIS_MODULE, + .pm = &rockchip_dp_pm_ops, + .of_match_table = of_match_ptr(rockchip_dp_dt_ids), + }, +}; + +module_platform_driver(rockchip_dp_driver); + +MODULE_AUTHOR("Yakir Yang <ykk@rock-chips.com>"); +MODULE_AUTHOR("Jeff chen <jeff.chen@rock-chips.com>"); +MODULE_DESCRIPTION("Rockchip Specific Analogix-DP Driver Extension"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/sun4i/Kconfig b/drivers/gpu/drm/sun4i/Kconfig new file mode 100644 index 000000000000..99510e64e91a --- /dev/null +++ b/drivers/gpu/drm/sun4i/Kconfig @@ -0,0 +1,14 @@ +config DRM_SUN4I + tristate "DRM Support for Allwinner A10 Display Engine" + depends on DRM && ARM + depends on ARCH_SUNXI || COMPILE_TEST + select DRM_GEM_CMA_HELPER + select DRM_KMS_HELPER + select DRM_KMS_CMA_HELPER + select DRM_PANEL + select REGMAP_MMIO + select VIDEOMODE_HELPERS + help + Choose this option if you have an Allwinner SoC with a + Display Engine. If M is selected the module will be called + sun4i-drm. diff --git a/drivers/gpu/drm/sun4i/Makefile b/drivers/gpu/drm/sun4i/Makefile new file mode 100644 index 000000000000..58cd55149827 --- /dev/null +++ b/drivers/gpu/drm/sun4i/Makefile @@ -0,0 +1,13 @@ +sun4i-drm-y += sun4i_crtc.o +sun4i-drm-y += sun4i_drv.o +sun4i-drm-y += sun4i_framebuffer.o +sun4i-drm-y += sun4i_layer.o + +sun4i-tcon-y += sun4i_tcon.o +sun4i-tcon-y += sun4i_rgb.o +sun4i-tcon-y += sun4i_dotclock.o + +obj-$(CONFIG_DRM_SUN4I) += sun4i-drm.o sun4i-tcon.o +obj-$(CONFIG_DRM_SUN4I) += sun4i_backend.o + +obj-$(CONFIG_DRM_SUN4I) += sun4i_tv.o diff --git a/drivers/gpu/drm/sun4i/sun4i_backend.c b/drivers/gpu/drm/sun4i/sun4i_backend.c new file mode 100644 index 000000000000..f7a15c1a93bf --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_backend.c @@ -0,0 +1,364 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include <drm/drmP.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_gem_cma_helper.h> +#include <drm/drm_plane_helper.h> + +#include <linux/component.h> +#include <linux/reset.h> + +#include "sun4i_backend.h" +#include "sun4i_drv.h" + +static u32 sunxi_rgb2yuv_coef[12] = { + 0x00000107, 0x00000204, 0x00000064, 0x00000108, + 0x00003f69, 0x00003ed6, 0x000001c1, 0x00000808, + 0x000001c1, 0x00003e88, 0x00003fb8, 0x00000808 +}; + +void sun4i_backend_apply_color_correction(struct sun4i_backend *backend) +{ + int i; + + DRM_DEBUG_DRIVER("Applying RGB to YUV color correction\n"); + + /* Set color correction */ + regmap_write(backend->regs, SUN4I_BACKEND_OCCTL_REG, + SUN4I_BACKEND_OCCTL_ENABLE); + + for (i = 0; i < 12; i++) + regmap_write(backend->regs, SUN4I_BACKEND_OCRCOEF_REG(i), + sunxi_rgb2yuv_coef[i]); +} +EXPORT_SYMBOL(sun4i_backend_apply_color_correction); + +void sun4i_backend_disable_color_correction(struct sun4i_backend *backend) +{ + DRM_DEBUG_DRIVER("Disabling color correction\n"); + + /* Disable color correction */ + regmap_update_bits(backend->regs, SUN4I_BACKEND_OCCTL_REG, + SUN4I_BACKEND_OCCTL_ENABLE, 0); +} +EXPORT_SYMBOL(sun4i_backend_disable_color_correction); + +void sun4i_backend_commit(struct sun4i_backend *backend) +{ + DRM_DEBUG_DRIVER("Committing changes\n"); + + regmap_write(backend->regs, SUN4I_BACKEND_REGBUFFCTL_REG, + SUN4I_BACKEND_REGBUFFCTL_AUTOLOAD_DIS | + SUN4I_BACKEND_REGBUFFCTL_LOADCTL); +} +EXPORT_SYMBOL(sun4i_backend_commit); + +void sun4i_backend_layer_enable(struct sun4i_backend *backend, + int layer, bool enable) +{ + u32 val; + + DRM_DEBUG_DRIVER("Enabling layer %d\n", layer); + + if (enable) + val = SUN4I_BACKEND_MODCTL_LAY_EN(layer); + else + val = 0; + + regmap_update_bits(backend->regs, SUN4I_BACKEND_MODCTL_REG, + SUN4I_BACKEND_MODCTL_LAY_EN(layer), val); +} +EXPORT_SYMBOL(sun4i_backend_layer_enable); + +static int sun4i_backend_drm_format_to_layer(u32 format, u32 *mode) +{ + switch (format) { + case DRM_FORMAT_ARGB8888: + *mode = SUN4I_BACKEND_LAY_FBFMT_ARGB8888; + break; + + case DRM_FORMAT_XRGB8888: + *mode = SUN4I_BACKEND_LAY_FBFMT_XRGB8888; + break; + + case DRM_FORMAT_RGB888: + *mode = SUN4I_BACKEND_LAY_FBFMT_RGB888; + break; + + default: + return -EINVAL; + } + + return 0; +} + +int sun4i_backend_update_layer_coord(struct sun4i_backend *backend, + int layer, struct drm_plane *plane) +{ + struct drm_plane_state *state = plane->state; + struct drm_framebuffer *fb = state->fb; + + DRM_DEBUG_DRIVER("Updating layer %d\n", layer); + + if (plane->type == DRM_PLANE_TYPE_PRIMARY) { + DRM_DEBUG_DRIVER("Primary layer, updating global size W: %u H: %u\n", + state->crtc_w, state->crtc_h); + regmap_write(backend->regs, SUN4I_BACKEND_DISSIZE_REG, + SUN4I_BACKEND_DISSIZE(state->crtc_w, + state->crtc_h)); + } + + /* Set the line width */ + DRM_DEBUG_DRIVER("Layer line width: %d bits\n", fb->pitches[0] * 8); + regmap_write(backend->regs, SUN4I_BACKEND_LAYLINEWIDTH_REG(layer), + fb->pitches[0] * 8); + + /* Set height and width */ + DRM_DEBUG_DRIVER("Layer size W: %u H: %u\n", + state->crtc_w, state->crtc_h); + regmap_write(backend->regs, SUN4I_BACKEND_LAYSIZE_REG(layer), + SUN4I_BACKEND_LAYSIZE(state->crtc_w, + state->crtc_h)); + + /* Set base coordinates */ + DRM_DEBUG_DRIVER("Layer coordinates X: %d Y: %d\n", + state->crtc_x, state->crtc_y); + regmap_write(backend->regs, SUN4I_BACKEND_LAYCOOR_REG(layer), + SUN4I_BACKEND_LAYCOOR(state->crtc_x, + state->crtc_y)); + + return 0; +} +EXPORT_SYMBOL(sun4i_backend_update_layer_coord); + +int sun4i_backend_update_layer_formats(struct sun4i_backend *backend, + int layer, struct drm_plane *plane) +{ + struct drm_plane_state *state = plane->state; + struct drm_framebuffer *fb = state->fb; + bool interlaced = false; + u32 val; + int ret; + + if (plane->state->crtc) + interlaced = plane->state->crtc->state->adjusted_mode.flags + & DRM_MODE_FLAG_INTERLACE; + + regmap_update_bits(backend->regs, SUN4I_BACKEND_MODCTL_REG, + SUN4I_BACKEND_MODCTL_ITLMOD_EN, + interlaced ? SUN4I_BACKEND_MODCTL_ITLMOD_EN : 0); + + DRM_DEBUG_DRIVER("Switching display backend interlaced mode %s\n", + interlaced ? "on" : "off"); + + ret = sun4i_backend_drm_format_to_layer(fb->pixel_format, &val); + if (ret) { + DRM_DEBUG_DRIVER("Invalid format\n"); + return val; + } + + regmap_update_bits(backend->regs, SUN4I_BACKEND_ATTCTL_REG1(layer), + SUN4I_BACKEND_ATTCTL_REG1_LAY_FBFMT, val); + + return 0; +} +EXPORT_SYMBOL(sun4i_backend_update_layer_formats); + +int sun4i_backend_update_layer_buffer(struct sun4i_backend *backend, + int layer, struct drm_plane *plane) +{ + struct drm_plane_state *state = plane->state; + struct drm_framebuffer *fb = state->fb; + struct drm_gem_cma_object *gem; + u32 lo_paddr, hi_paddr; + dma_addr_t paddr; + int bpp; + + /* Get the physical address of the buffer in memory */ + gem = drm_fb_cma_get_gem_obj(fb, 0); + + DRM_DEBUG_DRIVER("Using GEM @ 0x%x\n", gem->paddr); + + /* Compute the start of the displayed memory */ + bpp = drm_format_plane_cpp(fb->pixel_format, 0); + paddr = gem->paddr + fb->offsets[0]; + paddr += (state->src_x >> 16) * bpp; + paddr += (state->src_y >> 16) * fb->pitches[0]; + + DRM_DEBUG_DRIVER("Setting buffer address to 0x%x\n", paddr); + + /* Write the 32 lower bits of the address (in bits) */ + lo_paddr = paddr << 3; + DRM_DEBUG_DRIVER("Setting address lower bits to 0x%x\n", lo_paddr); + regmap_write(backend->regs, SUN4I_BACKEND_LAYFB_L32ADD_REG(layer), + lo_paddr); + + /* And the upper bits */ + hi_paddr = paddr >> 29; + DRM_DEBUG_DRIVER("Setting address high bits to 0x%x\n", hi_paddr); + regmap_update_bits(backend->regs, SUN4I_BACKEND_LAYFB_H4ADD_REG, + SUN4I_BACKEND_LAYFB_H4ADD_MSK(layer), + SUN4I_BACKEND_LAYFB_H4ADD(layer, hi_paddr)); + + return 0; +} +EXPORT_SYMBOL(sun4i_backend_update_layer_buffer); + +static struct regmap_config sun4i_backend_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x5800, +}; + +static int sun4i_backend_bind(struct device *dev, struct device *master, + void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct drm_device *drm = data; + struct sun4i_drv *drv = drm->dev_private; + struct sun4i_backend *backend; + struct resource *res; + void __iomem *regs; + int i, ret; + + backend = devm_kzalloc(dev, sizeof(*backend), GFP_KERNEL); + if (!backend) + return -ENOMEM; + dev_set_drvdata(dev, backend); + drv->backend = backend; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(dev, res); + if (IS_ERR(regs)) { + dev_err(dev, "Couldn't map the backend registers\n"); + return PTR_ERR(regs); + } + + backend->regs = devm_regmap_init_mmio(dev, regs, + &sun4i_backend_regmap_config); + if (IS_ERR(backend->regs)) { + dev_err(dev, "Couldn't create the backend0 regmap\n"); + return PTR_ERR(backend->regs); + } + + backend->reset = devm_reset_control_get(dev, NULL); + if (IS_ERR(backend->reset)) { + dev_err(dev, "Couldn't get our reset line\n"); + return PTR_ERR(backend->reset); + } + + ret = reset_control_deassert(backend->reset); + if (ret) { + dev_err(dev, "Couldn't deassert our reset line\n"); + return ret; + } + + backend->bus_clk = devm_clk_get(dev, "ahb"); + if (IS_ERR(backend->bus_clk)) { + dev_err(dev, "Couldn't get the backend bus clock\n"); + ret = PTR_ERR(backend->bus_clk); + goto err_assert_reset; + } + clk_prepare_enable(backend->bus_clk); + + backend->mod_clk = devm_clk_get(dev, "mod"); + if (IS_ERR(backend->mod_clk)) { + dev_err(dev, "Couldn't get the backend module clock\n"); + ret = PTR_ERR(backend->mod_clk); + goto err_disable_bus_clk; + } + clk_prepare_enable(backend->mod_clk); + + backend->ram_clk = devm_clk_get(dev, "ram"); + if (IS_ERR(backend->ram_clk)) { + dev_err(dev, "Couldn't get the backend RAM clock\n"); + ret = PTR_ERR(backend->ram_clk); + goto err_disable_mod_clk; + } + clk_prepare_enable(backend->ram_clk); + + /* Reset the registers */ + for (i = 0x800; i < 0x1000; i += 4) + regmap_write(backend->regs, i, 0); + + /* Disable registers autoloading */ + regmap_write(backend->regs, SUN4I_BACKEND_REGBUFFCTL_REG, + SUN4I_BACKEND_REGBUFFCTL_AUTOLOAD_DIS); + + /* Enable the backend */ + regmap_write(backend->regs, SUN4I_BACKEND_MODCTL_REG, + SUN4I_BACKEND_MODCTL_DEBE_EN | + SUN4I_BACKEND_MODCTL_START_CTL); + + return 0; + +err_disable_mod_clk: + clk_disable_unprepare(backend->mod_clk); +err_disable_bus_clk: + clk_disable_unprepare(backend->bus_clk); +err_assert_reset: + reset_control_assert(backend->reset); + return ret; +} + +static void sun4i_backend_unbind(struct device *dev, struct device *master, + void *data) +{ + struct sun4i_backend *backend = dev_get_drvdata(dev); + + clk_disable_unprepare(backend->ram_clk); + clk_disable_unprepare(backend->mod_clk); + clk_disable_unprepare(backend->bus_clk); + reset_control_assert(backend->reset); +} + +static struct component_ops sun4i_backend_ops = { + .bind = sun4i_backend_bind, + .unbind = sun4i_backend_unbind, +}; + +static int sun4i_backend_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &sun4i_backend_ops); +} + +static int sun4i_backend_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &sun4i_backend_ops); + + return 0; +} + +static const struct of_device_id sun4i_backend_of_table[] = { + { .compatible = "allwinner,sun5i-a13-display-backend" }, + { } +}; +MODULE_DEVICE_TABLE(of, sun4i_backend_of_table); + +static struct platform_driver sun4i_backend_platform_driver = { + .probe = sun4i_backend_probe, + .remove = sun4i_backend_remove, + .driver = { + .name = "sun4i-backend", + .of_match_table = sun4i_backend_of_table, + }, +}; +module_platform_driver(sun4i_backend_platform_driver); + +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); +MODULE_DESCRIPTION("Allwinner A10 Display Backend Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sun4i/sun4i_backend.h b/drivers/gpu/drm/sun4i/sun4i_backend.h new file mode 100644 index 000000000000..7070bb3434e5 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_backend.h @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#ifndef _SUN4I_BACKEND_H_ +#define _SUN4I_BACKEND_H_ + +#include <linux/clk.h> +#include <linux/regmap.h> +#include <linux/reset.h> + +#define SUN4I_BACKEND_MODCTL_REG 0x800 +#define SUN4I_BACKEND_MODCTL_LINE_SEL BIT(29) +#define SUN4I_BACKEND_MODCTL_ITLMOD_EN BIT(28) +#define SUN4I_BACKEND_MODCTL_OUT_SEL GENMASK(22, 20) +#define SUN4I_BACKEND_MODCTL_OUT_LCD (0 << 20) +#define SUN4I_BACKEND_MODCTL_OUT_FE0 (6 << 20) +#define SUN4I_BACKEND_MODCTL_OUT_FE1 (7 << 20) +#define SUN4I_BACKEND_MODCTL_HWC_EN BIT(16) +#define SUN4I_BACKEND_MODCTL_LAY_EN(l) BIT(8 + l) +#define SUN4I_BACKEND_MODCTL_OCSC_EN BIT(5) +#define SUN4I_BACKEND_MODCTL_DFLK_EN BIT(4) +#define SUN4I_BACKEND_MODCTL_DLP_START_CTL BIT(2) +#define SUN4I_BACKEND_MODCTL_START_CTL BIT(1) +#define SUN4I_BACKEND_MODCTL_DEBE_EN BIT(0) + +#define SUN4I_BACKEND_BACKCOLOR_REG 0x804 +#define SUN4I_BACKEND_BACKCOLOR(r, g, b) (((r) << 16) | ((g) << 8) | (b)) + +#define SUN4I_BACKEND_DISSIZE_REG 0x808 +#define SUN4I_BACKEND_DISSIZE(w, h) (((((h) - 1) & 0xffff) << 16) | \ + (((w) - 1) & 0xffff)) + +#define SUN4I_BACKEND_LAYSIZE_REG(l) (0x810 + (0x4 * (l))) +#define SUN4I_BACKEND_LAYSIZE(w, h) (((((h) - 1) & 0x1fff) << 16) | \ + (((w) - 1) & 0x1fff)) + +#define SUN4I_BACKEND_LAYCOOR_REG(l) (0x820 + (0x4 * (l))) +#define SUN4I_BACKEND_LAYCOOR(x, y) ((((u32)(y) & 0xffff) << 16) | \ + ((u32)(x) & 0xffff)) + +#define SUN4I_BACKEND_LAYLINEWIDTH_REG(l) (0x840 + (0x4 * (l))) + +#define SUN4I_BACKEND_LAYFB_L32ADD_REG(l) (0x850 + (0x4 * (l))) + +#define SUN4I_BACKEND_LAYFB_H4ADD_REG 0x860 +#define SUN4I_BACKEND_LAYFB_H4ADD_MSK(l) GENMASK(3 + ((l) * 8), 0) +#define SUN4I_BACKEND_LAYFB_H4ADD(l, val) ((val) << ((l) * 8)) + +#define SUN4I_BACKEND_REGBUFFCTL_REG 0x870 +#define SUN4I_BACKEND_REGBUFFCTL_AUTOLOAD_DIS BIT(1) +#define SUN4I_BACKEND_REGBUFFCTL_LOADCTL BIT(0) + +#define SUN4I_BACKEND_CKMAX_REG 0x880 +#define SUN4I_BACKEND_CKMIN_REG 0x884 +#define SUN4I_BACKEND_CKCFG_REG 0x888 +#define SUN4I_BACKEND_ATTCTL_REG0(l) (0x890 + (0x4 * (l))) +#define SUN4I_BACKEND_ATTCTL_REG0_LAY_PIPESEL_MASK BIT(15) +#define SUN4I_BACKEND_ATTCTL_REG0_LAY_PIPESEL(x) ((x) << 15) +#define SUN4I_BACKEND_ATTCTL_REG0_LAY_PRISEL_MASK GENMASK(11, 10) +#define SUN4I_BACKEND_ATTCTL_REG0_LAY_PRISEL(x) ((x) << 10) + +#define SUN4I_BACKEND_ATTCTL_REG1(l) (0x8a0 + (0x4 * (l))) +#define SUN4I_BACKEND_ATTCTL_REG1_LAY_HSCAFCT GENMASK(15, 14) +#define SUN4I_BACKEND_ATTCTL_REG1_LAY_WSCAFCT GENMASK(13, 12) +#define SUN4I_BACKEND_ATTCTL_REG1_LAY_FBFMT GENMASK(11, 8) +#define SUN4I_BACKEND_LAY_FBFMT_1BPP (0 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_2BPP (1 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_4BPP (2 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_8BPP (3 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_RGB655 (4 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_RGB565 (5 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_RGB556 (6 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_ARGB1555 (7 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_RGBA5551 (8 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_XRGB8888 (9 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_ARGB8888 (10 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_RGB888 (11 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_ARGB4444 (12 << 8) +#define SUN4I_BACKEND_LAY_FBFMT_RGBA4444 (13 << 8) + +#define SUN4I_BACKEND_DLCDPCTL_REG 0x8b0 +#define SUN4I_BACKEND_DLCDPFRMBUF_ADDRCTL_REG 0x8b4 +#define SUN4I_BACKEND_DLCDPCOOR_REG0 0x8b8 +#define SUN4I_BACKEND_DLCDPCOOR_REG1 0x8bc + +#define SUN4I_BACKEND_INT_EN_REG 0x8c0 +#define SUN4I_BACKEND_INT_FLAG_REG 0x8c4 +#define SUN4I_BACKEND_REG_LOAD_FINISHED BIT(1) + +#define SUN4I_BACKEND_HWCCTL_REG 0x8d8 +#define SUN4I_BACKEND_HWCFBCTL_REG 0x8e0 +#define SUN4I_BACKEND_WBCTL_REG 0x8f0 +#define SUN4I_BACKEND_WBADD_REG 0x8f4 +#define SUN4I_BACKEND_WBLINEWIDTH_REG 0x8f8 +#define SUN4I_BACKEND_SPREN_REG 0x900 +#define SUN4I_BACKEND_SPRFMTCTL_REG 0x908 +#define SUN4I_BACKEND_SPRALPHACTL_REG 0x90c +#define SUN4I_BACKEND_IYUVCTL_REG 0x920 +#define SUN4I_BACKEND_IYUVADD_REG(c) (0x930 + (0x4 * (c))) +#define SUN4I_BACKEND_IYUVLINEWITDTH_REG(c) (0x940 + (0x4 * (c))) +#define SUN4I_BACKEND_YGCOEF_REG(c) (0x950 + (0x4 * (c))) +#define SUN4I_BACKEND_YGCONS_REG 0x95c +#define SUN4I_BACKEND_URCOEF_REG(c) (0x960 + (0x4 * (c))) +#define SUN4I_BACKEND_URCONS_REG 0x96c +#define SUN4I_BACKEND_VBCOEF_REG(c) (0x970 + (0x4 * (c))) +#define SUN4I_BACKEND_VBCONS_REG 0x97c +#define SUN4I_BACKEND_KSCTL_REG 0x980 +#define SUN4I_BACKEND_KSBKCOLOR_REG 0x984 +#define SUN4I_BACKEND_KSFSTLINEWIDTH_REG 0x988 +#define SUN4I_BACKEND_KSVSCAFCT_REG 0x98c +#define SUN4I_BACKEND_KSHSCACOEF_REG(x) (0x9a0 + (0x4 * (x))) +#define SUN4I_BACKEND_OCCTL_REG 0x9c0 +#define SUN4I_BACKEND_OCCTL_ENABLE BIT(0) + +#define SUN4I_BACKEND_OCRCOEF_REG(x) (0x9d0 + (0x4 * (x))) +#define SUN4I_BACKEND_OCRCONS_REG 0x9dc +#define SUN4I_BACKEND_OCGCOEF_REG(x) (0x9e0 + (0x4 * (x))) +#define SUN4I_BACKEND_OCGCONS_REG 0x9ec +#define SUN4I_BACKEND_OCBCOEF_REG(x) (0x9f0 + (0x4 * (x))) +#define SUN4I_BACKEND_OCBCONS_REG 0x9fc +#define SUN4I_BACKEND_SPRCOORCTL_REG(s) (0xa00 + (0x4 * (s))) +#define SUN4I_BACKEND_SPRATTCTL_REG(s) (0xb00 + (0x4 * (s))) +#define SUN4I_BACKEND_SPRADD_REG(s) (0xc00 + (0x4 * (s))) +#define SUN4I_BACKEND_SPRLINEWIDTH_REG(s) (0xd00 + (0x4 * (s))) + +#define SUN4I_BACKEND_SPRPALTAB_OFF 0x4000 +#define SUN4I_BACKEND_GAMMATAB_OFF 0x4400 +#define SUN4I_BACKEND_HWCPATTERN_OFF 0x4800 +#define SUN4I_BACKEND_HWCCOLORTAB_OFF 0x4c00 +#define SUN4I_BACKEND_PIPE_OFF(p) (0x5000 + (0x400 * (p))) + +struct sun4i_backend { + struct regmap *regs; + + struct reset_control *reset; + + struct clk *bus_clk; + struct clk *mod_clk; + struct clk *ram_clk; +}; + +void sun4i_backend_apply_color_correction(struct sun4i_backend *backend); +void sun4i_backend_disable_color_correction(struct sun4i_backend *backend); + +void sun4i_backend_commit(struct sun4i_backend *backend); + +void sun4i_backend_layer_enable(struct sun4i_backend *backend, + int layer, bool enable); +int sun4i_backend_update_layer_coord(struct sun4i_backend *backend, + int layer, struct drm_plane *plane); +int sun4i_backend_update_layer_formats(struct sun4i_backend *backend, + int layer, struct drm_plane *plane); +int sun4i_backend_update_layer_buffer(struct sun4i_backend *backend, + int layer, struct drm_plane *plane); + +#endif /* _SUN4I_BACKEND_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun4i_crtc.c b/drivers/gpu/drm/sun4i/sun4i_crtc.c new file mode 100644 index 000000000000..4182a21f5923 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_crtc.c @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include <drm/drmP.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_modes.h> + +#include <linux/clk-provider.h> +#include <linux/ioport.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/regmap.h> + +#include <video/videomode.h> + +#include "sun4i_backend.h" +#include "sun4i_crtc.h" +#include "sun4i_drv.h" +#include "sun4i_tcon.h" + +static void sun4i_crtc_atomic_begin(struct drm_crtc *crtc, + struct drm_crtc_state *old_state) +{ + struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc); + struct drm_device *dev = crtc->dev; + unsigned long flags; + + if (crtc->state->event) { + WARN_ON(drm_crtc_vblank_get(crtc) != 0); + + spin_lock_irqsave(&dev->event_lock, flags); + scrtc->event = crtc->state->event; + spin_unlock_irqrestore(&dev->event_lock, flags); + crtc->state->event = NULL; + } +} + +static void sun4i_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_crtc_state *old_state) +{ + struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc); + struct sun4i_drv *drv = scrtc->drv; + + DRM_DEBUG_DRIVER("Committing plane changes\n"); + + sun4i_backend_commit(drv->backend); +} + +static void sun4i_crtc_disable(struct drm_crtc *crtc) +{ + struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc); + struct sun4i_drv *drv = scrtc->drv; + + DRM_DEBUG_DRIVER("Disabling the CRTC\n"); + + sun4i_tcon_disable(drv->tcon); +} + +static void sun4i_crtc_enable(struct drm_crtc *crtc) +{ + struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(crtc); + struct sun4i_drv *drv = scrtc->drv; + + DRM_DEBUG_DRIVER("Enabling the CRTC\n"); + + sun4i_tcon_enable(drv->tcon); +} + +static const struct drm_crtc_helper_funcs sun4i_crtc_helper_funcs = { + .atomic_begin = sun4i_crtc_atomic_begin, + .atomic_flush = sun4i_crtc_atomic_flush, + .disable = sun4i_crtc_disable, + .enable = sun4i_crtc_enable, +}; + +static const struct drm_crtc_funcs sun4i_crtc_funcs = { + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .destroy = drm_crtc_cleanup, + .page_flip = drm_atomic_helper_page_flip, + .reset = drm_atomic_helper_crtc_reset, + .set_config = drm_atomic_helper_set_config, +}; + +struct sun4i_crtc *sun4i_crtc_init(struct drm_device *drm) +{ + struct sun4i_drv *drv = drm->dev_private; + struct sun4i_crtc *scrtc; + int ret; + + scrtc = devm_kzalloc(drm->dev, sizeof(*scrtc), GFP_KERNEL); + if (!scrtc) + return NULL; + scrtc->drv = drv; + + ret = drm_crtc_init_with_planes(drm, &scrtc->crtc, + drv->primary, + NULL, + &sun4i_crtc_funcs, + NULL); + if (ret) { + dev_err(drm->dev, "Couldn't init DRM CRTC\n"); + return NULL; + } + + drm_crtc_helper_add(&scrtc->crtc, &sun4i_crtc_helper_funcs); + + return scrtc; +} diff --git a/drivers/gpu/drm/sun4i/sun4i_crtc.h b/drivers/gpu/drm/sun4i/sun4i_crtc.h new file mode 100644 index 000000000000..dec8ce4d9b25 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_crtc.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#ifndef _SUN4I_CRTC_H_ +#define _SUN4I_CRTC_H_ + +struct sun4i_crtc { + struct drm_crtc crtc; + struct drm_pending_vblank_event *event; + + struct sun4i_drv *drv; +}; + +static inline struct sun4i_crtc *drm_crtc_to_sun4i_crtc(struct drm_crtc *crtc) +{ + return container_of(crtc, struct sun4i_crtc, crtc); +} + +struct sun4i_crtc *sun4i_crtc_init(struct drm_device *drm); + +#endif /* _SUN4I_CRTC_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun4i_dotclock.c b/drivers/gpu/drm/sun4i/sun4i_dotclock.c new file mode 100644 index 000000000000..3ff668cb463c --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_dotclock.c @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2016 Free Electrons + * Copyright (C) 2016 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include <linux/clk-provider.h> +#include <linux/regmap.h> + +#include "sun4i_tcon.h" + +struct sun4i_dclk { + struct clk_hw hw; + struct regmap *regmap; +}; + +static inline struct sun4i_dclk *hw_to_dclk(struct clk_hw *hw) +{ + return container_of(hw, struct sun4i_dclk, hw); +} + +static void sun4i_dclk_disable(struct clk_hw *hw) +{ + struct sun4i_dclk *dclk = hw_to_dclk(hw); + + regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG, + BIT(SUN4I_TCON0_DCLK_GATE_BIT), 0); +} + +static int sun4i_dclk_enable(struct clk_hw *hw) +{ + struct sun4i_dclk *dclk = hw_to_dclk(hw); + + return regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG, + BIT(SUN4I_TCON0_DCLK_GATE_BIT), + BIT(SUN4I_TCON0_DCLK_GATE_BIT)); +} + +static int sun4i_dclk_is_enabled(struct clk_hw *hw) +{ + struct sun4i_dclk *dclk = hw_to_dclk(hw); + u32 val; + + regmap_read(dclk->regmap, SUN4I_TCON0_DCLK_REG, &val); + + return val & BIT(SUN4I_TCON0_DCLK_GATE_BIT); +} + +static unsigned long sun4i_dclk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct sun4i_dclk *dclk = hw_to_dclk(hw); + u32 val; + + regmap_read(dclk->regmap, SUN4I_TCON0_DCLK_REG, &val); + + val >>= SUN4I_TCON0_DCLK_DIV_SHIFT; + val &= SUN4I_TCON0_DCLK_DIV_WIDTH; + + if (!val) + val = 1; + + return parent_rate / val; +} + +static long sun4i_dclk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + return *parent_rate / DIV_ROUND_CLOSEST(*parent_rate, rate); +} + +static int sun4i_dclk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct sun4i_dclk *dclk = hw_to_dclk(hw); + int div = DIV_ROUND_CLOSEST(parent_rate, rate); + + return regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG, + GENMASK(6, 0), div); +} + +static int sun4i_dclk_get_phase(struct clk_hw *hw) +{ + struct sun4i_dclk *dclk = hw_to_dclk(hw); + u32 val; + + regmap_read(dclk->regmap, SUN4I_TCON0_IO_POL_REG, &val); + + val >>= 28; + val &= 3; + + return val * 120; +} + +static int sun4i_dclk_set_phase(struct clk_hw *hw, int degrees) +{ + struct sun4i_dclk *dclk = hw_to_dclk(hw); + + regmap_update_bits(dclk->regmap, SUN4I_TCON0_IO_POL_REG, + GENMASK(29, 28), + degrees / 120); + + return 0; +} + +static const struct clk_ops sun4i_dclk_ops = { + .disable = sun4i_dclk_disable, + .enable = sun4i_dclk_enable, + .is_enabled = sun4i_dclk_is_enabled, + + .recalc_rate = sun4i_dclk_recalc_rate, + .round_rate = sun4i_dclk_round_rate, + .set_rate = sun4i_dclk_set_rate, + + .get_phase = sun4i_dclk_get_phase, + .set_phase = sun4i_dclk_set_phase, +}; + +int sun4i_dclk_create(struct device *dev, struct sun4i_tcon *tcon) +{ + const char *clk_name, *parent_name; + struct clk_init_data init; + struct sun4i_dclk *dclk; + + parent_name = __clk_get_name(tcon->sclk0); + of_property_read_string_index(dev->of_node, "clock-output-names", 0, + &clk_name); + + dclk = devm_kzalloc(dev, sizeof(*dclk), GFP_KERNEL); + if (!dclk) + return -ENOMEM; + + init.name = clk_name; + init.ops = &sun4i_dclk_ops; + init.parent_names = &parent_name; + init.num_parents = 1; + + dclk->regmap = tcon->regs; + dclk->hw.init = &init; + + tcon->dclk = clk_register(dev, &dclk->hw); + if (IS_ERR(tcon->dclk)) + return PTR_ERR(tcon->dclk); + + return 0; +} +EXPORT_SYMBOL(sun4i_dclk_create); + +int sun4i_dclk_free(struct sun4i_tcon *tcon) +{ + clk_unregister(tcon->dclk); + return 0; +} +EXPORT_SYMBOL(sun4i_dclk_free); diff --git a/drivers/gpu/drm/sun4i/sun4i_dotclock.h b/drivers/gpu/drm/sun4i/sun4i_dotclock.h new file mode 100644 index 000000000000..d5e25fa9eff1 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_dotclock.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#ifndef _SUN4I_DOTCLOCK_H_ +#define _SUN4I_DOTCLOCK_H_ + +struct sun4i_tcon; + +int sun4i_dclk_create(struct device *dev, struct sun4i_tcon *tcon); +int sun4i_dclk_free(struct sun4i_tcon *tcon); + +#endif /* _SUN4I_DOTCLOCK_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.c b/drivers/gpu/drm/sun4i/sun4i_drv.c new file mode 100644 index 000000000000..76e922bb60e5 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_drv.c @@ -0,0 +1,358 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include <linux/component.h> +#include <linux/of_graph.h> + +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_gem_cma_helper.h> + +#include "sun4i_crtc.h" +#include "sun4i_drv.h" +#include "sun4i_framebuffer.h" +#include "sun4i_layer.h" +#include "sun4i_tcon.h" + +static int sun4i_drv_connector_plug_all(struct drm_device *drm) +{ + struct drm_connector *connector, *failed; + int ret; + + mutex_lock(&drm->mode_config.mutex); + list_for_each_entry(connector, &drm->mode_config.connector_list, head) { + ret = drm_connector_register(connector); + if (ret) { + failed = connector; + goto err; + } + } + mutex_unlock(&drm->mode_config.mutex); + return 0; + +err: + list_for_each_entry(connector, &drm->mode_config.connector_list, head) { + if (failed == connector) + break; + + drm_connector_unregister(connector); + } + mutex_unlock(&drm->mode_config.mutex); + + return ret; +} + +static int sun4i_drv_enable_vblank(struct drm_device *drm, unsigned int pipe) +{ + struct sun4i_drv *drv = drm->dev_private; + struct sun4i_tcon *tcon = drv->tcon; + + DRM_DEBUG_DRIVER("Enabling VBLANK on pipe %d\n", pipe); + + sun4i_tcon_enable_vblank(tcon, true); + + return 0; +} + +static void sun4i_drv_disable_vblank(struct drm_device *drm, unsigned int pipe) +{ + struct sun4i_drv *drv = drm->dev_private; + struct sun4i_tcon *tcon = drv->tcon; + + DRM_DEBUG_DRIVER("Disabling VBLANK on pipe %d\n", pipe); + + sun4i_tcon_enable_vblank(tcon, false); +} + +static const struct file_operations sun4i_drv_fops = { + .owner = THIS_MODULE, + .open = drm_open, + .release = drm_release, + .unlocked_ioctl = drm_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = drm_compat_ioctl, +#endif + .poll = drm_poll, + .read = drm_read, + .llseek = no_llseek, + .mmap = drm_gem_cma_mmap, +}; + +static struct drm_driver sun4i_drv_driver = { + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME | DRIVER_ATOMIC, + + /* Generic Operations */ + .fops = &sun4i_drv_fops, + .name = "sun4i-drm", + .desc = "Allwinner sun4i Display Engine", + .date = "20150629", + .major = 1, + .minor = 0, + + /* GEM Operations */ + .dumb_create = drm_gem_cma_dumb_create, + .dumb_destroy = drm_gem_dumb_destroy, + .dumb_map_offset = drm_gem_cma_dumb_map_offset, + .gem_free_object = drm_gem_cma_free_object, + .gem_vm_ops = &drm_gem_cma_vm_ops, + + /* PRIME Operations */ + .prime_handle_to_fd = drm_gem_prime_handle_to_fd, + .prime_fd_to_handle = drm_gem_prime_fd_to_handle, + .gem_prime_import = drm_gem_prime_import, + .gem_prime_export = drm_gem_prime_export, + .gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table, + .gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table, + .gem_prime_vmap = drm_gem_cma_prime_vmap, + .gem_prime_vunmap = drm_gem_cma_prime_vunmap, + .gem_prime_mmap = drm_gem_cma_prime_mmap, + + /* Frame Buffer Operations */ + + /* VBlank Operations */ + .get_vblank_counter = drm_vblank_count, + .enable_vblank = sun4i_drv_enable_vblank, + .disable_vblank = sun4i_drv_disable_vblank, +}; + +static int sun4i_drv_bind(struct device *dev) +{ + struct drm_device *drm; + struct sun4i_drv *drv; + int ret; + + drm = drm_dev_alloc(&sun4i_drv_driver, dev); + if (!drm) + return -ENOMEM; + + ret = drm_dev_set_unique(drm, dev_name(drm->dev)); + if (ret) + goto free_drm; + + drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL); + if (!drv) { + ret = -ENOMEM; + goto free_drm; + } + drm->dev_private = drv; + + drm_vblank_init(drm, 1); + drm_mode_config_init(drm); + + ret = component_bind_all(drm->dev, drm); + if (ret) { + dev_err(drm->dev, "Couldn't bind all pipelines components\n"); + goto free_drm; + } + + /* Create our layers */ + drv->layers = sun4i_layers_init(drm); + if (!drv->layers) { + dev_err(drm->dev, "Couldn't create the planes\n"); + ret = -EINVAL; + goto free_drm; + } + + /* Create our CRTC */ + drv->crtc = sun4i_crtc_init(drm); + if (!drv->crtc) { + dev_err(drm->dev, "Couldn't create the CRTC\n"); + ret = -EINVAL; + goto free_drm; + } + drm->irq_enabled = true; + + /* Create our framebuffer */ + drv->fbdev = sun4i_framebuffer_init(drm); + if (IS_ERR(drv->fbdev)) { + dev_err(drm->dev, "Couldn't create our framebuffer\n"); + ret = PTR_ERR(drv->fbdev); + goto free_drm; + } + + /* Enable connectors polling */ + drm_kms_helper_poll_init(drm); + + ret = drm_dev_register(drm, 0); + if (ret) + goto free_drm; + + ret = sun4i_drv_connector_plug_all(drm); + if (ret) + goto unregister_drm; + + return 0; + +unregister_drm: + drm_dev_unregister(drm); +free_drm: + drm_dev_unref(drm); + return ret; +} + +static void sun4i_drv_unbind(struct device *dev) +{ + struct drm_device *drm = dev_get_drvdata(dev); + + drm_dev_unregister(drm); + drm_kms_helper_poll_fini(drm); + sun4i_framebuffer_free(drm); + drm_vblank_cleanup(drm); + drm_dev_unref(drm); +} + +static const struct component_master_ops sun4i_drv_master_ops = { + .bind = sun4i_drv_bind, + .unbind = sun4i_drv_unbind, +}; + +static bool sun4i_drv_node_is_frontend(struct device_node *node) +{ + return of_device_is_compatible(node, + "allwinner,sun5i-a13-display-frontend"); +} + +static bool sun4i_drv_node_is_tcon(struct device_node *node) +{ + return of_device_is_compatible(node, "allwinner,sun5i-a13-tcon"); +} + +static int compare_of(struct device *dev, void *data) +{ + DRM_DEBUG_DRIVER("Comparing of node %s with %s\n", + of_node_full_name(dev->of_node), + of_node_full_name(data)); + + return dev->of_node == data; +} + +static int sun4i_drv_add_endpoints(struct device *dev, + struct component_match **match, + struct device_node *node) +{ + struct device_node *port, *ep, *remote; + int count = 0; + + /* + * We don't support the frontend for now, so we will never + * have a device bound. Just skip over it, but we still want + * the rest our pipeline to be added. + */ + if (!sun4i_drv_node_is_frontend(node) && + !of_device_is_available(node)) + return 0; + + if (!sun4i_drv_node_is_frontend(node)) { + /* Add current component */ + DRM_DEBUG_DRIVER("Adding component %s\n", + of_node_full_name(node)); + component_match_add(dev, match, compare_of, node); + count++; + } + + /* Inputs are listed first, then outputs */ + port = of_graph_get_port_by_id(node, 1); + if (!port) { + DRM_DEBUG_DRIVER("No output to bind\n"); + return count; + } + + for_each_available_child_of_node(port, ep) { + remote = of_graph_get_remote_port_parent(ep); + if (!remote) { + DRM_DEBUG_DRIVER("Error retrieving the output node\n"); + of_node_put(remote); + continue; + } + + /* + * If the node is our TCON, the first port is used for our + * panel, and will not be part of the + * component framework. + */ + if (sun4i_drv_node_is_tcon(node)) { + struct of_endpoint endpoint; + + if (of_graph_parse_endpoint(ep, &endpoint)) { + DRM_DEBUG_DRIVER("Couldn't parse endpoint\n"); + continue; + } + + if (!endpoint.id) { + DRM_DEBUG_DRIVER("Endpoint is our panel... skipping\n"); + continue; + } + } + + /* Walk down our tree */ + count += sun4i_drv_add_endpoints(dev, match, remote); + + of_node_put(remote); + } + + return count; +} + +static int sun4i_drv_probe(struct platform_device *pdev) +{ + struct component_match *match = NULL; + struct device_node *np = pdev->dev.of_node; + int i, count = 0; + + for (i = 0;; i++) { + struct device_node *pipeline = of_parse_phandle(np, + "allwinner,pipelines", + i); + if (!pipeline) + break; + + count += sun4i_drv_add_endpoints(&pdev->dev, &match, + pipeline); + + DRM_DEBUG_DRIVER("Queued %d outputs on pipeline %d\n", + count, i); + } + + if (count) + return component_master_add_with_match(&pdev->dev, + &sun4i_drv_master_ops, + match); + else + return 0; +} + +static int sun4i_drv_remove(struct platform_device *pdev) +{ + return 0; +} + +static const struct of_device_id sun4i_drv_of_table[] = { + { .compatible = "allwinner,sun5i-a13-display-engine" }, + { } +}; +MODULE_DEVICE_TABLE(of, sun4i_drv_of_table); + +static struct platform_driver sun4i_drv_platform_driver = { + .probe = sun4i_drv_probe, + .remove = sun4i_drv_remove, + .driver = { + .name = "sun4i-drm", + .of_match_table = sun4i_drv_of_table, + }, +}; +module_platform_driver(sun4i_drv_platform_driver); + +MODULE_AUTHOR("Boris Brezillon <boris.brezillon@free-electrons.com>"); +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); +MODULE_DESCRIPTION("Allwinner A10 Display Engine DRM/KMS Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sun4i/sun4i_drv.h b/drivers/gpu/drm/sun4i/sun4i_drv.h new file mode 100644 index 000000000000..597353eab728 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_drv.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#ifndef _SUN4I_DRV_H_ +#define _SUN4I_DRV_H_ + +#include <linux/clk.h> +#include <linux/regmap.h> + +struct sun4i_drv { + struct sun4i_backend *backend; + struct sun4i_crtc *crtc; + struct sun4i_tcon *tcon; + + struct drm_plane *primary; + struct drm_fbdev_cma *fbdev; + + struct sun4i_layer **layers; +}; + +#endif /* _SUN4I_DRV_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun4i_framebuffer.c b/drivers/gpu/drm/sun4i/sun4i_framebuffer.c new file mode 100644 index 000000000000..a0b30c216a5b --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_framebuffer.c @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drmP.h> + +#include "sun4i_drv.h" + +static void sun4i_de_output_poll_changed(struct drm_device *drm) +{ + struct sun4i_drv *drv = drm->dev_private; + + if (drv->fbdev) + drm_fbdev_cma_hotplug_event(drv->fbdev); +} + +static const struct drm_mode_config_funcs sun4i_de_mode_config_funcs = { + .output_poll_changed = sun4i_de_output_poll_changed, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, + .fb_create = drm_fb_cma_create, +}; + +struct drm_fbdev_cma *sun4i_framebuffer_init(struct drm_device *drm) +{ + drm_mode_config_reset(drm); + + drm->mode_config.max_width = 8192; + drm->mode_config.max_height = 8192; + + drm->mode_config.funcs = &sun4i_de_mode_config_funcs; + + return drm_fbdev_cma_init(drm, 32, + drm->mode_config.num_crtc, + drm->mode_config.num_connector); +} + +void sun4i_framebuffer_free(struct drm_device *drm) +{ + struct sun4i_drv *drv = drm->dev_private; + + drm_fbdev_cma_fini(drv->fbdev); + drm_mode_config_cleanup(drm); +} diff --git a/drivers/gpu/drm/sun4i/sun4i_framebuffer.h b/drivers/gpu/drm/sun4i/sun4i_framebuffer.h new file mode 100644 index 000000000000..3afd65252ee0 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_framebuffer.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#ifndef _SUN4I_FRAMEBUFFER_H_ +#define _SUN4I_FRAMEBUFFER_H_ + +struct drm_fbdev_cma *sun4i_framebuffer_init(struct drm_device *drm); +void sun4i_framebuffer_free(struct drm_device *drm); + +#endif /* _SUN4I_FRAMEBUFFER_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun4i_layer.c b/drivers/gpu/drm/sun4i/sun4i_layer.c new file mode 100644 index 000000000000..068ab806309b --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_layer.c @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_plane_helper.h> +#include <drm/drmP.h> + +#include "sun4i_backend.h" +#include "sun4i_drv.h" +#include "sun4i_layer.h" + +#define SUN4I_NUM_LAYERS 2 + +static int sun4i_backend_layer_atomic_check(struct drm_plane *plane, + struct drm_plane_state *state) +{ + return 0; +} + +static void sun4i_backend_layer_atomic_disable(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct sun4i_layer *layer = plane_to_sun4i_layer(plane); + struct sun4i_drv *drv = layer->drv; + struct sun4i_backend *backend = drv->backend; + + sun4i_backend_layer_enable(backend, layer->id, false); +} + +static void sun4i_backend_layer_atomic_update(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct sun4i_layer *layer = plane_to_sun4i_layer(plane); + struct sun4i_drv *drv = layer->drv; + struct sun4i_backend *backend = drv->backend; + + sun4i_backend_update_layer_coord(backend, layer->id, plane); + sun4i_backend_update_layer_formats(backend, layer->id, plane); + sun4i_backend_update_layer_buffer(backend, layer->id, plane); + sun4i_backend_layer_enable(backend, layer->id, true); +} + +static struct drm_plane_helper_funcs sun4i_backend_layer_helper_funcs = { + .atomic_check = sun4i_backend_layer_atomic_check, + .atomic_disable = sun4i_backend_layer_atomic_disable, + .atomic_update = sun4i_backend_layer_atomic_update, +}; + +static const struct drm_plane_funcs sun4i_backend_layer_funcs = { + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .destroy = drm_plane_cleanup, + .disable_plane = drm_atomic_helper_disable_plane, + .reset = drm_atomic_helper_plane_reset, + .update_plane = drm_atomic_helper_update_plane, +}; + +static const uint32_t sun4i_backend_layer_formats[] = { + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_RGB888, +}; + +static struct sun4i_layer *sun4i_layer_init_one(struct drm_device *drm, + enum drm_plane_type type) +{ + struct sun4i_drv *drv = drm->dev_private; + struct sun4i_layer *layer; + int ret; + + layer = devm_kzalloc(drm->dev, sizeof(*layer), GFP_KERNEL); + if (!layer) + return ERR_PTR(-ENOMEM); + + ret = drm_universal_plane_init(drm, &layer->plane, BIT(0), + &sun4i_backend_layer_funcs, + sun4i_backend_layer_formats, + ARRAY_SIZE(sun4i_backend_layer_formats), + type, + NULL); + if (ret) { + dev_err(drm->dev, "Couldn't initialize layer\n"); + return ERR_PTR(ret); + } + + drm_plane_helper_add(&layer->plane, + &sun4i_backend_layer_helper_funcs); + layer->drv = drv; + + if (type == DRM_PLANE_TYPE_PRIMARY) + drv->primary = &layer->plane; + + return layer; +} + +struct sun4i_layer **sun4i_layers_init(struct drm_device *drm) +{ + struct sun4i_drv *drv = drm->dev_private; + struct sun4i_layer **layers; + int i; + + layers = devm_kcalloc(drm->dev, SUN4I_NUM_LAYERS, sizeof(**layers), + GFP_KERNEL); + if (!layers) + return ERR_PTR(-ENOMEM); + + /* + * The hardware is a bit unusual here. + * + * Even though it supports 4 layers, it does the composition + * in two separate steps. + * + * The first one is assigning a layer to one of its two + * pipes. If more that 1 layer is assigned to the same pipe, + * and if pixels overlaps, the pipe will take the pixel from + * the layer with the highest priority. + * + * The second step is the actual alpha blending, that takes + * the two pipes as input, and uses the eventual alpha + * component to do the transparency between the two. + * + * This two steps scenario makes us unable to guarantee a + * robust alpha blending between the 4 layers in all + * situations. So we just expose two layers, one per pipe. On + * SoCs that support it, sprites could fill the need for more + * layers. + */ + for (i = 0; i < SUN4I_NUM_LAYERS; i++) { + enum drm_plane_type type = (i == 0) + ? DRM_PLANE_TYPE_PRIMARY + : DRM_PLANE_TYPE_OVERLAY; + struct sun4i_layer *layer = layers[i]; + + layer = sun4i_layer_init_one(drm, type); + if (IS_ERR(layer)) { + dev_err(drm->dev, "Couldn't initialize %s plane\n", + i ? "overlay" : "primary"); + return ERR_CAST(layer); + }; + + DRM_DEBUG_DRIVER("Assigning %s plane to pipe %d\n", + i ? "overlay" : "primary", i); + regmap_update_bits(drv->backend->regs, SUN4I_BACKEND_ATTCTL_REG0(i), + SUN4I_BACKEND_ATTCTL_REG0_LAY_PIPESEL_MASK, + SUN4I_BACKEND_ATTCTL_REG0_LAY_PIPESEL(i)); + + layer->id = i; + }; + + return layers; +} diff --git a/drivers/gpu/drm/sun4i/sun4i_layer.h b/drivers/gpu/drm/sun4i/sun4i_layer.h new file mode 100644 index 000000000000..a2f65d7a3f4e --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_layer.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#ifndef _SUN4I_LAYER_H_ +#define _SUN4I_LAYER_H_ + +struct sun4i_layer { + struct drm_plane plane; + struct sun4i_drv *drv; + int id; +}; + +static inline struct sun4i_layer * +plane_to_sun4i_layer(struct drm_plane *plane) +{ + return container_of(plane, struct sun4i_layer, plane); +} + +struct sun4i_layer **sun4i_layers_init(struct drm_device *drm); + +#endif /* _SUN4I_LAYER_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun4i_rgb.c b/drivers/gpu/drm/sun4i/sun4i_rgb.c new file mode 100644 index 000000000000..ab6494818050 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_rgb.c @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include <linux/clk.h> + +#include <drm/drmP.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_panel.h> + +#include "sun4i_drv.h" +#include "sun4i_tcon.h" + +struct sun4i_rgb { + struct drm_connector connector; + struct drm_encoder encoder; + + struct sun4i_drv *drv; +}; + +static inline struct sun4i_rgb * +drm_connector_to_sun4i_rgb(struct drm_connector *connector) +{ + return container_of(connector, struct sun4i_rgb, + connector); +} + +static inline struct sun4i_rgb * +drm_encoder_to_sun4i_rgb(struct drm_encoder *encoder) +{ + return container_of(encoder, struct sun4i_rgb, + encoder); +} + +static int sun4i_rgb_get_modes(struct drm_connector *connector) +{ + struct sun4i_rgb *rgb = + drm_connector_to_sun4i_rgb(connector); + struct sun4i_drv *drv = rgb->drv; + struct sun4i_tcon *tcon = drv->tcon; + + return drm_panel_get_modes(tcon->panel); +} + +static int sun4i_rgb_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + u32 hsync = mode->hsync_end - mode->hsync_start; + u32 vsync = mode->vsync_end - mode->vsync_start; + + DRM_DEBUG_DRIVER("Validating modes...\n"); + + if (hsync < 1) + return MODE_HSYNC_NARROW; + + if (hsync > 0x3ff) + return MODE_HSYNC_WIDE; + + if ((mode->hdisplay < 1) || (mode->htotal < 1)) + return MODE_H_ILLEGAL; + + if ((mode->hdisplay > 0x7ff) || (mode->htotal > 0xfff)) + return MODE_BAD_HVALUE; + + DRM_DEBUG_DRIVER("Horizontal parameters OK\n"); + + if (vsync < 1) + return MODE_VSYNC_NARROW; + + if (vsync > 0x3ff) + return MODE_VSYNC_WIDE; + + if ((mode->vdisplay < 1) || (mode->vtotal < 1)) + return MODE_V_ILLEGAL; + + if ((mode->vdisplay > 0x7ff) || (mode->vtotal > 0xfff)) + return MODE_BAD_VVALUE; + + DRM_DEBUG_DRIVER("Vertical parameters OK\n"); + + return MODE_OK; +} + +static struct drm_encoder * +sun4i_rgb_best_encoder(struct drm_connector *connector) +{ + struct sun4i_rgb *rgb = + drm_connector_to_sun4i_rgb(connector); + + return &rgb->encoder; +} + +static struct drm_connector_helper_funcs sun4i_rgb_con_helper_funcs = { + .get_modes = sun4i_rgb_get_modes, + .mode_valid = sun4i_rgb_mode_valid, + .best_encoder = sun4i_rgb_best_encoder, +}; + +static enum drm_connector_status +sun4i_rgb_connector_detect(struct drm_connector *connector, bool force) +{ + return connector_status_connected; +} + +static void +sun4i_rgb_connector_destroy(struct drm_connector *connector) +{ + struct sun4i_rgb *rgb = drm_connector_to_sun4i_rgb(connector); + struct sun4i_drv *drv = rgb->drv; + struct sun4i_tcon *tcon = drv->tcon; + + drm_panel_detach(tcon->panel); + drm_connector_cleanup(connector); +} + +static struct drm_connector_funcs sun4i_rgb_con_funcs = { + .dpms = drm_atomic_helper_connector_dpms, + .detect = sun4i_rgb_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = sun4i_rgb_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 int sun4i_rgb_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + return 0; +} + +static void sun4i_rgb_encoder_enable(struct drm_encoder *encoder) +{ + struct sun4i_rgb *rgb = drm_encoder_to_sun4i_rgb(encoder); + struct sun4i_drv *drv = rgb->drv; + struct sun4i_tcon *tcon = drv->tcon; + + DRM_DEBUG_DRIVER("Enabling RGB output\n"); + + drm_panel_enable(tcon->panel); + sun4i_tcon_channel_enable(tcon, 0); +} + +static void sun4i_rgb_encoder_disable(struct drm_encoder *encoder) +{ + struct sun4i_rgb *rgb = drm_encoder_to_sun4i_rgb(encoder); + struct sun4i_drv *drv = rgb->drv; + struct sun4i_tcon *tcon = drv->tcon; + + DRM_DEBUG_DRIVER("Disabling RGB output\n"); + + sun4i_tcon_channel_disable(tcon, 0); + drm_panel_disable(tcon->panel); +} + +static void sun4i_rgb_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct sun4i_rgb *rgb = drm_encoder_to_sun4i_rgb(encoder); + struct sun4i_drv *drv = rgb->drv; + struct sun4i_tcon *tcon = drv->tcon; + + sun4i_tcon0_mode_set(tcon, mode); + + clk_set_rate(tcon->dclk, mode->crtc_clock * 1000); + + /* FIXME: This seems to be board specific */ + clk_set_phase(tcon->dclk, 120); +} + +static struct drm_encoder_helper_funcs sun4i_rgb_enc_helper_funcs = { + .atomic_check = sun4i_rgb_atomic_check, + .mode_set = sun4i_rgb_encoder_mode_set, + .disable = sun4i_rgb_encoder_disable, + .enable = sun4i_rgb_encoder_enable, +}; + +static void sun4i_rgb_enc_destroy(struct drm_encoder *encoder) +{ + drm_encoder_cleanup(encoder); +} + +static struct drm_encoder_funcs sun4i_rgb_enc_funcs = { + .destroy = sun4i_rgb_enc_destroy, +}; + +int sun4i_rgb_init(struct drm_device *drm) +{ + struct sun4i_drv *drv = drm->dev_private; + struct sun4i_tcon *tcon = drv->tcon; + struct sun4i_rgb *rgb; + int ret; + + /* If we don't have a panel, there's no point in going on */ + if (!tcon->panel) + return -ENODEV; + + rgb = devm_kzalloc(drm->dev, sizeof(*rgb), GFP_KERNEL); + if (!rgb) + return -ENOMEM; + rgb->drv = drv; + + drm_encoder_helper_add(&rgb->encoder, + &sun4i_rgb_enc_helper_funcs); + ret = drm_encoder_init(drm, + &rgb->encoder, + &sun4i_rgb_enc_funcs, + DRM_MODE_ENCODER_NONE, + NULL); + if (ret) { + dev_err(drm->dev, "Couldn't initialise the rgb encoder\n"); + goto err_out; + } + + /* The RGB encoder can only work with the TCON channel 0 */ + rgb->encoder.possible_crtcs = BIT(0); + + drm_connector_helper_add(&rgb->connector, + &sun4i_rgb_con_helper_funcs); + ret = drm_connector_init(drm, &rgb->connector, + &sun4i_rgb_con_funcs, + DRM_MODE_CONNECTOR_Unknown); + if (ret) { + dev_err(drm->dev, "Couldn't initialise the rgb connector\n"); + goto err_cleanup_connector; + } + + drm_mode_connector_attach_encoder(&rgb->connector, &rgb->encoder); + + drm_panel_attach(tcon->panel, &rgb->connector); + + return 0; + +err_cleanup_connector: + drm_encoder_cleanup(&rgb->encoder); +err_out: + return ret; +} +EXPORT_SYMBOL(sun4i_rgb_init); diff --git a/drivers/gpu/drm/sun4i/sun4i_rgb.h b/drivers/gpu/drm/sun4i/sun4i_rgb.h new file mode 100644 index 000000000000..7c4da4c8acdd --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_rgb.h @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#ifndef _SUN4I_RGB_H_ +#define _SUN4I_RGB_H_ + +int sun4i_rgb_init(struct drm_device *drm); + +#endif /* _SUN4I_RGB_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.c b/drivers/gpu/drm/sun4i/sun4i_tcon.c new file mode 100644 index 000000000000..9f19b0e08560 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.c @@ -0,0 +1,561 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include <drm/drmP.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_modes.h> +#include <drm/drm_panel.h> + +#include <linux/component.h> +#include <linux/ioport.h> +#include <linux/of_address.h> +#include <linux/of_graph.h> +#include <linux/of_irq.h> +#include <linux/regmap.h> +#include <linux/reset.h> + +#include "sun4i_crtc.h" +#include "sun4i_dotclock.h" +#include "sun4i_drv.h" +#include "sun4i_rgb.h" +#include "sun4i_tcon.h" + +void sun4i_tcon_disable(struct sun4i_tcon *tcon) +{ + DRM_DEBUG_DRIVER("Disabling TCON\n"); + + /* Disable the TCON */ + regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, + SUN4I_TCON_GCTL_TCON_ENABLE, 0); +} +EXPORT_SYMBOL(sun4i_tcon_disable); + +void sun4i_tcon_enable(struct sun4i_tcon *tcon) +{ + DRM_DEBUG_DRIVER("Enabling TCON\n"); + + /* Enable the TCON */ + regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, + SUN4I_TCON_GCTL_TCON_ENABLE, + SUN4I_TCON_GCTL_TCON_ENABLE); +} +EXPORT_SYMBOL(sun4i_tcon_enable); + +void sun4i_tcon_channel_disable(struct sun4i_tcon *tcon, int channel) +{ + /* Disable the TCON's channel */ + if (channel == 0) { + regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, + SUN4I_TCON0_CTL_TCON_ENABLE, 0); + clk_disable_unprepare(tcon->dclk); + } else if (channel == 1) { + regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, + SUN4I_TCON1_CTL_TCON_ENABLE, 0); + clk_disable_unprepare(tcon->sclk1); + } +} +EXPORT_SYMBOL(sun4i_tcon_channel_disable); + +void sun4i_tcon_channel_enable(struct sun4i_tcon *tcon, int channel) +{ + /* Enable the TCON's channel */ + if (channel == 0) { + regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, + SUN4I_TCON0_CTL_TCON_ENABLE, + SUN4I_TCON0_CTL_TCON_ENABLE); + clk_prepare_enable(tcon->dclk); + } else if (channel == 1) { + regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, + SUN4I_TCON1_CTL_TCON_ENABLE, + SUN4I_TCON1_CTL_TCON_ENABLE); + clk_prepare_enable(tcon->sclk1); + } +} +EXPORT_SYMBOL(sun4i_tcon_channel_enable); + +void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable) +{ + u32 mask, val = 0; + + DRM_DEBUG_DRIVER("%sabling VBLANK interrupt\n", enable ? "En" : "Dis"); + + mask = SUN4I_TCON_GINT0_VBLANK_ENABLE(0) | + SUN4I_TCON_GINT0_VBLANK_ENABLE(1); + + if (enable) + val = mask; + + regmap_update_bits(tcon->regs, SUN4I_TCON_GINT0_REG, mask, val); +} +EXPORT_SYMBOL(sun4i_tcon_enable_vblank); + +static int sun4i_tcon_get_clk_delay(struct drm_display_mode *mode, + int channel) +{ + int delay = mode->vtotal - mode->vdisplay; + + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + delay /= 2; + + if (channel == 1) + delay -= 2; + + delay = min(delay, 30); + + DRM_DEBUG_DRIVER("TCON %d clock delay %u\n", channel, delay); + + return delay; +} + +void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon, + struct drm_display_mode *mode) +{ + unsigned int bp, hsync, vsync; + u8 clk_delay; + u32 val = 0; + + /* Adjust clock delay */ + clk_delay = sun4i_tcon_get_clk_delay(mode, 0); + regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG, + SUN4I_TCON0_CTL_CLK_DELAY_MASK, + SUN4I_TCON0_CTL_CLK_DELAY(clk_delay)); + + /* Set the resolution */ + regmap_write(tcon->regs, SUN4I_TCON0_BASIC0_REG, + SUN4I_TCON0_BASIC0_X(mode->crtc_hdisplay) | + SUN4I_TCON0_BASIC0_Y(mode->crtc_vdisplay)); + + /* + * This is called a backporch in the register documentation, + * but it really is the front porch + hsync + */ + bp = mode->crtc_htotal - mode->crtc_hsync_start; + DRM_DEBUG_DRIVER("Setting horizontal total %d, backporch %d\n", + mode->crtc_htotal, bp); + + /* Set horizontal display timings */ + regmap_write(tcon->regs, SUN4I_TCON0_BASIC1_REG, + SUN4I_TCON0_BASIC1_H_TOTAL(mode->crtc_htotal) | + SUN4I_TCON0_BASIC1_H_BACKPORCH(bp)); + + /* + * This is called a backporch in the register documentation, + * but it really is the front porch + hsync + */ + bp = mode->crtc_vtotal - mode->crtc_vsync_start; + DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n", + mode->crtc_vtotal, bp); + + /* Set vertical display timings */ + regmap_write(tcon->regs, SUN4I_TCON0_BASIC2_REG, + SUN4I_TCON0_BASIC2_V_TOTAL(mode->crtc_vtotal) | + SUN4I_TCON0_BASIC2_V_BACKPORCH(bp)); + + /* Set Hsync and Vsync length */ + hsync = mode->crtc_hsync_end - mode->crtc_hsync_start; + vsync = mode->crtc_vsync_end - mode->crtc_vsync_start; + DRM_DEBUG_DRIVER("Setting HSYNC %d, VSYNC %d\n", hsync, vsync); + regmap_write(tcon->regs, SUN4I_TCON0_BASIC3_REG, + SUN4I_TCON0_BASIC3_V_SYNC(vsync) | + SUN4I_TCON0_BASIC3_H_SYNC(hsync)); + + /* Setup the polarity of the various signals */ + if (!(mode->flags & DRM_MODE_FLAG_PHSYNC)) + val |= SUN4I_TCON0_IO_POL_HSYNC_POSITIVE; + + if (!(mode->flags & DRM_MODE_FLAG_PVSYNC)) + val |= SUN4I_TCON0_IO_POL_VSYNC_POSITIVE; + + regmap_update_bits(tcon->regs, SUN4I_TCON0_IO_POL_REG, + SUN4I_TCON0_IO_POL_HSYNC_POSITIVE | SUN4I_TCON0_IO_POL_VSYNC_POSITIVE, + val); + + /* Map output pins to channel 0 */ + regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, + SUN4I_TCON_GCTL_IOMAP_MASK, + SUN4I_TCON_GCTL_IOMAP_TCON0); + + /* Enable the output on the pins */ + regmap_write(tcon->regs, SUN4I_TCON0_IO_TRI_REG, 0); +} +EXPORT_SYMBOL(sun4i_tcon0_mode_set); + +void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon, + struct drm_display_mode *mode) +{ + unsigned int bp, hsync, vsync; + u8 clk_delay; + u32 val; + + /* Adjust clock delay */ + clk_delay = sun4i_tcon_get_clk_delay(mode, 1); + regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, + SUN4I_TCON1_CTL_CLK_DELAY_MASK, + SUN4I_TCON1_CTL_CLK_DELAY(clk_delay)); + + /* Set interlaced mode */ + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + val = SUN4I_TCON1_CTL_INTERLACE_ENABLE; + else + val = 0; + regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG, + SUN4I_TCON1_CTL_INTERLACE_ENABLE, + val); + + /* Set the input resolution */ + regmap_write(tcon->regs, SUN4I_TCON1_BASIC0_REG, + SUN4I_TCON1_BASIC0_X(mode->crtc_hdisplay) | + SUN4I_TCON1_BASIC0_Y(mode->crtc_vdisplay)); + + /* Set the upscaling resolution */ + regmap_write(tcon->regs, SUN4I_TCON1_BASIC1_REG, + SUN4I_TCON1_BASIC1_X(mode->crtc_hdisplay) | + SUN4I_TCON1_BASIC1_Y(mode->crtc_vdisplay)); + + /* Set the output resolution */ + regmap_write(tcon->regs, SUN4I_TCON1_BASIC2_REG, + SUN4I_TCON1_BASIC2_X(mode->crtc_hdisplay) | + SUN4I_TCON1_BASIC2_Y(mode->crtc_vdisplay)); + + /* Set horizontal display timings */ + bp = mode->crtc_htotal - mode->crtc_hsync_end; + DRM_DEBUG_DRIVER("Setting horizontal total %d, backporch %d\n", + mode->htotal, bp); + regmap_write(tcon->regs, SUN4I_TCON1_BASIC3_REG, + SUN4I_TCON1_BASIC3_H_TOTAL(mode->crtc_htotal) | + SUN4I_TCON1_BASIC3_H_BACKPORCH(bp)); + + /* Set vertical display timings */ + bp = mode->crtc_vtotal - mode->crtc_vsync_end; + DRM_DEBUG_DRIVER("Setting vertical total %d, backporch %d\n", + mode->vtotal, bp); + regmap_write(tcon->regs, SUN4I_TCON1_BASIC4_REG, + SUN4I_TCON1_BASIC4_V_TOTAL(mode->vtotal) | + SUN4I_TCON1_BASIC4_V_BACKPORCH(bp)); + + /* Set Hsync and Vsync length */ + hsync = mode->crtc_hsync_end - mode->crtc_hsync_start; + vsync = mode->crtc_vsync_end - mode->crtc_vsync_start; + DRM_DEBUG_DRIVER("Setting HSYNC %d, VSYNC %d\n", hsync, vsync); + regmap_write(tcon->regs, SUN4I_TCON1_BASIC5_REG, + SUN4I_TCON1_BASIC5_V_SYNC(vsync) | + SUN4I_TCON1_BASIC5_H_SYNC(hsync)); + + /* Map output pins to channel 1 */ + regmap_update_bits(tcon->regs, SUN4I_TCON_GCTL_REG, + SUN4I_TCON_GCTL_IOMAP_MASK, + SUN4I_TCON_GCTL_IOMAP_TCON1); + + /* + * FIXME: Undocumented bits + */ + if (tcon->has_mux) + regmap_write(tcon->regs, SUN4I_TCON_MUX_CTRL_REG, 1); +} +EXPORT_SYMBOL(sun4i_tcon1_mode_set); + +static void sun4i_tcon_finish_page_flip(struct drm_device *dev, + struct sun4i_crtc *scrtc) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->event_lock, flags); + if (scrtc->event) { + drm_crtc_send_vblank_event(&scrtc->crtc, scrtc->event); + drm_crtc_vblank_put(&scrtc->crtc); + scrtc->event = NULL; + } + spin_unlock_irqrestore(&dev->event_lock, flags); +} + +static irqreturn_t sun4i_tcon_handler(int irq, void *private) +{ + struct sun4i_tcon *tcon = private; + struct drm_device *drm = tcon->drm; + struct sun4i_drv *drv = drm->dev_private; + struct sun4i_crtc *scrtc = drv->crtc; + unsigned int status; + + regmap_read(tcon->regs, SUN4I_TCON_GINT0_REG, &status); + + if (!(status & (SUN4I_TCON_GINT0_VBLANK_INT(0) | + SUN4I_TCON_GINT0_VBLANK_INT(1)))) + return IRQ_NONE; + + drm_crtc_handle_vblank(&scrtc->crtc); + sun4i_tcon_finish_page_flip(drm, scrtc); + + /* Acknowledge the interrupt */ + regmap_update_bits(tcon->regs, SUN4I_TCON_GINT0_REG, + SUN4I_TCON_GINT0_VBLANK_INT(0) | + SUN4I_TCON_GINT0_VBLANK_INT(1), + 0); + + return IRQ_HANDLED; +} + +static int sun4i_tcon_init_clocks(struct device *dev, + struct sun4i_tcon *tcon) +{ + tcon->clk = devm_clk_get(dev, "ahb"); + if (IS_ERR(tcon->clk)) { + dev_err(dev, "Couldn't get the TCON bus clock\n"); + return PTR_ERR(tcon->clk); + } + clk_prepare_enable(tcon->clk); + + tcon->sclk0 = devm_clk_get(dev, "tcon-ch0"); + if (IS_ERR(tcon->sclk0)) { + dev_err(dev, "Couldn't get the TCON channel 0 clock\n"); + return PTR_ERR(tcon->sclk0); + } + + tcon->sclk1 = devm_clk_get(dev, "tcon-ch1"); + if (IS_ERR(tcon->sclk1)) { + dev_err(dev, "Couldn't get the TCON channel 1 clock\n"); + return PTR_ERR(tcon->sclk1); + } + + return sun4i_dclk_create(dev, tcon); +} + +static void sun4i_tcon_free_clocks(struct sun4i_tcon *tcon) +{ + sun4i_dclk_free(tcon); + clk_disable_unprepare(tcon->clk); +} + +static int sun4i_tcon_init_irq(struct device *dev, + struct sun4i_tcon *tcon) +{ + struct platform_device *pdev = to_platform_device(dev); + int irq, ret; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(dev, "Couldn't retrieve the TCON interrupt\n"); + return irq; + } + + ret = devm_request_irq(dev, irq, sun4i_tcon_handler, 0, + dev_name(dev), tcon); + if (ret) { + dev_err(dev, "Couldn't request the IRQ\n"); + return ret; + } + + return 0; +} + +static struct regmap_config sun4i_tcon_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x800, +}; + +static int sun4i_tcon_init_regmap(struct device *dev, + struct sun4i_tcon *tcon) +{ + struct platform_device *pdev = to_platform_device(dev); + struct resource *res; + void __iomem *regs; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(dev, res); + if (IS_ERR(regs)) { + dev_err(dev, "Couldn't map the TCON registers\n"); + return PTR_ERR(regs); + } + + tcon->regs = devm_regmap_init_mmio(dev, regs, + &sun4i_tcon_regmap_config); + if (IS_ERR(tcon->regs)) { + dev_err(dev, "Couldn't create the TCON regmap\n"); + return PTR_ERR(tcon->regs); + } + + /* Make sure the TCON is disabled and all IRQs are off */ + regmap_write(tcon->regs, SUN4I_TCON_GCTL_REG, 0); + regmap_write(tcon->regs, SUN4I_TCON_GINT0_REG, 0); + regmap_write(tcon->regs, SUN4I_TCON_GINT1_REG, 0); + + /* Disable IO lines and set them to tristate */ + regmap_write(tcon->regs, SUN4I_TCON0_IO_TRI_REG, ~0); + regmap_write(tcon->regs, SUN4I_TCON1_IO_TRI_REG, ~0); + + return 0; +} + +static struct drm_panel *sun4i_tcon_find_panel(struct device_node *node) +{ + struct device_node *port, *remote, *child; + struct device_node *end_node = NULL; + + /* Inputs are listed first, then outputs */ + port = of_graph_get_port_by_id(node, 1); + + /* + * Our first output is the RGB interface where the panel will + * be connected. + */ + for_each_child_of_node(port, child) { + u32 reg; + + of_property_read_u32(child, "reg", ®); + if (reg == 0) + end_node = child; + } + + if (!end_node) { + DRM_DEBUG_DRIVER("Missing panel endpoint\n"); + return ERR_PTR(-ENODEV); + } + + remote = of_graph_get_remote_port_parent(end_node); + if (!remote) { + DRM_DEBUG_DRIVER("Enable to parse remote node\n"); + return ERR_PTR(-EINVAL); + } + + return of_drm_find_panel(remote); +} + +static int sun4i_tcon_bind(struct device *dev, struct device *master, + void *data) +{ + struct drm_device *drm = data; + struct sun4i_drv *drv = drm->dev_private; + struct sun4i_tcon *tcon; + int ret; + + tcon = devm_kzalloc(dev, sizeof(*tcon), GFP_KERNEL); + if (!tcon) + return -ENOMEM; + dev_set_drvdata(dev, tcon); + drv->tcon = tcon; + tcon->drm = drm; + + if (of_device_is_compatible(dev->of_node, "allwinner,sun5i-a13-tcon")) + tcon->has_mux = true; + + tcon->lcd_rst = devm_reset_control_get(dev, "lcd"); + if (IS_ERR(tcon->lcd_rst)) { + dev_err(dev, "Couldn't get our reset line\n"); + return PTR_ERR(tcon->lcd_rst); + } + + /* Make sure our TCON is reset */ + if (!reset_control_status(tcon->lcd_rst)) + reset_control_assert(tcon->lcd_rst); + + ret = reset_control_deassert(tcon->lcd_rst); + if (ret) { + dev_err(dev, "Couldn't deassert our reset line\n"); + return ret; + } + + ret = sun4i_tcon_init_regmap(dev, tcon); + if (ret) { + dev_err(dev, "Couldn't init our TCON regmap\n"); + goto err_assert_reset; + } + + ret = sun4i_tcon_init_clocks(dev, tcon); + if (ret) { + dev_err(dev, "Couldn't init our TCON clocks\n"); + goto err_assert_reset; + } + + ret = sun4i_tcon_init_irq(dev, tcon); + if (ret) { + dev_err(dev, "Couldn't init our TCON interrupts\n"); + goto err_free_clocks; + } + + tcon->panel = sun4i_tcon_find_panel(dev->of_node); + if (IS_ERR(tcon->panel)) { + dev_info(dev, "No panel found... RGB output disabled\n"); + return 0; + } + + return sun4i_rgb_init(drm); + +err_free_clocks: + sun4i_tcon_free_clocks(tcon); +err_assert_reset: + reset_control_assert(tcon->lcd_rst); + return ret; +} + +static void sun4i_tcon_unbind(struct device *dev, struct device *master, + void *data) +{ + struct sun4i_tcon *tcon = dev_get_drvdata(dev); + + sun4i_tcon_free_clocks(tcon); +} + +static struct component_ops sun4i_tcon_ops = { + .bind = sun4i_tcon_bind, + .unbind = sun4i_tcon_unbind, +}; + +static int sun4i_tcon_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct drm_panel *panel; + + /* + * The panel is not ready. + * Defer the probe. + */ + panel = sun4i_tcon_find_panel(node); + if (IS_ERR(panel)) { + /* + * If we don't have a panel endpoint, just go on + */ + if (PTR_ERR(panel) != -ENODEV) + return -EPROBE_DEFER; + } + + return component_add(&pdev->dev, &sun4i_tcon_ops); +} + +static int sun4i_tcon_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &sun4i_tcon_ops); + + return 0; +} + +static const struct of_device_id sun4i_tcon_of_table[] = { + { .compatible = "allwinner,sun5i-a13-tcon" }, + { } +}; +MODULE_DEVICE_TABLE(of, sun4i_tcon_of_table); + +static struct platform_driver sun4i_tcon_platform_driver = { + .probe = sun4i_tcon_probe, + .remove = sun4i_tcon_remove, + .driver = { + .name = "sun4i-tcon", + .of_match_table = sun4i_tcon_of_table, + }, +}; +module_platform_driver(sun4i_tcon_platform_driver); + +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); +MODULE_DESCRIPTION("Allwinner A10 Timing Controller Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/sun4i/sun4i_tcon.h b/drivers/gpu/drm/sun4i/sun4i_tcon.h new file mode 100644 index 000000000000..0e0b11db401b --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_tcon.h @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * Boris Brezillon <boris.brezillon@free-electrons.com> + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#ifndef __SUN4I_TCON_H__ +#define __SUN4I_TCON_H__ + +#include <drm/drm_crtc.h> + +#include <linux/kernel.h> +#include <linux/reset.h> + +#define SUN4I_TCON_GCTL_REG 0x0 +#define SUN4I_TCON_GCTL_TCON_ENABLE BIT(31) +#define SUN4I_TCON_GCTL_IOMAP_MASK BIT(0) +#define SUN4I_TCON_GCTL_IOMAP_TCON1 (1 << 0) +#define SUN4I_TCON_GCTL_IOMAP_TCON0 (0 << 0) + +#define SUN4I_TCON_GINT0_REG 0x4 +#define SUN4I_TCON_GINT0_VBLANK_ENABLE(pipe) BIT(31 - (pipe)) +#define SUN4I_TCON_GINT0_VBLANK_INT(pipe) BIT(15 - (pipe)) + +#define SUN4I_TCON_GINT1_REG 0x8 +#define SUN4I_TCON_FRM_CTL_REG 0x10 + +#define SUN4I_TCON0_CTL_REG 0x40 +#define SUN4I_TCON0_CTL_TCON_ENABLE BIT(31) +#define SUN4I_TCON0_CTL_CLK_DELAY_MASK GENMASK(8, 4) +#define SUN4I_TCON0_CTL_CLK_DELAY(delay) ((delay << 4) & SUN4I_TCON0_CTL_CLK_DELAY_MASK) + +#define SUN4I_TCON0_DCLK_REG 0x44 +#define SUN4I_TCON0_DCLK_GATE_BIT (31) +#define SUN4I_TCON0_DCLK_DIV_SHIFT (0) +#define SUN4I_TCON0_DCLK_DIV_WIDTH (7) + +#define SUN4I_TCON0_BASIC0_REG 0x48 +#define SUN4I_TCON0_BASIC0_X(width) ((((width) - 1) & 0xfff) << 16) +#define SUN4I_TCON0_BASIC0_Y(height) (((height) - 1) & 0xfff) + +#define SUN4I_TCON0_BASIC1_REG 0x4c +#define SUN4I_TCON0_BASIC1_H_TOTAL(total) ((((total) - 1) & 0x1fff) << 16) +#define SUN4I_TCON0_BASIC1_H_BACKPORCH(bp) (((bp) - 1) & 0xfff) + +#define SUN4I_TCON0_BASIC2_REG 0x50 +#define SUN4I_TCON0_BASIC2_V_TOTAL(total) ((((total) * 2) & 0x1fff) << 16) +#define SUN4I_TCON0_BASIC2_V_BACKPORCH(bp) (((bp) - 1) & 0xfff) + +#define SUN4I_TCON0_BASIC3_REG 0x54 +#define SUN4I_TCON0_BASIC3_H_SYNC(width) ((((width) - 1) & 0x7ff) << 16) +#define SUN4I_TCON0_BASIC3_V_SYNC(height) (((height) - 1) & 0x7ff) + +#define SUN4I_TCON0_HV_IF_REG 0x58 +#define SUN4I_TCON0_CPU_IF_REG 0x60 +#define SUN4I_TCON0_CPU_WR_REG 0x64 +#define SUN4I_TCON0_CPU_RD0_REG 0x68 +#define SUN4I_TCON0_CPU_RDA_REG 0x6c +#define SUN4I_TCON0_TTL0_REG 0x70 +#define SUN4I_TCON0_TTL1_REG 0x74 +#define SUN4I_TCON0_TTL2_REG 0x78 +#define SUN4I_TCON0_TTL3_REG 0x7c +#define SUN4I_TCON0_TTL4_REG 0x80 +#define SUN4I_TCON0_LVDS_IF_REG 0x84 +#define SUN4I_TCON0_IO_POL_REG 0x88 +#define SUN4I_TCON0_IO_POL_DCLK_PHASE(phase) ((phase & 3) << 28) +#define SUN4I_TCON0_IO_POL_HSYNC_POSITIVE BIT(25) +#define SUN4I_TCON0_IO_POL_VSYNC_POSITIVE BIT(24) + +#define SUN4I_TCON0_IO_TRI_REG 0x8c +#define SUN4I_TCON0_IO_TRI_HSYNC_DISABLE BIT(25) +#define SUN4I_TCON0_IO_TRI_VSYNC_DISABLE BIT(24) +#define SUN4I_TCON0_IO_TRI_DATA_PINS_DISABLE(pins) GENMASK(pins, 0) + +#define SUN4I_TCON1_CTL_REG 0x90 +#define SUN4I_TCON1_CTL_TCON_ENABLE BIT(31) +#define SUN4I_TCON1_CTL_INTERLACE_ENABLE BIT(20) +#define SUN4I_TCON1_CTL_CLK_DELAY_MASK GENMASK(8, 4) +#define SUN4I_TCON1_CTL_CLK_DELAY(delay) ((delay << 4) & SUN4I_TCON1_CTL_CLK_DELAY_MASK) + +#define SUN4I_TCON1_BASIC0_REG 0x94 +#define SUN4I_TCON1_BASIC0_X(width) ((((width) - 1) & 0xfff) << 16) +#define SUN4I_TCON1_BASIC0_Y(height) (((height) - 1) & 0xfff) + +#define SUN4I_TCON1_BASIC1_REG 0x98 +#define SUN4I_TCON1_BASIC1_X(width) ((((width) - 1) & 0xfff) << 16) +#define SUN4I_TCON1_BASIC1_Y(height) (((height) - 1) & 0xfff) + +#define SUN4I_TCON1_BASIC2_REG 0x9c +#define SUN4I_TCON1_BASIC2_X(width) ((((width) - 1) & 0xfff) << 16) +#define SUN4I_TCON1_BASIC2_Y(height) (((height) - 1) & 0xfff) + +#define SUN4I_TCON1_BASIC3_REG 0xa0 +#define SUN4I_TCON1_BASIC3_H_TOTAL(total) ((((total) - 1) & 0x1fff) << 16) +#define SUN4I_TCON1_BASIC3_H_BACKPORCH(bp) (((bp) - 1) & 0xfff) + +#define SUN4I_TCON1_BASIC4_REG 0xa4 +#define SUN4I_TCON1_BASIC4_V_TOTAL(total) (((total) & 0x1fff) << 16) +#define SUN4I_TCON1_BASIC4_V_BACKPORCH(bp) (((bp) - 1) & 0xfff) + +#define SUN4I_TCON1_BASIC5_REG 0xa8 +#define SUN4I_TCON1_BASIC5_H_SYNC(width) ((((width) - 1) & 0x3ff) << 16) +#define SUN4I_TCON1_BASIC5_V_SYNC(height) (((height) - 1) & 0x3ff) + +#define SUN4I_TCON1_IO_POL_REG 0xf0 +#define SUN4I_TCON1_IO_TRI_REG 0xf4 +#define SUN4I_TCON_CEU_CTL_REG 0x100 +#define SUN4I_TCON_CEU_MUL_RR_REG 0x110 +#define SUN4I_TCON_CEU_MUL_RG_REG 0x114 +#define SUN4I_TCON_CEU_MUL_RB_REG 0x118 +#define SUN4I_TCON_CEU_ADD_RC_REG 0x11c +#define SUN4I_TCON_CEU_MUL_GR_REG 0x120 +#define SUN4I_TCON_CEU_MUL_GG_REG 0x124 +#define SUN4I_TCON_CEU_MUL_GB_REG 0x128 +#define SUN4I_TCON_CEU_ADD_GC_REG 0x12c +#define SUN4I_TCON_CEU_MUL_BR_REG 0x130 +#define SUN4I_TCON_CEU_MUL_BG_REG 0x134 +#define SUN4I_TCON_CEU_MUL_BB_REG 0x138 +#define SUN4I_TCON_CEU_ADD_BC_REG 0x13c +#define SUN4I_TCON_CEU_RANGE_R_REG 0x140 +#define SUN4I_TCON_CEU_RANGE_G_REG 0x144 +#define SUN4I_TCON_CEU_RANGE_B_REG 0x148 +#define SUN4I_TCON_MUX_CTRL_REG 0x200 +#define SUN4I_TCON1_FILL_CTL_REG 0x300 +#define SUN4I_TCON1_FILL_BEG0_REG 0x304 +#define SUN4I_TCON1_FILL_END0_REG 0x308 +#define SUN4I_TCON1_FILL_DATA0_REG 0x30c +#define SUN4I_TCON1_FILL_BEG1_REG 0x310 +#define SUN4I_TCON1_FILL_END1_REG 0x314 +#define SUN4I_TCON1_FILL_DATA1_REG 0x318 +#define SUN4I_TCON1_FILL_BEG2_REG 0x31c +#define SUN4I_TCON1_FILL_END2_REG 0x320 +#define SUN4I_TCON1_FILL_DATA2_REG 0x324 +#define SUN4I_TCON1_GAMMA_TABLE_REG 0x400 + +#define SUN4I_TCON_MAX_CHANNELS 2 + +struct sun4i_tcon { + struct drm_device *drm; + struct regmap *regs; + + /* Main bus clock */ + struct clk *clk; + + /* Clocks for the TCON channels */ + struct clk *sclk0; + struct clk *sclk1; + + /* Pixel clock */ + struct clk *dclk; + + /* Reset control */ + struct reset_control *lcd_rst; + + /* Platform adjustments */ + bool has_mux; + + struct drm_panel *panel; +}; + +/* Global Control */ +void sun4i_tcon_disable(struct sun4i_tcon *tcon); +void sun4i_tcon_enable(struct sun4i_tcon *tcon); + +/* Channel Control */ +void sun4i_tcon_channel_disable(struct sun4i_tcon *tcon, int channel); +void sun4i_tcon_channel_enable(struct sun4i_tcon *tcon, int channel); + +void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable); + +/* Mode Related Controls */ +void sun4i_tcon_switch_interlace(struct sun4i_tcon *tcon, + bool enable); +void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon, + struct drm_display_mode *mode); +void sun4i_tcon1_mode_set(struct sun4i_tcon *tcon, + struct drm_display_mode *mode); + +#endif /* __SUN4I_TCON_H__ */ diff --git a/drivers/gpu/drm/sun4i/sun4i_tv.c b/drivers/gpu/drm/sun4i/sun4i_tv.c new file mode 100644 index 000000000000..bc047f923508 --- /dev/null +++ b/drivers/gpu/drm/sun4i/sun4i_tv.c @@ -0,0 +1,708 @@ +/* + * Copyright (C) 2015 Free Electrons + * Copyright (C) 2015 NextThing Co + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + */ + +#include <linux/clk.h> +#include <linux/component.h> +#include <linux/of_address.h> +#include <linux/regmap.h> +#include <linux/reset.h> + +#include <drm/drmP.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_panel.h> + +#include "sun4i_backend.h" +#include "sun4i_drv.h" +#include "sun4i_tcon.h" + +#define SUN4I_TVE_EN_REG 0x000 +#define SUN4I_TVE_EN_DAC_MAP_MASK GENMASK(19, 4) +#define SUN4I_TVE_EN_DAC_MAP(dac, out) (((out) & 0xf) << (dac + 1) * 4) +#define SUN4I_TVE_EN_ENABLE BIT(0) + +#define SUN4I_TVE_CFG0_REG 0x004 +#define SUN4I_TVE_CFG0_DAC_CONTROL_54M BIT(26) +#define SUN4I_TVE_CFG0_CORE_DATAPATH_54M BIT(25) +#define SUN4I_TVE_CFG0_CORE_CONTROL_54M BIT(24) +#define SUN4I_TVE_CFG0_YC_EN BIT(17) +#define SUN4I_TVE_CFG0_COMP_EN BIT(16) +#define SUN4I_TVE_CFG0_RES(x) ((x) & 0xf) +#define SUN4I_TVE_CFG0_RES_480i SUN4I_TVE_CFG0_RES(0) +#define SUN4I_TVE_CFG0_RES_576i SUN4I_TVE_CFG0_RES(1) + +#define SUN4I_TVE_DAC0_REG 0x008 +#define SUN4I_TVE_DAC0_CLOCK_INVERT BIT(24) +#define SUN4I_TVE_DAC0_LUMA(x) (((x) & 3) << 20) +#define SUN4I_TVE_DAC0_LUMA_0_4 SUN4I_TVE_DAC0_LUMA(3) +#define SUN4I_TVE_DAC0_CHROMA(x) (((x) & 3) << 18) +#define SUN4I_TVE_DAC0_CHROMA_0_75 SUN4I_TVE_DAC0_CHROMA(3) +#define SUN4I_TVE_DAC0_INTERNAL_DAC(x) (((x) & 3) << 16) +#define SUN4I_TVE_DAC0_INTERNAL_DAC_37_5_OHMS SUN4I_TVE_DAC0_INTERNAL_DAC(3) +#define SUN4I_TVE_DAC0_DAC_EN(dac) BIT(dac) + +#define SUN4I_TVE_NOTCH_REG 0x00c +#define SUN4I_TVE_NOTCH_DAC0_TO_DAC_DLY(dac, x) ((4 - (x)) << (dac * 3)) + +#define SUN4I_TVE_CHROMA_FREQ_REG 0x010 + +#define SUN4I_TVE_PORCH_REG 0x014 +#define SUN4I_TVE_PORCH_BACK(x) ((x) << 16) +#define SUN4I_TVE_PORCH_FRONT(x) (x) + +#define SUN4I_TVE_LINE_REG 0x01c +#define SUN4I_TVE_LINE_FIRST(x) ((x) << 16) +#define SUN4I_TVE_LINE_NUMBER(x) (x) + +#define SUN4I_TVE_LEVEL_REG 0x020 +#define SUN4I_TVE_LEVEL_BLANK(x) ((x) << 16) +#define SUN4I_TVE_LEVEL_BLACK(x) (x) + +#define SUN4I_TVE_DAC1_REG 0x024 +#define SUN4I_TVE_DAC1_AMPLITUDE(dac, x) ((x) << (dac * 8)) + +#define SUN4I_TVE_DETECT_STA_REG 0x038 +#define SUN4I_TVE_DETECT_STA_DAC(dac) BIT((dac * 8)) +#define SUN4I_TVE_DETECT_STA_UNCONNECTED 0 +#define SUN4I_TVE_DETECT_STA_CONNECTED 1 +#define SUN4I_TVE_DETECT_STA_GROUND 2 + +#define SUN4I_TVE_CB_CR_LVL_REG 0x10c +#define SUN4I_TVE_CB_CR_LVL_CR_BURST(x) ((x) << 8) +#define SUN4I_TVE_CB_CR_LVL_CB_BURST(x) (x) + +#define SUN4I_TVE_TINT_BURST_PHASE_REG 0x110 +#define SUN4I_TVE_TINT_BURST_PHASE_CHROMA(x) (x) + +#define SUN4I_TVE_BURST_WIDTH_REG 0x114 +#define SUN4I_TVE_BURST_WIDTH_BREEZEWAY(x) ((x) << 16) +#define SUN4I_TVE_BURST_WIDTH_BURST_WIDTH(x) ((x) << 8) +#define SUN4I_TVE_BURST_WIDTH_HSYNC_WIDTH(x) (x) + +#define SUN4I_TVE_CB_CR_GAIN_REG 0x118 +#define SUN4I_TVE_CB_CR_GAIN_CR(x) ((x) << 8) +#define SUN4I_TVE_CB_CR_GAIN_CB(x) (x) + +#define SUN4I_TVE_SYNC_VBI_REG 0x11c +#define SUN4I_TVE_SYNC_VBI_SYNC(x) ((x) << 16) +#define SUN4I_TVE_SYNC_VBI_VBLANK(x) (x) + +#define SUN4I_TVE_ACTIVE_LINE_REG 0x124 +#define SUN4I_TVE_ACTIVE_LINE(x) (x) + +#define SUN4I_TVE_CHROMA_REG 0x128 +#define SUN4I_TVE_CHROMA_COMP_GAIN(x) ((x) & 3) +#define SUN4I_TVE_CHROMA_COMP_GAIN_50 SUN4I_TVE_CHROMA_COMP_GAIN(2) + +#define SUN4I_TVE_12C_REG 0x12c +#define SUN4I_TVE_12C_NOTCH_WIDTH_WIDE BIT(8) +#define SUN4I_TVE_12C_COMP_YUV_EN BIT(0) + +#define SUN4I_TVE_RESYNC_REG 0x130 +#define SUN4I_TVE_RESYNC_FIELD BIT(31) +#define SUN4I_TVE_RESYNC_LINE(x) ((x) << 16) +#define SUN4I_TVE_RESYNC_PIXEL(x) (x) + +#define SUN4I_TVE_SLAVE_REG 0x134 + +#define SUN4I_TVE_WSS_DATA2_REG 0x244 + +struct color_gains { + u16 cb; + u16 cr; +}; + +struct burst_levels { + u16 cb; + u16 cr; +}; + +struct video_levels { + u16 black; + u16 blank; +}; + +struct resync_parameters { + bool field; + u16 line; + u16 pixel; +}; + +struct tv_mode { + char *name; + + u32 mode; + u32 chroma_freq; + u16 back_porch; + u16 front_porch; + u16 line_number; + u16 vblank_level; + + u32 hdisplay; + u16 hfront_porch; + u16 hsync_len; + u16 hback_porch; + + u32 vdisplay; + u16 vfront_porch; + u16 vsync_len; + u16 vback_porch; + + bool yc_en; + bool dac3_en; + bool dac_bit25_en; + + struct color_gains *color_gains; + struct burst_levels *burst_levels; + struct video_levels *video_levels; + struct resync_parameters *resync_params; +}; + +struct sun4i_tv { + struct drm_connector connector; + struct drm_encoder encoder; + + struct clk *clk; + struct regmap *regs; + struct reset_control *reset; + + struct sun4i_drv *drv; +}; + +struct video_levels ntsc_video_levels = { + .black = 282, .blank = 240, +}; + +struct video_levels pal_video_levels = { + .black = 252, .blank = 252, +}; + +struct burst_levels ntsc_burst_levels = { + .cb = 79, .cr = 0, +}; + +struct burst_levels pal_burst_levels = { + .cb = 40, .cr = 40, +}; + +struct color_gains ntsc_color_gains = { + .cb = 160, .cr = 160, +}; + +struct color_gains pal_color_gains = { + .cb = 224, .cr = 224, +}; + +struct resync_parameters ntsc_resync_parameters = { + .field = false, .line = 14, .pixel = 12, +}; + +struct resync_parameters pal_resync_parameters = { + .field = true, .line = 13, .pixel = 12, +}; + +struct tv_mode tv_modes[] = { + { + .name = "NTSC", + .mode = SUN4I_TVE_CFG0_RES_480i, + .chroma_freq = 0x21f07c1f, + .yc_en = true, + .dac3_en = true, + .dac_bit25_en = true, + + .back_porch = 118, + .front_porch = 32, + .line_number = 525, + + .hdisplay = 720, + .hfront_porch = 18, + .hsync_len = 2, + .hback_porch = 118, + + .vdisplay = 480, + .vfront_porch = 26, + .vsync_len = 2, + .vback_porch = 17, + + .vblank_level = 240, + + .color_gains = &ntsc_color_gains, + .burst_levels = &ntsc_burst_levels, + .video_levels = &ntsc_video_levels, + .resync_params = &ntsc_resync_parameters, + }, + { + .name = "PAL", + .mode = SUN4I_TVE_CFG0_RES_576i, + .chroma_freq = 0x2a098acb, + + .back_porch = 138, + .front_porch = 24, + .line_number = 625, + + .hdisplay = 720, + .hfront_porch = 3, + .hsync_len = 2, + .hback_porch = 139, + + .vdisplay = 576, + .vfront_porch = 28, + .vsync_len = 2, + .vback_porch = 19, + + .vblank_level = 252, + + .color_gains = &pal_color_gains, + .burst_levels = &pal_burst_levels, + .video_levels = &pal_video_levels, + .resync_params = &pal_resync_parameters, + }, +}; + +static inline struct sun4i_tv * +drm_encoder_to_sun4i_tv(struct drm_encoder *encoder) +{ + return container_of(encoder, struct sun4i_tv, + encoder); +} + +static inline struct sun4i_tv * +drm_connector_to_sun4i_tv(struct drm_connector *connector) +{ + return container_of(connector, struct sun4i_tv, + connector); +} + +/* + * FIXME: If only the drm_display_mode private field was usable, this + * could go away... + * + * So far, it doesn't seem to be preserved when the mode is passed by + * to mode_set for some reason. + */ +static struct tv_mode *sun4i_tv_find_tv_by_mode(struct drm_display_mode *mode) +{ + int i; + + /* First try to identify the mode by name */ + for (i = 0; i < ARRAY_SIZE(tv_modes); i++) { + struct tv_mode *tv_mode = &tv_modes[i]; + + DRM_DEBUG_DRIVER("Comparing mode %s vs %s", + mode->name, tv_mode->name); + + if (!strcmp(mode->name, tv_mode->name)) + return tv_mode; + } + + /* Then by number of lines */ + for (i = 0; i < ARRAY_SIZE(tv_modes); i++) { + struct tv_mode *tv_mode = &tv_modes[i]; + + DRM_DEBUG_DRIVER("Comparing mode %s vs %s (X: %d vs %d)", + mode->name, tv_mode->name, + mode->vdisplay, tv_mode->vdisplay); + + if (mode->vdisplay == tv_mode->vdisplay) + return tv_mode; + } + + return NULL; +} + +static void sun4i_tv_mode_to_drm_mode(struct tv_mode *tv_mode, + struct drm_display_mode *mode) +{ + DRM_DEBUG_DRIVER("Creating mode %s\n", mode->name); + + mode->type = DRM_MODE_TYPE_DRIVER; + mode->clock = 13500; + mode->flags = DRM_MODE_FLAG_INTERLACE; + + mode->hdisplay = tv_mode->hdisplay; + mode->hsync_start = mode->hdisplay + tv_mode->hfront_porch; + mode->hsync_end = mode->hsync_start + tv_mode->hsync_len; + mode->htotal = mode->hsync_end + tv_mode->hback_porch; + + mode->vdisplay = tv_mode->vdisplay; + mode->vsync_start = mode->vdisplay + tv_mode->vfront_porch; + mode->vsync_end = mode->vsync_start + tv_mode->vsync_len; + mode->vtotal = mode->vsync_end + tv_mode->vback_porch; +} + +static int sun4i_tv_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + return 0; +} + +static void sun4i_tv_disable(struct drm_encoder *encoder) +{ + struct sun4i_tv *tv = drm_encoder_to_sun4i_tv(encoder); + struct sun4i_drv *drv = tv->drv; + struct sun4i_tcon *tcon = drv->tcon; + + DRM_DEBUG_DRIVER("Disabling the TV Output\n"); + + sun4i_tcon_channel_disable(tcon, 1); + + regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG, + SUN4I_TVE_EN_ENABLE, + 0); + sun4i_backend_disable_color_correction(drv->backend); +} + +static void sun4i_tv_enable(struct drm_encoder *encoder) +{ + struct sun4i_tv *tv = drm_encoder_to_sun4i_tv(encoder); + struct sun4i_drv *drv = tv->drv; + struct sun4i_tcon *tcon = drv->tcon; + + DRM_DEBUG_DRIVER("Enabling the TV Output\n"); + + sun4i_backend_apply_color_correction(drv->backend); + + regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG, + SUN4I_TVE_EN_ENABLE, + SUN4I_TVE_EN_ENABLE); + + sun4i_tcon_channel_enable(tcon, 1); +} + +static void sun4i_tv_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct sun4i_tv *tv = drm_encoder_to_sun4i_tv(encoder); + struct sun4i_drv *drv = tv->drv; + struct sun4i_tcon *tcon = drv->tcon; + struct tv_mode *tv_mode = sun4i_tv_find_tv_by_mode(mode); + + sun4i_tcon1_mode_set(tcon, mode); + + /* Enable and map the DAC to the output */ + regmap_update_bits(tv->regs, SUN4I_TVE_EN_REG, + SUN4I_TVE_EN_DAC_MAP_MASK, + SUN4I_TVE_EN_DAC_MAP(0, 1) | + SUN4I_TVE_EN_DAC_MAP(1, 2) | + SUN4I_TVE_EN_DAC_MAP(2, 3) | + SUN4I_TVE_EN_DAC_MAP(3, 4)); + + /* Set PAL settings */ + regmap_write(tv->regs, SUN4I_TVE_CFG0_REG, + tv_mode->mode | + (tv_mode->yc_en ? SUN4I_TVE_CFG0_YC_EN : 0) | + SUN4I_TVE_CFG0_COMP_EN | + SUN4I_TVE_CFG0_DAC_CONTROL_54M | + SUN4I_TVE_CFG0_CORE_DATAPATH_54M | + SUN4I_TVE_CFG0_CORE_CONTROL_54M); + + /* Configure the DAC for a composite output */ + regmap_write(tv->regs, SUN4I_TVE_DAC0_REG, + SUN4I_TVE_DAC0_DAC_EN(0) | + (tv_mode->dac3_en ? SUN4I_TVE_DAC0_DAC_EN(3) : 0) | + SUN4I_TVE_DAC0_INTERNAL_DAC_37_5_OHMS | + SUN4I_TVE_DAC0_CHROMA_0_75 | + SUN4I_TVE_DAC0_LUMA_0_4 | + SUN4I_TVE_DAC0_CLOCK_INVERT | + (tv_mode->dac_bit25_en ? BIT(25) : 0) | + BIT(30)); + + /* Configure the sample delay between DAC0 and the other DAC */ + regmap_write(tv->regs, SUN4I_TVE_NOTCH_REG, + SUN4I_TVE_NOTCH_DAC0_TO_DAC_DLY(1, 0) | + SUN4I_TVE_NOTCH_DAC0_TO_DAC_DLY(2, 0)); + + regmap_write(tv->regs, SUN4I_TVE_CHROMA_FREQ_REG, + tv_mode->chroma_freq); + + /* Set the front and back porch */ + regmap_write(tv->regs, SUN4I_TVE_PORCH_REG, + SUN4I_TVE_PORCH_BACK(tv_mode->back_porch) | + SUN4I_TVE_PORCH_FRONT(tv_mode->front_porch)); + + /* Set the lines setup */ + regmap_write(tv->regs, SUN4I_TVE_LINE_REG, + SUN4I_TVE_LINE_FIRST(22) | + SUN4I_TVE_LINE_NUMBER(tv_mode->line_number)); + + regmap_write(tv->regs, SUN4I_TVE_LEVEL_REG, + SUN4I_TVE_LEVEL_BLANK(tv_mode->video_levels->blank) | + SUN4I_TVE_LEVEL_BLACK(tv_mode->video_levels->black)); + + regmap_write(tv->regs, SUN4I_TVE_DAC1_REG, + SUN4I_TVE_DAC1_AMPLITUDE(0, 0x18) | + SUN4I_TVE_DAC1_AMPLITUDE(1, 0x18) | + SUN4I_TVE_DAC1_AMPLITUDE(2, 0x18) | + SUN4I_TVE_DAC1_AMPLITUDE(3, 0x18)); + + regmap_write(tv->regs, SUN4I_TVE_CB_CR_LVL_REG, + SUN4I_TVE_CB_CR_LVL_CB_BURST(tv_mode->burst_levels->cb) | + SUN4I_TVE_CB_CR_LVL_CR_BURST(tv_mode->burst_levels->cr)); + + /* Set burst width for a composite output */ + regmap_write(tv->regs, SUN4I_TVE_BURST_WIDTH_REG, + SUN4I_TVE_BURST_WIDTH_HSYNC_WIDTH(126) | + SUN4I_TVE_BURST_WIDTH_BURST_WIDTH(68) | + SUN4I_TVE_BURST_WIDTH_BREEZEWAY(22)); + + regmap_write(tv->regs, SUN4I_TVE_CB_CR_GAIN_REG, + SUN4I_TVE_CB_CR_GAIN_CB(tv_mode->color_gains->cb) | + SUN4I_TVE_CB_CR_GAIN_CR(tv_mode->color_gains->cr)); + + regmap_write(tv->regs, SUN4I_TVE_SYNC_VBI_REG, + SUN4I_TVE_SYNC_VBI_SYNC(0x10) | + SUN4I_TVE_SYNC_VBI_VBLANK(tv_mode->vblank_level)); + + regmap_write(tv->regs, SUN4I_TVE_ACTIVE_LINE_REG, + SUN4I_TVE_ACTIVE_LINE(1440)); + + /* Set composite chroma gain to 50 % */ + regmap_write(tv->regs, SUN4I_TVE_CHROMA_REG, + SUN4I_TVE_CHROMA_COMP_GAIN_50); + + regmap_write(tv->regs, SUN4I_TVE_12C_REG, + SUN4I_TVE_12C_COMP_YUV_EN | + SUN4I_TVE_12C_NOTCH_WIDTH_WIDE); + + regmap_write(tv->regs, SUN4I_TVE_RESYNC_REG, + SUN4I_TVE_RESYNC_PIXEL(tv_mode->resync_params->pixel) | + SUN4I_TVE_RESYNC_LINE(tv_mode->resync_params->line) | + (tv_mode->resync_params->field ? + SUN4I_TVE_RESYNC_FIELD : 0)); + + regmap_write(tv->regs, SUN4I_TVE_SLAVE_REG, 0); + + clk_set_rate(tcon->sclk1, mode->crtc_clock * 1000); +} + +static struct drm_encoder_helper_funcs sun4i_tv_helper_funcs = { + .atomic_check = sun4i_tv_atomic_check, + .disable = sun4i_tv_disable, + .enable = sun4i_tv_enable, + .mode_set = sun4i_tv_mode_set, +}; + +static void sun4i_tv_destroy(struct drm_encoder *encoder) +{ + drm_encoder_cleanup(encoder); +} + +static struct drm_encoder_funcs sun4i_tv_funcs = { + .destroy = sun4i_tv_destroy, +}; + +static int sun4i_tv_comp_get_modes(struct drm_connector *connector) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(tv_modes); i++) { + struct drm_display_mode *mode = drm_mode_create(connector->dev); + struct tv_mode *tv_mode = &tv_modes[i]; + + strcpy(mode->name, tv_mode->name); + + sun4i_tv_mode_to_drm_mode(tv_mode, mode); + drm_mode_probed_add(connector, mode); + } + + return i; +} + +static int sun4i_tv_comp_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + /* TODO */ + return MODE_OK; +} + +static struct drm_encoder * +sun4i_tv_comp_best_encoder(struct drm_connector *connector) +{ + struct sun4i_tv *tv = drm_connector_to_sun4i_tv(connector); + + return &tv->encoder; +} + +static struct drm_connector_helper_funcs sun4i_tv_comp_connector_helper_funcs = { + .get_modes = sun4i_tv_comp_get_modes, + .mode_valid = sun4i_tv_comp_mode_valid, + .best_encoder = sun4i_tv_comp_best_encoder, +}; + +static enum drm_connector_status +sun4i_tv_comp_connector_detect(struct drm_connector *connector, bool force) +{ + return connector_status_connected; +} + +static void +sun4i_tv_comp_connector_destroy(struct drm_connector *connector) +{ + drm_connector_cleanup(connector); +} + +static struct drm_connector_funcs sun4i_tv_comp_connector_funcs = { + .dpms = drm_atomic_helper_connector_dpms, + .detect = sun4i_tv_comp_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = sun4i_tv_comp_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 regmap_config sun4i_tv_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = SUN4I_TVE_WSS_DATA2_REG, + .name = "tv-encoder", +}; + +static int sun4i_tv_bind(struct device *dev, struct device *master, + void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct drm_device *drm = data; + struct sun4i_drv *drv = drm->dev_private; + struct sun4i_tv *tv; + struct resource *res; + void __iomem *regs; + int ret; + + tv = devm_kzalloc(dev, sizeof(*tv), GFP_KERNEL); + if (!tv) + return -ENOMEM; + tv->drv = drv; + dev_set_drvdata(dev, tv); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(dev, res); + if (IS_ERR(regs)) { + dev_err(dev, "Couldn't map the TV encoder registers\n"); + return PTR_ERR(regs); + } + + tv->regs = devm_regmap_init_mmio(dev, regs, + &sun4i_tv_regmap_config); + if (IS_ERR(tv->regs)) { + dev_err(dev, "Couldn't create the TV encoder regmap\n"); + return PTR_ERR(tv->regs); + } + + tv->reset = devm_reset_control_get(dev, NULL); + if (IS_ERR(tv->reset)) { + dev_err(dev, "Couldn't get our reset line\n"); + return PTR_ERR(tv->reset); + } + + ret = reset_control_deassert(tv->reset); + if (ret) { + dev_err(dev, "Couldn't deassert our reset line\n"); + return ret; + } + + tv->clk = devm_clk_get(dev, NULL); + if (IS_ERR(tv->clk)) { + dev_err(dev, "Couldn't get the TV encoder clock\n"); + ret = PTR_ERR(tv->clk); + goto err_assert_reset; + } + clk_prepare_enable(tv->clk); + + drm_encoder_helper_add(&tv->encoder, + &sun4i_tv_helper_funcs); + ret = drm_encoder_init(drm, + &tv->encoder, + &sun4i_tv_funcs, + DRM_MODE_ENCODER_TVDAC, + NULL); + if (ret) { + dev_err(dev, "Couldn't initialise the TV encoder\n"); + goto err_disable_clk; + } + + tv->encoder.possible_crtcs = BIT(0); + + drm_connector_helper_add(&tv->connector, + &sun4i_tv_comp_connector_helper_funcs); + ret = drm_connector_init(drm, &tv->connector, + &sun4i_tv_comp_connector_funcs, + DRM_MODE_CONNECTOR_Composite); + if (ret) { + dev_err(dev, + "Couldn't initialise the Composite connector\n"); + goto err_cleanup_connector; + } + tv->connector.interlace_allowed = true; + + drm_mode_connector_attach_encoder(&tv->connector, &tv->encoder); + + return 0; + +err_cleanup_connector: + drm_encoder_cleanup(&tv->encoder); +err_disable_clk: + clk_disable_unprepare(tv->clk); +err_assert_reset: + reset_control_assert(tv->reset); + return ret; +} + +static void sun4i_tv_unbind(struct device *dev, struct device *master, + void *data) +{ + struct sun4i_tv *tv = dev_get_drvdata(dev); + + drm_connector_cleanup(&tv->connector); + drm_encoder_cleanup(&tv->encoder); + clk_disable_unprepare(tv->clk); +} + +static struct component_ops sun4i_tv_ops = { + .bind = sun4i_tv_bind, + .unbind = sun4i_tv_unbind, +}; + +static int sun4i_tv_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &sun4i_tv_ops); +} + +static int sun4i_tv_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &sun4i_tv_ops); + + return 0; +} + +static const struct of_device_id sun4i_tv_of_table[] = { + { .compatible = "allwinner,sun4i-a10-tv-encoder" }, + { } +}; +MODULE_DEVICE_TABLE(of, sun4i_tv_of_table); + +static struct platform_driver sun4i_tv_platform_driver = { + .probe = sun4i_tv_probe, + .remove = sun4i_tv_remove, + .driver = { + .name = "sun4i-tve", + .of_match_table = sun4i_tv_of_table, + }, +}; +module_platform_driver(sun4i_tv_platform_driver); + +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); +MODULE_DESCRIPTION("Allwinner A10 TV Encoder Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/tegra/drm.c b/drivers/gpu/drm/tegra/drm.c index 8e6b18caa706..2be88eb0cb83 100644 --- a/drivers/gpu/drm/tegra/drm.c +++ b/drivers/gpu/drm/tegra/drm.c @@ -878,7 +878,7 @@ static int tegra_debugfs_framebuffers(struct seq_file *s, void *data) seq_printf(s, "%3d: user size: %d x %d, depth %d, %d bpp, refcount %d\n", fb->base.id, fb->width, fb->height, fb->depth, fb->bits_per_pixel, - atomic_read(&fb->refcount.refcount)); + drm_framebuffer_read_refcount(fb)); } mutex_unlock(&drm->mode_config.fb_lock); diff --git a/drivers/gpu/drm/ttm/Makefile b/drivers/gpu/drm/ttm/Makefile index b433b9f040c9..f92325800f8a 100644 --- a/drivers/gpu/drm/ttm/Makefile +++ b/drivers/gpu/drm/ttm/Makefile @@ -2,9 +2,10 @@ # Makefile for the drm device driver. This driver provides support for the ccflags-y := -Iinclude/drm -ttm-y := ttm_agp_backend.o ttm_memory.o ttm_tt.o ttm_bo.o \ +ttm-y := ttm_memory.o ttm_tt.o ttm_bo.o \ ttm_bo_util.o ttm_bo_vm.o ttm_module.o \ ttm_object.o ttm_lock.o ttm_execbuf_util.o ttm_page_alloc.o \ ttm_bo_manager.o ttm_page_alloc_dma.o +ttm-$(CONFIG_AGP) += ttm_agp_backend.o obj-$(CONFIG_DRM_TTM) += ttm.o diff --git a/drivers/gpu/drm/ttm/ttm_agp_backend.c b/drivers/gpu/drm/ttm/ttm_agp_backend.c index 764be36397fd..028ab6007873 100644 --- a/drivers/gpu/drm/ttm/ttm_agp_backend.c +++ b/drivers/gpu/drm/ttm/ttm_agp_backend.c @@ -34,7 +34,6 @@ #include <drm/ttm/ttm_module.h> #include <drm/ttm/ttm_bo_driver.h> #include <drm/ttm/ttm_page_alloc.h> -#ifdef TTM_HAS_AGP #include <drm/ttm/ttm_placement.h> #include <linux/agp_backend.h> #include <linux/module.h> @@ -148,5 +147,3 @@ void ttm_agp_tt_unpopulate(struct ttm_tt *ttm) ttm_pool_unpopulate(ttm); } EXPORT_SYMBOL(ttm_agp_tt_unpopulate); - -#endif diff --git a/drivers/gpu/drm/ttm/ttm_page_alloc.c b/drivers/gpu/drm/ttm/ttm_page_alloc.c index 025c429050c0..a37de5db5731 100644 --- a/drivers/gpu/drm/ttm/ttm_page_alloc.c +++ b/drivers/gpu/drm/ttm/ttm_page_alloc.c @@ -48,7 +48,7 @@ #include <drm/ttm/ttm_bo_driver.h> #include <drm/ttm/ttm_page_alloc.h> -#ifdef TTM_HAS_AGP +#if IS_ENABLED(CONFIG_AGP) #include <asm/agp.h> #endif @@ -219,7 +219,7 @@ static struct ttm_pool_manager *_manager; #ifndef CONFIG_X86 static int set_pages_array_wb(struct page **pages, int addrinarray) { -#ifdef TTM_HAS_AGP +#if IS_ENABLED(CONFIG_AGP) int i; for (i = 0; i < addrinarray; i++) @@ -230,7 +230,7 @@ static int set_pages_array_wb(struct page **pages, int addrinarray) static int set_pages_array_wc(struct page **pages, int addrinarray) { -#ifdef TTM_HAS_AGP +#if IS_ENABLED(CONFIG_AGP) int i; for (i = 0; i < addrinarray; i++) @@ -241,7 +241,7 @@ static int set_pages_array_wc(struct page **pages, int addrinarray) static int set_pages_array_uc(struct page **pages, int addrinarray) { -#ifdef TTM_HAS_AGP +#if IS_ENABLED(CONFIG_AGP) int i; for (i = 0; i < addrinarray; i++) diff --git a/drivers/gpu/drm/ttm/ttm_page_alloc_dma.c b/drivers/gpu/drm/ttm/ttm_page_alloc_dma.c index 624d941aaad1..bef9f6feb635 100644 --- a/drivers/gpu/drm/ttm/ttm_page_alloc_dma.c +++ b/drivers/gpu/drm/ttm/ttm_page_alloc_dma.c @@ -50,7 +50,7 @@ #include <linux/kthread.h> #include <drm/ttm/ttm_bo_driver.h> #include <drm/ttm/ttm_page_alloc.h> -#ifdef TTM_HAS_AGP +#if IS_ENABLED(CONFIG_AGP) #include <asm/agp.h> #endif @@ -271,7 +271,7 @@ static struct kobj_type ttm_pool_kobj_type = { #ifndef CONFIG_X86 static int set_pages_array_wb(struct page **pages, int addrinarray) { -#ifdef TTM_HAS_AGP +#if IS_ENABLED(CONFIG_AGP) int i; for (i = 0; i < addrinarray; i++) @@ -282,7 +282,7 @@ static int set_pages_array_wb(struct page **pages, int addrinarray) static int set_pages_array_wc(struct page **pages, int addrinarray) { -#ifdef TTM_HAS_AGP +#if IS_ENABLED(CONFIG_AGP) int i; for (i = 0; i < addrinarray; i++) @@ -293,7 +293,7 @@ static int set_pages_array_wc(struct page **pages, int addrinarray) static int set_pages_array_uc(struct page **pages, int addrinarray) { -#ifdef TTM_HAS_AGP +#if IS_ENABLED(CONFIG_AGP) int i; for (i = 0; i < addrinarray; i++) diff --git a/drivers/gpu/drm/udl/udl_drv.c b/drivers/gpu/drm/udl/udl_drv.c index 772ec9e1f590..c20408940cd0 100644 --- a/drivers/gpu/drm/udl/udl_drv.c +++ b/drivers/gpu/drm/udl/udl_drv.c @@ -94,7 +94,7 @@ static void udl_usb_disconnect(struct usb_interface *interface) struct drm_device *dev = usb_get_intfdata(interface); drm_kms_helper_poll_disable(dev); - drm_connector_unplug_all(dev); + drm_connector_unregister_all(dev); udl_fbdev_unplug(dev); udl_drop_usb(dev); drm_unplug_dev(dev); diff --git a/drivers/gpu/drm/vgem/vgem_drv.c b/drivers/gpu/drm/vgem/vgem_drv.c index c503a840fd88..ae4de36d1d83 100644 --- a/drivers/gpu/drm/vgem/vgem_drv.c +++ b/drivers/gpu/drm/vgem/vgem_drv.c @@ -89,7 +89,6 @@ int vgem_gem_get_pages(struct drm_vgem_gem_object *obj) static int vgem_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf) { struct drm_vgem_gem_object *obj = vma->vm_private_data; - struct drm_device *dev = obj->base.dev; loff_t num_pages; pgoff_t page_offset; int ret; @@ -103,12 +102,8 @@ static int vgem_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf) if (page_offset > num_pages) return VM_FAULT_SIGBUS; - mutex_lock(&dev->struct_mutex); - ret = vm_insert_page(vma, (unsigned long)vmf->virtual_address, obj->pages[page_offset]); - - mutex_unlock(&dev->struct_mutex); switch (ret) { case 0: return VM_FAULT_NOPAGE; @@ -154,6 +149,10 @@ static struct drm_gem_object *vgem_gem_create(struct drm_device *dev, if (err) goto out; + err = vgem_gem_get_pages(obj); + if (err) + goto out; + err = drm_gem_handle_create(file, gem_object, handle); if (err) goto handle_out; @@ -201,37 +200,23 @@ int vgem_gem_dumb_map(struct drm_file *file, struct drm_device *dev, int ret = 0; struct drm_gem_object *obj; - mutex_lock(&dev->struct_mutex); obj = drm_gem_object_lookup(dev, file, handle); - if (!obj) { - ret = -ENOENT; - goto unlock; - } + if (!obj) + return -ENOENT; - if (!drm_vma_node_has_offset(&obj->vma_node)) { - ret = drm_gem_create_mmap_offset(obj); - if (ret) - goto unref; - } + ret = drm_gem_create_mmap_offset(obj); + if (ret) + goto unref; BUG_ON(!obj->filp); obj->filp->private_data = obj; - ret = vgem_gem_get_pages(to_vgem_bo(obj)); - if (ret) - goto fail_get_pages; - *offset = drm_vma_node_offset_addr(&obj->vma_node); - goto unref; - -fail_get_pages: - drm_gem_free_mmap_offset(obj); unref: - drm_gem_object_unreference(obj); -unlock: - mutex_unlock(&dev->struct_mutex); + drm_gem_object_unreference_unlocked(obj); + return ret; } diff --git a/drivers/gpu/drm/virtio/virtgpu_display.c b/drivers/gpu/drm/virtio/virtgpu_display.c index 4854dac87e24..12b72e29678a 100644 --- a/drivers/gpu/drm/virtio/virtgpu_display.c +++ b/drivers/gpu/drm/virtio/virtgpu_display.c @@ -38,13 +38,6 @@ #define XRES_MAX 8192 #define YRES_MAX 8192 -static void virtio_gpu_crtc_gamma_set(struct drm_crtc *crtc, - u16 *red, u16 *green, u16 *blue, - uint32_t start, uint32_t size) -{ - /* TODO */ -} - static void virtio_gpu_hide_cursor(struct virtio_gpu_device *vgdev, struct virtio_gpu_output *output) @@ -173,7 +166,6 @@ static int virtio_gpu_page_flip(struct drm_crtc *crtc, static const struct drm_crtc_funcs virtio_gpu_crtc_funcs = { .cursor_set2 = virtio_gpu_crtc_cursor_set, .cursor_move = virtio_gpu_crtc_cursor_move, - .gamma_set = virtio_gpu_crtc_gamma_set, .set_config = drm_atomic_helper_set_config, .destroy = drm_crtc_cleanup, @@ -416,7 +408,6 @@ static int vgdev_output_init(struct virtio_gpu_device *vgdev, int index) return PTR_ERR(plane); drm_crtc_init_with_planes(dev, crtc, plane, NULL, &virtio_gpu_crtc_funcs, NULL); - drm_mode_crtc_set_gamma_size(crtc, 256); drm_crtc_helper_add(crtc, &virtio_gpu_crtc_helper_funcs); plane->crtc = crtc; diff --git a/drivers/gpu/drm/virtio/virtgpu_drv.c b/drivers/gpu/drm/virtio/virtgpu_drv.c index 7f898cfdc746..3cc7afa77a35 100644 --- a/drivers/gpu/drm/virtio/virtgpu_drv.c +++ b/drivers/gpu/drm/virtio/virtgpu_drv.c @@ -42,10 +42,8 @@ module_param_named(modeset, virtio_gpu_modeset, int, 0400); static int virtio_gpu_probe(struct virtio_device *vdev) { -#ifdef CONFIG_VGA_CONSOLE if (vgacon_text_force() && virtio_gpu_modeset == -1) return -EINVAL; -#endif if (virtio_gpu_modeset == 0) return -EINVAL; diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_drv.c b/drivers/gpu/drm/vmwgfx/vmwgfx_drv.c index 6cbb7d4bdd11..e7335a48ebf6 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_drv.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_drv.c @@ -1530,10 +1530,8 @@ static int __init vmwgfx_init(void) { int ret; -#ifdef CONFIG_VGA_CONSOLE if (vgacon_text_force()) return -EINVAL; -#endif ret = drm_pci_init(&driver, &vmw_pci_driver); if (ret) diff --git a/include/drm/bridge/analogix_dp.h b/include/drm/bridge/analogix_dp.h new file mode 100644 index 000000000000..25afb31f0389 --- /dev/null +++ b/include/drm/bridge/analogix_dp.h @@ -0,0 +1,41 @@ +/* + * Analogix DP (Display Port) Core interface driver. + * + * Copyright (C) 2015 Rockchip Electronics Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ +#ifndef _ANALOGIX_DP_H_ +#define _ANALOGIX_DP_H_ + +#include <drm/drm_crtc.h> + +enum analogix_dp_devtype { + EXYNOS_DP, + RK3288_DP, +}; + +struct analogix_dp_plat_data { + enum analogix_dp_devtype dev_type; + struct drm_panel *panel; + struct drm_encoder *encoder; + struct drm_connector *connector; + + int (*power_on)(struct analogix_dp_plat_data *); + int (*power_off)(struct analogix_dp_plat_data *); + int (*attach)(struct analogix_dp_plat_data *, struct drm_bridge *, + struct drm_connector *); + int (*get_modes)(struct analogix_dp_plat_data *); +}; + +int analogix_dp_resume(struct device *dev); +int analogix_dp_suspend(struct device *dev); + +int analogix_dp_bind(struct device *dev, struct drm_device *drm_dev, + struct analogix_dp_plat_data *plat_data); +void analogix_dp_unbind(struct device *dev, struct device *master, void *data); + +#endif /* _ANALOGIX_DP_H_ */ diff --git a/include/drm/drmP.h b/include/drm/drmP.h index 3c8422c69572..5de4cff05ac9 100644 --- a/include/drm/drmP.h +++ b/include/drm/drmP.h @@ -90,7 +90,7 @@ struct reservation_object; struct dma_buf_attachment; /* - * 4 debug categories are defined: + * The following categories are defined: * * CORE: Used in the generic drm code: drm_ioctl.c, drm_mm.c, drm_memory.c, ... * This is the category used by the DRM_DEBUG() macro. diff --git a/include/drm/drm_atomic_helper.h b/include/drm/drm_atomic_helper.h index 9054598c9a7a..fe9d89c7d1ed 100644 --- a/include/drm/drm_atomic_helper.h +++ b/include/drm/drm_atomic_helper.h @@ -42,6 +42,8 @@ int drm_atomic_helper_commit(struct drm_device *dev, struct drm_atomic_state *state, bool async); +void drm_atomic_helper_wait_for_fences(struct drm_device *dev, + struct drm_atomic_state *state); bool drm_atomic_helper_framebuffer_changed(struct drm_device *dev, struct drm_atomic_state *old_state, struct drm_crtc *crtc); diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h index e0170bf80bb0..297e527f1cac 100644 --- a/include/drm/drm_crtc.h +++ b/include/drm/drm_crtc.h @@ -45,20 +45,12 @@ struct drm_clip_rect; struct device_node; struct fence; -#define DRM_MODE_OBJECT_CRTC 0xcccccccc -#define DRM_MODE_OBJECT_CONNECTOR 0xc0c0c0c0 -#define DRM_MODE_OBJECT_ENCODER 0xe0e0e0e0 -#define DRM_MODE_OBJECT_MODE 0xdededede -#define DRM_MODE_OBJECT_PROPERTY 0xb0b0b0b0 -#define DRM_MODE_OBJECT_FB 0xfbfbfbfb -#define DRM_MODE_OBJECT_BLOB 0xbbbbbbbb -#define DRM_MODE_OBJECT_PLANE 0xeeeeeeee -#define DRM_MODE_OBJECT_ANY 0 - struct drm_mode_object { uint32_t id; uint32_t type; struct drm_object_properties *properties; + struct kref refcount; + void (*free_cb)(struct kref *kref); }; #define DRM_OBJECT_MAX_PROPERTY 24 @@ -233,8 +225,8 @@ struct drm_framebuffer { * should be deferred. In cases like this, the driver would like to * hold a ref to the fb even though it has already been removed from * userspace perspective. + * The refcount is stored inside the mode object. */ - struct kref refcount; /* * Place on the dev->mode_config.fb_list, access protected by * dev->mode_config.fb_lock. @@ -258,7 +250,6 @@ struct drm_framebuffer { struct drm_property_blob { struct drm_mode_object base; struct drm_device *dev; - struct kref refcount; struct list_head head_global; struct list_head head_file; size_t length; @@ -2259,8 +2250,9 @@ static inline unsigned drm_connector_index(struct drm_connector *connector) return connector->connector_id; } -/* helper to unplug all connectors from sysfs for device */ -extern void drm_connector_unplug_all(struct drm_device *dev); +/* helpers to {un}register all connectors from sysfs for device */ +extern int drm_connector_register_all(struct drm_device *dev); +extern void drm_connector_unregister_all(struct drm_device *dev); extern int drm_bridge_add(struct drm_bridge *bridge); extern void drm_bridge_remove(struct drm_bridge *bridge); @@ -2386,8 +2378,6 @@ extern int drm_framebuffer_init(struct drm_device *dev, const struct drm_framebuffer_funcs *funcs); extern struct drm_framebuffer *drm_framebuffer_lookup(struct drm_device *dev, uint32_t id); -extern void drm_framebuffer_unreference(struct drm_framebuffer *fb); -extern void drm_framebuffer_reference(struct drm_framebuffer *fb); extern void drm_framebuffer_remove(struct drm_framebuffer *fb); extern void drm_framebuffer_cleanup(struct drm_framebuffer *fb); extern void drm_framebuffer_unregister_private(struct drm_framebuffer *fb); @@ -2445,6 +2435,8 @@ extern int drm_mode_crtc_set_gamma_size(struct drm_crtc *crtc, int gamma_size); extern struct drm_mode_object *drm_mode_object_find(struct drm_device *dev, uint32_t id, uint32_t type); +void drm_mode_object_reference(struct drm_mode_object *obj); +void drm_mode_object_unreference(struct drm_mode_object *obj); /* IOCTLs */ extern int drm_mode_getresources(struct drm_device *dev, @@ -2510,6 +2502,8 @@ extern int drm_edid_header_is_valid(const u8 *raw_edid); extern bool drm_edid_block_valid(u8 *raw_edid, int block, bool print_bad_edid, bool *edid_corrupt); extern bool drm_edid_is_valid(struct edid *edid); +extern void drm_edid_get_monitor_name(struct edid *edid, char *name, + int buflen); extern struct drm_tile_group *drm_mode_create_tile_group(struct drm_device *dev, char topology[8]); @@ -2600,14 +2594,51 @@ static inline struct drm_property *drm_property_find(struct drm_device *dev, static inline uint32_t drm_color_lut_extract(uint32_t user_input, uint32_t bit_precision) { - uint32_t val = user_input + (1 << (16 - bit_precision - 1)); + uint32_t val = user_input; uint32_t max = 0xffff >> (16 - bit_precision); - val >>= 16 - bit_precision; + /* Round only if we're not using full precision. */ + if (bit_precision < 16) { + val += 1UL << (16 - bit_precision - 1); + val >>= 16 - bit_precision; + } return clamp_val(val, 0, max); } +/* + * drm_framebuffer_reference - incr the fb refcnt + * @fb: framebuffer + * + * This functions increments the fb's refcount. + */ +static inline void drm_framebuffer_reference(struct drm_framebuffer *fb) +{ + drm_mode_object_reference(&fb->base); +} + +/** + * drm_framebuffer_unreference - unref a framebuffer + * @fb: framebuffer to unref + * + * This functions decrements the fb's refcount and frees it if it drops to zero. + */ +static inline void drm_framebuffer_unreference(struct drm_framebuffer *fb) +{ + drm_mode_object_unreference(&fb->base); +} + +/** + * drm_framebuffer_read_refcount - read the framebuffer reference count. + * @fb: framebuffer + * + * This functions returns the framebuffer's reference count. + */ +static inline uint32_t drm_framebuffer_read_refcount(struct drm_framebuffer *fb) +{ + return atomic_read(&fb->base.refcount.refcount); +} + /* Plane list iterator for legacy (overlay only) planes. */ #define drm_for_each_legacy_plane(plane, dev) \ list_for_each_entry(plane, &(dev)->mode_config.plane_list, head) \ diff --git a/include/drm/drm_edid.h b/include/drm/drm_edid.h index dec6221e8198..919933d1beb4 100644 --- a/include/drm/drm_edid.h +++ b/include/drm/drm_edid.h @@ -328,7 +328,15 @@ int drm_edid_to_speaker_allocation(struct edid *edid, u8 **sadb); int drm_av_sync_delay(struct drm_connector *connector, const struct drm_display_mode *mode); struct drm_connector *drm_select_eld(struct drm_encoder *encoder); + +#ifdef CONFIG_DRM_LOAD_EDID_FIRMWARE int drm_load_edid_firmware(struct drm_connector *connector); +#else +static inline int drm_load_edid_firmware(struct drm_connector *connector) +{ + return 0; +} +#endif int drm_hdmi_avi_infoframe_from_display_mode(struct hdmi_avi_infoframe *frame, diff --git a/include/drm/drm_fb_cma_helper.h b/include/drm/drm_fb_cma_helper.h index be62bd321e75..ae49c24fbf50 100644 --- a/include/drm/drm_fb_cma_helper.h +++ b/include/drm/drm_fb_cma_helper.h @@ -24,6 +24,8 @@ struct drm_gem_cma_object *drm_fb_cma_get_gem_obj(struct drm_framebuffer *fb, unsigned int plane); #ifdef CONFIG_DEBUG_FS +struct seq_file; + int drm_fb_cma_debugfs_show(struct seq_file *m, void *arg); #endif diff --git a/include/drm/drm_vma_manager.h b/include/drm/drm_vma_manager.h index 2f63dd5e05eb..06ea8e077ec2 100644 --- a/include/drm/drm_vma_manager.h +++ b/include/drm/drm_vma_manager.h @@ -176,19 +176,6 @@ static inline unsigned long drm_vma_node_size(struct drm_vma_offset_node *node) } /** - * drm_vma_node_has_offset() - Check whether node is added to offset manager - * @node: Node to be checked - * - * RETURNS: - * true iff the node was previously allocated an offset and added to - * an vma offset manager. - */ -static inline bool drm_vma_node_has_offset(struct drm_vma_offset_node *node) -{ - return drm_mm_node_allocated(&node->vm_node); -} - -/** * drm_vma_node_offset_addr() - Return sanitized offset for user-space mmaps * @node: Linked offset node * @@ -220,7 +207,7 @@ static inline __u64 drm_vma_node_offset_addr(struct drm_vma_offset_node *node) static inline void drm_vma_node_unmap(struct drm_vma_offset_node *node, struct address_space *file_mapping) { - if (drm_vma_node_has_offset(node)) + if (drm_mm_node_allocated(&node->vm_node)) unmap_mapping_range(file_mapping, drm_vma_node_offset_addr(node), drm_vma_node_size(node) << PAGE_SHIFT, 1); diff --git a/include/drm/ttm/ttm_bo_driver.h b/include/drm/ttm/ttm_bo_driver.h index 3d4bf08aa21f..cb91f80c15b3 100644 --- a/include/drm/ttm/ttm_bo_driver.h +++ b/include/drm/ttm/ttm_bo_driver.h @@ -1030,8 +1030,7 @@ extern pgprot_t ttm_io_prot(uint32_t caching_flags, pgprot_t tmp); extern const struct ttm_mem_type_manager_func ttm_bo_manager_func; -#if (defined(CONFIG_AGP) || (defined(CONFIG_AGP_MODULE) && defined(MODULE))) -#define TTM_HAS_AGP +#if IS_ENABLED(CONFIG_AGP) #include <linux/agp_backend.h> /** diff --git a/include/linux/console.h b/include/linux/console.h index ea731af2451e..e49cc1ef19be 100644 --- a/include/linux/console.h +++ b/include/linux/console.h @@ -191,6 +191,8 @@ void vcs_remove_sysfs(int index); #ifdef CONFIG_VGA_CONSOLE extern bool vgacon_text_force(void); +#else +static inline bool vgacon_text_force(void) { return false; } #endif #endif /* _LINUX_CONSOLE_H */ diff --git a/include/uapi/drm/drm.h b/include/uapi/drm/drm.h index a0ebfe7c9a28..368325061ca7 100644 --- a/include/uapi/drm/drm.h +++ b/include/uapi/drm/drm.h @@ -36,7 +36,13 @@ #ifndef _DRM_H_ #define _DRM_H_ -#if defined(__KERNEL__) || defined(__linux__) +#if defined(__KERNEL__) + +#include <linux/types.h> +#include <asm/ioctl.h> +typedef unsigned int drm_handle_t; + +#elif defined(__linux__) #include <linux/types.h> #include <asm/ioctl.h> @@ -181,7 +187,7 @@ enum drm_map_type { _DRM_SHM = 2, /**< shared, cached */ _DRM_AGP = 3, /**< AGP/GART */ _DRM_SCATTER_GATHER = 4, /**< Scatter/gather memory for PCI DMA */ - _DRM_CONSISTENT = 5, /**< Consistent memory for PCI DMA */ + _DRM_CONSISTENT = 5 /**< Consistent memory for PCI DMA */ }; /** @@ -373,7 +379,11 @@ struct drm_buf_pub { */ struct drm_buf_map { int count; /**< Length of the buffer list */ +#ifdef __cplusplus + void __user *virt; +#else void __user *virtual; /**< Mmap'd area in user-virtual */ +#endif struct drm_buf_pub __user *list; /**< Buffer information */ }; @@ -431,7 +441,7 @@ struct drm_draw { * DRM_IOCTL_UPDATE_DRAW ioctl argument type. */ typedef enum { - DRM_DRAWABLE_CLIPRECTS, + DRM_DRAWABLE_CLIPRECTS } drm_drawable_info_type_t; struct drm_update_draw { @@ -681,7 +691,7 @@ struct drm_prime_handle { __s32 fd; }; -#include <drm/drm_mode.h> +#include "drm_mode.h" #define DRM_IOCTL_BASE 'd' #define DRM_IO(nr) _IO(DRM_IOCTL_BASE,nr) diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h index c0217434d28d..7a7856e02e49 100644 --- a/include/uapi/drm/drm_mode.h +++ b/include/uapi/drm/drm_mode.h @@ -320,6 +320,16 @@ struct drm_mode_connector_set_property { __u32 connector_id; }; +#define DRM_MODE_OBJECT_CRTC 0xcccccccc +#define DRM_MODE_OBJECT_CONNECTOR 0xc0c0c0c0 +#define DRM_MODE_OBJECT_ENCODER 0xe0e0e0e0 +#define DRM_MODE_OBJECT_MODE 0xdededede +#define DRM_MODE_OBJECT_PROPERTY 0xb0b0b0b0 +#define DRM_MODE_OBJECT_FB 0xfbfbfbfb +#define DRM_MODE_OBJECT_BLOB 0xbbbbbbbb +#define DRM_MODE_OBJECT_PLANE 0xeeeeeeee +#define DRM_MODE_OBJECT_ANY 0 + struct drm_mode_obj_get_properties { __u64 props_ptr; __u64 prop_values_ptr; diff --git a/include/uapi/drm/qxl_drm.h b/include/uapi/drm/qxl_drm.h index 4d1e32640463..826615d8e180 100644 --- a/include/uapi/drm/qxl_drm.h +++ b/include/uapi/drm/qxl_drm.h @@ -84,7 +84,6 @@ struct drm_qxl_command { __u32 pad; }; -/* XXX: call it drm_qxl_commands? */ struct drm_qxl_execbuffer { __u32 flags; /* for future use */ __u32 commands_num; diff --git a/include/uapi/drm/sis_drm.h b/include/uapi/drm/sis_drm.h index 374858cdcdaa..a20d88bc4970 100644 --- a/include/uapi/drm/sis_drm.h +++ b/include/uapi/drm/sis_drm.h @@ -27,6 +27,8 @@ #ifndef __SIS_DRM_H__ #define __SIS_DRM_H__ +#include "drm.h" + /* SiS specific ioctls */ #define NOT_USED_0_3 #define DRM_SIS_FB_ALLOC 0x04 diff --git a/include/video/mipi_display.h b/include/video/mipi_display.h index ddcc8ca7316b..19aa65a35546 100644 --- a/include/video/mipi_display.h +++ b/include/video/mipi_display.h @@ -115,6 +115,14 @@ enum { MIPI_DCS_READ_MEMORY_CONTINUE = 0x3E, MIPI_DCS_SET_TEAR_SCANLINE = 0x44, MIPI_DCS_GET_SCANLINE = 0x45, + MIPI_DCS_SET_DISPLAY_BRIGHTNESS = 0x51, /* MIPI DCS 1.3 */ + MIPI_DCS_GET_DISPLAY_BRIGHTNESS = 0x52, /* MIPI DCS 1.3 */ + MIPI_DCS_WRITE_CONTROL_DISPLAY = 0x53, /* MIPI DCS 1.3 */ + MIPI_DCS_GET_CONTROL_DISPLAY = 0x54, /* MIPI DCS 1.3 */ + MIPI_DCS_WRITE_POWER_SAVE = 0x55, /* MIPI DCS 1.3 */ + MIPI_DCS_GET_POWER_SAVE = 0x56, /* MIPI DCS 1.3 */ + MIPI_DCS_SET_CABC_MIN_BRIGHTNESS = 0x5E, /* MIPI DCS 1.3 */ + MIPI_DCS_GET_CABC_MIN_BRIGHTNESS = 0x5F, /* MIPI DCS 1.3 */ MIPI_DCS_READ_DDB_START = 0xA1, MIPI_DCS_READ_DDB_CONTINUE = 0xA8, }; |