mirror of
				git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
				synced 2025-10-31 16:54:21 +00:00 
			
		
		
		
	Xilinx ZynqMP DisplayPort bridge support
-----BEGIN PGP SIGNATURE----- iJgEABYKAEAWIQTAnvhxs4J7QT+XHKnMPy2AAyfeZAUCY1cckSIcbGF1cmVudC5w aW5jaGFydEBpZGVhc29uYm9hcmQuY29tAAoJEMw/LYADJ95k29wA/iL0kT48c4PJ XIFqNRkQKzdTA2rCzeEZDImSpwEsnY4tAP0S5/Z/E6HecvJUydx1PuqIM3kkQOOs qe9EPcnPiEuOBQ== =Ap68 -----END PGP SIGNATURE----- Merge tag 'drm-next-20221025' of git://linuxtv.org/pinchartl/media into drm-next Xilinx ZynqMP DisplayPort bridge support Signed-off-by: Dave Airlie <airlied@redhat.com> From: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Link: https://patchwork.freedesktop.org/patch/msgid/Y1cdU4HJoy0Pr2sQ@pendragon.ideasonboard.com
This commit is contained in:
		
						commit
						7f7a942c0a
					
				
					 12 changed files with 1394 additions and 957 deletions
				
			
		|  | @ -117,6 +117,45 @@ properties: | |||
|       - const: dp-phy0 | ||||
|       - const: dp-phy1 | ||||
| 
 | ||||
|   ports: | ||||
|     $ref: /schemas/graph.yaml#/properties/ports | ||||
|     description: | | ||||
|       Connections to the programmable logic and the DisplayPort PHYs. Each port | ||||
|       shall have a single endpoint. | ||||
| 
 | ||||
|     properties: | ||||
|       port@0: | ||||
|         $ref: /schemas/graph.yaml#/properties/port | ||||
|         description: The live video input from the programmable logic | ||||
| 
 | ||||
|       port@1: | ||||
|         $ref: /schemas/graph.yaml#/properties/port | ||||
|         description: The live graphics input from the programmable logic | ||||
| 
 | ||||
|       port@2: | ||||
|         $ref: /schemas/graph.yaml#/properties/port | ||||
|         description: The live audio input from the programmable logic | ||||
| 
 | ||||
|       port@3: | ||||
|         $ref: /schemas/graph.yaml#/properties/port | ||||
|         description: The blended video output to the programmable logic | ||||
| 
 | ||||
|       port@4: | ||||
|         $ref: /schemas/graph.yaml#/properties/port | ||||
|         description: The mixed audio output to the programmable logic | ||||
| 
 | ||||
|       port@5: | ||||
|         $ref: /schemas/graph.yaml#/properties/port | ||||
|         description: The DisplayPort output | ||||
| 
 | ||||
|     required: | ||||
|       - port@0 | ||||
|       - port@1 | ||||
|       - port@2 | ||||
|       - port@3 | ||||
|       - port@4 | ||||
|       - port@5 | ||||
| 
 | ||||
| required: | ||||
|   - compatible | ||||
|   - reg | ||||
|  | @ -130,6 +169,7 @@ required: | |||
|   - dma-names | ||||
|   - phys | ||||
|   - phy-names | ||||
|   - ports | ||||
| 
 | ||||
| additionalProperties: false | ||||
| 
 | ||||
|  | @ -164,6 +204,33 @@ examples: | |||
|                <&psgtr 0 PHY_TYPE_DP 1 3>; | ||||
| 
 | ||||
|         phy-names = "dp-phy0", "dp-phy1"; | ||||
| 
 | ||||
|         ports { | ||||
|             #address-cells = <1>; | ||||
|             #size-cells = <0>; | ||||
| 
 | ||||
|             port@0 { | ||||
|                 reg = <0>; | ||||
|             }; | ||||
|             port@1 { | ||||
|                 reg = <1>; | ||||
|             }; | ||||
|             port@2 { | ||||
|                 reg = <2>; | ||||
|             }; | ||||
|             port@3 { | ||||
|                 reg = <3>; | ||||
|             }; | ||||
|             port@4 { | ||||
|                 reg = <4>; | ||||
|             }; | ||||
|             port@5 { | ||||
|                 reg = <5>; | ||||
|                 dpsub_dp_out: endpoint { | ||||
|                     remote-endpoint = <&dp_connector>; | ||||
|                 }; | ||||
|             }; | ||||
|         }; | ||||
|     }; | ||||
| 
 | ||||
| ... | ||||
|  |  | |||
|  | @ -150,6 +150,18 @@ | |||
| 		#clock-cells = <0>; | ||||
| 		clock-frequency = <114285000>; | ||||
| 	}; | ||||
| 
 | ||||
| 	dpcon { | ||||
| 		compatible = "dp-connector"; | ||||
| 		label = "P11"; | ||||
| 		type = "full-size"; | ||||
| 
 | ||||
| 		port { | ||||
| 			dpcon_in: endpoint { | ||||
| 				remote-endpoint = <&dpsub_dp_out>; | ||||
| 			}; | ||||
| 		}; | ||||
| 	}; | ||||
| }; | ||||
| 
 | ||||
| &can1 { | ||||
|  | @ -1015,4 +1027,12 @@ | |||
| 	phy-names = "dp-phy0", "dp-phy1"; | ||||
| 	phys = <&psgtr 1 PHY_TYPE_DP 0 3>, | ||||
| 	       <&psgtr 0 PHY_TYPE_DP 1 3>; | ||||
| 
 | ||||
| 	ports { | ||||
| 		port@5 { | ||||
| 			dpsub_dp_out: endpoint { | ||||
| 				remote-endpoint = <&dpcon_in>; | ||||
| 			}; | ||||
| 		}; | ||||
| 	}; | ||||
| }; | ||||
|  |  | |||
|  | @ -930,6 +930,30 @@ | |||
| 			       <&zynqmp_dpdma ZYNQMP_DPDMA_VIDEO1>, | ||||
| 			       <&zynqmp_dpdma ZYNQMP_DPDMA_VIDEO2>, | ||||
| 			       <&zynqmp_dpdma ZYNQMP_DPDMA_GRAPHICS>; | ||||
| 
 | ||||
| 			ports { | ||||
| 				#address-cells = <1>; | ||||
| 				#size-cells = <0>; | ||||
| 
 | ||||
| 				port@0 { | ||||
| 					reg = <0>; | ||||
| 				}; | ||||
| 				port@1 { | ||||
| 					reg = <1>; | ||||
| 				}; | ||||
| 				port@2 { | ||||
| 					reg = <2>; | ||||
| 				}; | ||||
| 				port@3 { | ||||
| 					reg = <3>; | ||||
| 				}; | ||||
| 				port@4 { | ||||
| 					reg = <4>; | ||||
| 				}; | ||||
| 				port@5 { | ||||
| 					reg = <5>; | ||||
| 				}; | ||||
| 			}; | ||||
| 		}; | ||||
| 	}; | ||||
| }; | ||||
|  |  | |||
|  | @ -1,2 +1,2 @@ | |||
| zynqmp-dpsub-y := zynqmp_disp.o zynqmp_dpsub.o zynqmp_dp.o | ||||
| zynqmp-dpsub-y := zynqmp_disp.o zynqmp_dpsub.o zynqmp_dp.o zynqmp_kms.o | ||||
| obj-$(CONFIG_DRM_ZYNQMP_DPSUB) += zynqmp-dpsub.o | ||||
|  |  | |||
|  | @ -9,29 +9,19 @@ | |||
|  * - Laurent Pinchart <laurent.pinchart@ideasonboard.com> | ||||
|  */ | ||||
| 
 | ||||
| #include <drm/drm_atomic.h> | ||||
| #include <drm/drm_atomic_helper.h> | ||||
| #include <drm/drm_atomic_uapi.h> | ||||
| #include <drm/drm_blend.h> | ||||
| #include <drm/drm_crtc.h> | ||||
| #include <drm/drm_device.h> | ||||
| #include <drm/drm_fb_dma_helper.h> | ||||
| #include <drm/drm_fourcc.h> | ||||
| #include <drm/drm_framebuffer.h> | ||||
| #include <drm/drm_managed.h> | ||||
| #include <drm/drm_plane.h> | ||||
| #include <drm/drm_vblank.h> | ||||
| 
 | ||||
| #include <linux/clk.h> | ||||
| #include <linux/delay.h> | ||||
| #include <linux/dma/xilinx_dpdma.h> | ||||
| #include <linux/dma-mapping.h> | ||||
| #include <linux/dmaengine.h> | ||||
| #include <linux/module.h> | ||||
| #include <linux/of.h> | ||||
| #include <linux/platform_device.h> | ||||
| #include <linux/pm_runtime.h> | ||||
| #include <linux/spinlock.h> | ||||
| #include <linux/slab.h> | ||||
| 
 | ||||
| #include "zynqmp_disp.h" | ||||
| #include "zynqmp_disp_regs.h" | ||||
|  | @ -72,45 +62,22 @@ | |||
| #define ZYNQMP_DISP_AV_BUF_NUM_VID_GFX_BUFFERS		4 | ||||
| #define ZYNQMP_DISP_AV_BUF_NUM_BUFFERS			6 | ||||
| 
 | ||||
| #define ZYNQMP_DISP_NUM_LAYERS				2 | ||||
| #define ZYNQMP_DISP_MAX_NUM_SUB_PLANES			3 | ||||
| 
 | ||||
| /**
 | ||||
|  * struct zynqmp_disp_format - Display subsystem format information | ||||
|  * @drm_fmt: DRM format (4CC) | ||||
|  * @buf_fmt: AV buffer format | ||||
|  * @bus_fmt: Media bus formats (live formats) | ||||
|  * @swap: Flag to swap R & B for RGB formats, and U & V for YUV formats | ||||
|  * @sf: Scaling factors for color components | ||||
|  */ | ||||
| struct zynqmp_disp_format { | ||||
| 	u32 drm_fmt; | ||||
| 	u32 buf_fmt; | ||||
| 	u32 bus_fmt; | ||||
| 	bool swap; | ||||
| 	const u32 *sf; | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * enum zynqmp_disp_layer_id - Layer identifier | ||||
|  * @ZYNQMP_DISP_LAYER_VID: Video layer | ||||
|  * @ZYNQMP_DISP_LAYER_GFX: Graphics layer | ||||
|  */ | ||||
| enum zynqmp_disp_layer_id { | ||||
| 	ZYNQMP_DISP_LAYER_VID, | ||||
| 	ZYNQMP_DISP_LAYER_GFX | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * enum zynqmp_disp_layer_mode - Layer mode | ||||
|  * @ZYNQMP_DISP_LAYER_NONLIVE: non-live (memory) mode | ||||
|  * @ZYNQMP_DISP_LAYER_LIVE: live (stream) mode | ||||
|  */ | ||||
| enum zynqmp_disp_layer_mode { | ||||
| 	ZYNQMP_DISP_LAYER_NONLIVE, | ||||
| 	ZYNQMP_DISP_LAYER_LIVE | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * struct zynqmp_disp_layer_dma - DMA channel for one data plane of a layer | ||||
|  * @chan: DMA channel | ||||
|  | @ -136,8 +103,7 @@ struct zynqmp_disp_layer_info { | |||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * struct zynqmp_disp_layer - Display layer (DRM plane) | ||||
|  * @plane: DRM plane | ||||
|  * struct zynqmp_disp_layer - Display layer | ||||
|  * @id: Layer ID | ||||
|  * @disp: Back pointer to struct zynqmp_disp | ||||
|  * @info: Static layer information | ||||
|  | @ -147,8 +113,7 @@ struct zynqmp_disp_layer_info { | |||
|  * @mode: Current operation mode | ||||
|  */ | ||||
| struct zynqmp_disp_layer { | ||||
| 	struct drm_plane plane; | ||||
| 	enum zynqmp_disp_layer_id id; | ||||
| 	enum zynqmp_dpsub_layer_id id; | ||||
| 	struct zynqmp_disp *disp; | ||||
| 	const struct zynqmp_disp_layer_info *info; | ||||
| 
 | ||||
|  | @ -156,32 +121,22 @@ struct zynqmp_disp_layer { | |||
| 
 | ||||
| 	const struct zynqmp_disp_format *disp_fmt; | ||||
| 	const struct drm_format_info *drm_fmt; | ||||
| 	enum zynqmp_disp_layer_mode mode; | ||||
| 	enum zynqmp_dpsub_layer_mode mode; | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * struct zynqmp_disp - Display controller | ||||
|  * @dev: Device structure | ||||
|  * @drm: DRM core | ||||
|  * @dpsub: Display subsystem | ||||
|  * @crtc: DRM CRTC | ||||
|  * @blend.base: Register I/O base address for the blender | ||||
|  * @avbuf.base: Register I/O base address for the audio/video buffer manager | ||||
|  * @audio.base: Registers I/O base address for the audio mixer | ||||
|  * @audio.clk: Audio clock | ||||
|  * @audio.clk_from_ps: True of the audio clock comes from PS, false from PL | ||||
|  * @layers: Layers (planes) | ||||
|  * @event: Pending vblank event request | ||||
|  * @pclk: Pixel clock | ||||
|  * @pclk_from_ps: True of the video clock comes from PS, false from PL | ||||
|  */ | ||||
| struct zynqmp_disp { | ||||
| 	struct device *dev; | ||||
| 	struct drm_device *drm; | ||||
| 	struct zynqmp_dpsub *dpsub; | ||||
| 
 | ||||
| 	struct drm_crtc crtc; | ||||
| 
 | ||||
| 	struct { | ||||
| 		void __iomem *base; | ||||
| 	} blend; | ||||
|  | @ -190,16 +145,9 @@ struct zynqmp_disp { | |||
| 	} avbuf; | ||||
| 	struct { | ||||
| 		void __iomem *base; | ||||
| 		struct clk *clk; | ||||
| 		bool clk_from_ps; | ||||
| 	} audio; | ||||
| 
 | ||||
| 	struct zynqmp_disp_layer layers[ZYNQMP_DISP_NUM_LAYERS]; | ||||
| 
 | ||||
| 	struct drm_pending_vblank_event *event; | ||||
| 
 | ||||
| 	struct clk *pclk; | ||||
| 	bool pclk_from_ps; | ||||
| 	struct zynqmp_disp_layer layers[ZYNQMP_DPSUB_NUM_LAYERS]; | ||||
| }; | ||||
| 
 | ||||
| /* -----------------------------------------------------------------------------
 | ||||
|  | @ -416,14 +364,9 @@ static void zynqmp_disp_avbuf_write(struct zynqmp_disp *disp, int reg, u32 val) | |||
| 	writel(val, disp->avbuf.base + reg); | ||||
| } | ||||
| 
 | ||||
| static bool zynqmp_disp_layer_is_gfx(const struct zynqmp_disp_layer *layer) | ||||
| { | ||||
| 	return layer->id == ZYNQMP_DISP_LAYER_GFX; | ||||
| } | ||||
| 
 | ||||
| static bool zynqmp_disp_layer_is_video(const struct zynqmp_disp_layer *layer) | ||||
| { | ||||
| 	return layer->id == ZYNQMP_DISP_LAYER_VID; | ||||
| 	return layer->id == ZYNQMP_DPSUB_LAYER_VID; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  | @ -566,27 +509,25 @@ static void zynqmp_disp_avbuf_disable_audio(struct zynqmp_disp *disp) | |||
|  * zynqmp_disp_avbuf_enable_video - Enable a video layer | ||||
|  * @disp: Display controller | ||||
|  * @layer: The layer | ||||
|  * @mode: Operating mode of layer | ||||
|  * | ||||
|  * Enable the video/graphics buffer for @layer. | ||||
|  */ | ||||
| static void zynqmp_disp_avbuf_enable_video(struct zynqmp_disp *disp, | ||||
| 					   struct zynqmp_disp_layer *layer, | ||||
| 					   enum zynqmp_disp_layer_mode mode) | ||||
| 					   struct zynqmp_disp_layer *layer) | ||||
| { | ||||
| 	u32 val; | ||||
| 
 | ||||
| 	val = zynqmp_disp_avbuf_read(disp, ZYNQMP_DISP_AV_BUF_OUTPUT); | ||||
| 	if (zynqmp_disp_layer_is_video(layer)) { | ||||
| 		val &= ~ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_MASK; | ||||
| 		if (mode == ZYNQMP_DISP_LAYER_NONLIVE) | ||||
| 		if (layer->mode == ZYNQMP_DPSUB_LAYER_NONLIVE) | ||||
| 			val |= ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_MEM; | ||||
| 		else | ||||
| 			val |= ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_LIVE; | ||||
| 	} else { | ||||
| 		val &= ~ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_MASK; | ||||
| 		val |= ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_MEM; | ||||
| 		if (mode == ZYNQMP_DISP_LAYER_NONLIVE) | ||||
| 		if (layer->mode == ZYNQMP_DPSUB_LAYER_NONLIVE) | ||||
| 			val |= ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_MEM; | ||||
| 		else | ||||
| 			val |= ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_LIVE; | ||||
|  | @ -758,8 +699,8 @@ static void zynqmp_disp_blend_set_bg_color(struct zynqmp_disp *disp, | |||
|  * @enable: True to enable global alpha blending | ||||
|  * @alpha: Global alpha value (ignored if @enabled is false) | ||||
|  */ | ||||
| static void zynqmp_disp_blend_set_global_alpha(struct zynqmp_disp *disp, | ||||
| 					       bool enable, u32 alpha) | ||||
| void zynqmp_disp_blend_set_global_alpha(struct zynqmp_disp *disp, | ||||
| 					bool enable, u32 alpha) | ||||
| { | ||||
| 	zynqmp_disp_blend_write(disp, ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA, | ||||
| 				ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA_VALUE(alpha) | | ||||
|  | @ -902,80 +843,6 @@ static void zynqmp_disp_audio_disable(struct zynqmp_disp *disp) | |||
| 				ZYNQMP_DISP_AUD_SOFT_RESET_AUD_SRST); | ||||
| } | ||||
| 
 | ||||
| static void zynqmp_disp_audio_init(struct zynqmp_disp *disp) | ||||
| { | ||||
| 	/* Try the live PL audio clock. */ | ||||
| 	disp->audio.clk = devm_clk_get(disp->dev, "dp_live_audio_aclk"); | ||||
| 	if (!IS_ERR(disp->audio.clk)) { | ||||
| 		disp->audio.clk_from_ps = false; | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	/* If the live PL audio clock is not valid, fall back to PS clock. */ | ||||
| 	disp->audio.clk = devm_clk_get(disp->dev, "dp_aud_clk"); | ||||
| 	if (!IS_ERR(disp->audio.clk)) { | ||||
| 		disp->audio.clk_from_ps = true; | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	dev_err(disp->dev, "audio disabled due to missing clock\n"); | ||||
| } | ||||
| 
 | ||||
| /* -----------------------------------------------------------------------------
 | ||||
|  * ZynqMP Display external functions for zynqmp_dp | ||||
|  */ | ||||
| 
 | ||||
| /**
 | ||||
|  * zynqmp_disp_handle_vblank - Handle the vblank event | ||||
|  * @disp: Display controller | ||||
|  * | ||||
|  * This function handles the vblank interrupt, and sends an event to | ||||
|  * CRTC object. This will be called by the DP vblank interrupt handler. | ||||
|  */ | ||||
| void zynqmp_disp_handle_vblank(struct zynqmp_disp *disp) | ||||
| { | ||||
| 	struct drm_crtc *crtc = &disp->crtc; | ||||
| 
 | ||||
| 	drm_crtc_handle_vblank(crtc); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * zynqmp_disp_audio_enabled - If the audio is enabled | ||||
|  * @disp: Display controller | ||||
|  * | ||||
|  * Return if the audio is enabled depending on the audio clock. | ||||
|  * | ||||
|  * Return: true if audio is enabled, or false. | ||||
|  */ | ||||
| bool zynqmp_disp_audio_enabled(struct zynqmp_disp *disp) | ||||
| { | ||||
| 	return !!disp->audio.clk; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * zynqmp_disp_get_audio_clk_rate - Get the current audio clock rate | ||||
|  * @disp: Display controller | ||||
|  * | ||||
|  * Return: the current audio clock rate. | ||||
|  */ | ||||
| unsigned int zynqmp_disp_get_audio_clk_rate(struct zynqmp_disp *disp) | ||||
| { | ||||
| 	if (zynqmp_disp_audio_enabled(disp)) | ||||
| 		return 0; | ||||
| 	return clk_get_rate(disp->audio.clk); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * zynqmp_disp_get_crtc_mask - Return the CRTC bit mask | ||||
|  * @disp: Display controller | ||||
|  * | ||||
|  * Return: the crtc mask of the zyqnmp_disp CRTC. | ||||
|  */ | ||||
| uint32_t zynqmp_disp_get_crtc_mask(struct zynqmp_disp *disp) | ||||
| { | ||||
| 	return drm_crtc_mask(&disp->crtc); | ||||
| } | ||||
| 
 | ||||
| /* -----------------------------------------------------------------------------
 | ||||
|  * ZynqMP Display Layer & DRM Plane | ||||
|  */ | ||||
|  | @ -1005,20 +872,47 @@ zynqmp_disp_layer_find_format(struct zynqmp_disp_layer *layer, | |||
| 	return NULL; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * zynqmp_disp_layer_drm_formats - Return the DRM formats supported by the layer | ||||
|  * @layer: The layer | ||||
|  * @num_formats: Pointer to the returned number of formats | ||||
|  * | ||||
|  * Return: A newly allocated u32 array that stores all the DRM formats | ||||
|  * supported by the layer. The number of formats in the array is returned | ||||
|  * through the num_formats argument. | ||||
|  */ | ||||
| u32 *zynqmp_disp_layer_drm_formats(struct zynqmp_disp_layer *layer, | ||||
| 				   unsigned int *num_formats) | ||||
| { | ||||
| 	unsigned int i; | ||||
| 	u32 *formats; | ||||
| 
 | ||||
| 	formats = kcalloc(layer->info->num_formats, sizeof(*formats), | ||||
| 			  GFP_KERNEL); | ||||
| 	if (!formats) | ||||
| 		return NULL; | ||||
| 
 | ||||
| 	for (i = 0; i < layer->info->num_formats; ++i) | ||||
| 		formats[i] = layer->info->formats[i].drm_fmt; | ||||
| 
 | ||||
| 	*num_formats = layer->info->num_formats; | ||||
| 	return formats; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * zynqmp_disp_layer_enable - Enable a layer | ||||
|  * @layer: The layer | ||||
|  * @mode: Operating mode of layer | ||||
|  * | ||||
|  * Enable the @layer in the audio/video buffer manager and the blender. DMA | ||||
|  * channels are started separately by zynqmp_disp_layer_update(). | ||||
|  */ | ||||
| static void zynqmp_disp_layer_enable(struct zynqmp_disp_layer *layer) | ||||
| void zynqmp_disp_layer_enable(struct zynqmp_disp_layer *layer, | ||||
| 			      enum zynqmp_dpsub_layer_mode mode) | ||||
| { | ||||
| 	zynqmp_disp_avbuf_enable_video(layer->disp, layer, | ||||
| 				       ZYNQMP_DISP_LAYER_NONLIVE); | ||||
| 	layer->mode = mode; | ||||
| 	zynqmp_disp_avbuf_enable_video(layer->disp, layer); | ||||
| 	zynqmp_disp_blend_layer_enable(layer->disp, layer); | ||||
| 
 | ||||
| 	layer->mode = ZYNQMP_DISP_LAYER_NONLIVE; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  | @ -1028,12 +922,14 @@ static void zynqmp_disp_layer_enable(struct zynqmp_disp_layer *layer) | |||
|  * Disable the layer by stopping its DMA channels and disabling it in the | ||||
|  * audio/video buffer manager and the blender. | ||||
|  */ | ||||
| static void zynqmp_disp_layer_disable(struct zynqmp_disp_layer *layer) | ||||
| void zynqmp_disp_layer_disable(struct zynqmp_disp_layer *layer) | ||||
| { | ||||
| 	unsigned int i; | ||||
| 
 | ||||
| 	for (i = 0; i < layer->drm_fmt->num_planes; i++) | ||||
| 		dmaengine_terminate_sync(layer->dmas[i].chan); | ||||
| 	if (layer->disp->dpsub->dma_enabled) { | ||||
| 		for (i = 0; i < layer->drm_fmt->num_planes; i++) | ||||
| 			dmaengine_terminate_sync(layer->dmas[i].chan); | ||||
| 	} | ||||
| 
 | ||||
| 	zynqmp_disp_avbuf_disable_video(layer->disp, layer); | ||||
| 	zynqmp_disp_blend_layer_disable(layer->disp, layer); | ||||
|  | @ -1042,15 +938,13 @@ static void zynqmp_disp_layer_disable(struct zynqmp_disp_layer *layer) | |||
| /**
 | ||||
|  * zynqmp_disp_layer_set_format - Set the layer format | ||||
|  * @layer: The layer | ||||
|  * @state: The plane state | ||||
|  * @info: The format info | ||||
|  * | ||||
|  * Set the format for @layer based on @state->fb->format. The layer must be | ||||
|  * disabled. | ||||
|  * Set the format for @layer to @info. The layer must be disabled. | ||||
|  */ | ||||
| static void zynqmp_disp_layer_set_format(struct zynqmp_disp_layer *layer, | ||||
| 					 struct drm_plane_state *state) | ||||
| void zynqmp_disp_layer_set_format(struct zynqmp_disp_layer *layer, | ||||
| 				  const struct drm_format_info *info) | ||||
| { | ||||
| 	const struct drm_format_info *info = state->fb->format; | ||||
| 	unsigned int i; | ||||
| 
 | ||||
| 	layer->disp_fmt = zynqmp_disp_layer_find_format(layer, info->format); | ||||
|  | @ -1058,6 +952,9 @@ static void zynqmp_disp_layer_set_format(struct zynqmp_disp_layer *layer, | |||
| 
 | ||||
| 	zynqmp_disp_avbuf_set_format(layer->disp, layer, layer->disp_fmt); | ||||
| 
 | ||||
| 	if (!layer->disp->dpsub->dma_enabled) | ||||
| 		return; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Set pconfig for each DMA channel to indicate they're part of a | ||||
| 	 * video group. | ||||
|  | @ -1087,13 +984,16 @@ static void zynqmp_disp_layer_set_format(struct zynqmp_disp_layer *layer, | |||
|  * | ||||
|  * Return: 0 on success, or the DMA descriptor failure error otherwise | ||||
|  */ | ||||
| static int zynqmp_disp_layer_update(struct zynqmp_disp_layer *layer, | ||||
| 				    struct drm_plane_state *state) | ||||
| int zynqmp_disp_layer_update(struct zynqmp_disp_layer *layer, | ||||
| 			     struct drm_plane_state *state) | ||||
| { | ||||
| 	const struct drm_format_info *info = layer->drm_fmt; | ||||
| 	unsigned int i; | ||||
| 
 | ||||
| 	for (i = 0; i < layer->drm_fmt->num_planes; i++) { | ||||
| 	if (!layer->disp->dpsub->dma_enabled) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	for (i = 0; i < info->num_planes; i++) { | ||||
| 		unsigned int width = state->crtc_w / (i ? info->hsub : 1); | ||||
| 		unsigned int height = state->crtc_h / (i ? info->vsub : 1); | ||||
| 		struct zynqmp_disp_layer_dma *dma = &layer->dmas[i]; | ||||
|  | @ -1128,143 +1028,6 @@ static int zynqmp_disp_layer_update(struct zynqmp_disp_layer *layer, | |||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static inline struct zynqmp_disp_layer *plane_to_layer(struct drm_plane *plane) | ||||
| { | ||||
| 	return container_of(plane, struct zynqmp_disp_layer, plane); | ||||
| } | ||||
| 
 | ||||
| static int | ||||
| zynqmp_disp_plane_atomic_check(struct drm_plane *plane, | ||||
| 			       struct drm_atomic_state *state) | ||||
| { | ||||
| 	struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, | ||||
| 										 plane); | ||||
| 	struct drm_crtc_state *crtc_state; | ||||
| 
 | ||||
| 	if (!new_plane_state->crtc) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	crtc_state = drm_atomic_get_crtc_state(state, new_plane_state->crtc); | ||||
| 	if (IS_ERR(crtc_state)) | ||||
| 		return PTR_ERR(crtc_state); | ||||
| 
 | ||||
| 	return drm_atomic_helper_check_plane_state(new_plane_state, | ||||
| 						   crtc_state, | ||||
| 						   DRM_PLANE_NO_SCALING, | ||||
| 						   DRM_PLANE_NO_SCALING, | ||||
| 						   false, false); | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| zynqmp_disp_plane_atomic_disable(struct drm_plane *plane, | ||||
| 				 struct drm_atomic_state *state) | ||||
| { | ||||
| 	struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state, | ||||
| 									   plane); | ||||
| 	struct zynqmp_disp_layer *layer = plane_to_layer(plane); | ||||
| 
 | ||||
| 	if (!old_state->fb) | ||||
| 		return; | ||||
| 
 | ||||
| 	zynqmp_disp_layer_disable(layer); | ||||
| 
 | ||||
| 	if (zynqmp_disp_layer_is_gfx(layer)) | ||||
| 		zynqmp_disp_blend_set_global_alpha(layer->disp, false, | ||||
| 						   plane->state->alpha >> 8); | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| zynqmp_disp_plane_atomic_update(struct drm_plane *plane, | ||||
| 				struct drm_atomic_state *state) | ||||
| { | ||||
| 	struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state, plane); | ||||
| 	struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, plane); | ||||
| 	struct zynqmp_disp_layer *layer = plane_to_layer(plane); | ||||
| 	bool format_changed = false; | ||||
| 
 | ||||
| 	if (!old_state->fb || | ||||
| 	    old_state->fb->format->format != new_state->fb->format->format) | ||||
| 		format_changed = true; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * If the format has changed (including going from a previously | ||||
| 	 * disabled state to any format), reconfigure the format. Disable the | ||||
| 	 * plane first if needed. | ||||
| 	 */ | ||||
| 	if (format_changed) { | ||||
| 		if (old_state->fb) | ||||
| 			zynqmp_disp_layer_disable(layer); | ||||
| 
 | ||||
| 		zynqmp_disp_layer_set_format(layer, new_state); | ||||
| 	} | ||||
| 
 | ||||
| 	zynqmp_disp_layer_update(layer, new_state); | ||||
| 
 | ||||
| 	if (zynqmp_disp_layer_is_gfx(layer)) | ||||
| 		zynqmp_disp_blend_set_global_alpha(layer->disp, true, | ||||
| 						   plane->state->alpha >> 8); | ||||
| 
 | ||||
| 	/* Enable or re-enable the plane is the format has changed. */ | ||||
| 	if (format_changed) | ||||
| 		zynqmp_disp_layer_enable(layer); | ||||
| } | ||||
| 
 | ||||
| static const struct drm_plane_helper_funcs zynqmp_disp_plane_helper_funcs = { | ||||
| 	.atomic_check		= zynqmp_disp_plane_atomic_check, | ||||
| 	.atomic_update		= zynqmp_disp_plane_atomic_update, | ||||
| 	.atomic_disable		= zynqmp_disp_plane_atomic_disable, | ||||
| }; | ||||
| 
 | ||||
| static const struct drm_plane_funcs zynqmp_disp_plane_funcs = { | ||||
| 	.update_plane		= drm_atomic_helper_update_plane, | ||||
| 	.disable_plane		= drm_atomic_helper_disable_plane, | ||||
| 	.destroy		= drm_plane_cleanup, | ||||
| 	.reset			= drm_atomic_helper_plane_reset, | ||||
| 	.atomic_duplicate_state	= drm_atomic_helper_plane_duplicate_state, | ||||
| 	.atomic_destroy_state	= drm_atomic_helper_plane_destroy_state, | ||||
| }; | ||||
| 
 | ||||
| static int zynqmp_disp_create_planes(struct zynqmp_disp *disp) | ||||
| { | ||||
| 	unsigned int i, j; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	for (i = 0; i < ZYNQMP_DISP_NUM_LAYERS; i++) { | ||||
| 		struct zynqmp_disp_layer *layer = &disp->layers[i]; | ||||
| 		enum drm_plane_type type; | ||||
| 		u32 *drm_formats; | ||||
| 
 | ||||
| 		drm_formats = drmm_kcalloc(disp->drm, sizeof(*drm_formats), | ||||
| 					   layer->info->num_formats, | ||||
| 					   GFP_KERNEL); | ||||
| 		if (!drm_formats) | ||||
| 			return -ENOMEM; | ||||
| 
 | ||||
| 		for (j = 0; j < layer->info->num_formats; ++j) | ||||
| 			drm_formats[j] = layer->info->formats[j].drm_fmt; | ||||
| 
 | ||||
| 		/* Graphics layer is primary, and video layer is overlay. */ | ||||
| 		type = zynqmp_disp_layer_is_video(layer) | ||||
| 		     ? DRM_PLANE_TYPE_OVERLAY : DRM_PLANE_TYPE_PRIMARY; | ||||
| 		ret = drm_universal_plane_init(disp->drm, &layer->plane, 0, | ||||
| 					       &zynqmp_disp_plane_funcs, | ||||
| 					       drm_formats, | ||||
| 					       layer->info->num_formats, | ||||
| 					       NULL, type, NULL); | ||||
| 		if (ret) | ||||
| 			return ret; | ||||
| 
 | ||||
| 		drm_plane_helper_add(&layer->plane, | ||||
| 				     &zynqmp_disp_plane_helper_funcs); | ||||
| 
 | ||||
| 		drm_plane_create_zpos_immutable_property(&layer->plane, i); | ||||
| 		if (zynqmp_disp_layer_is_gfx(layer)) | ||||
| 			drm_plane_create_alpha_property(&layer->plane); | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * zynqmp_disp_layer_release_dma - Release DMA channels for a layer | ||||
|  * @disp: Display controller | ||||
|  | @ -1277,7 +1040,7 @@ static void zynqmp_disp_layer_release_dma(struct zynqmp_disp *disp, | |||
| { | ||||
| 	unsigned int i; | ||||
| 
 | ||||
| 	if (!layer->info) | ||||
| 	if (!layer->info || !disp->dpsub->dma_enabled) | ||||
| 		return; | ||||
| 
 | ||||
| 	for (i = 0; i < layer->info->num_channels; i++) { | ||||
|  | @ -1300,7 +1063,7 @@ static void zynqmp_disp_destroy_layers(struct zynqmp_disp *disp) | |||
| { | ||||
| 	unsigned int i; | ||||
| 
 | ||||
| 	for (i = 0; i < ZYNQMP_DISP_NUM_LAYERS; i++) | ||||
| 	for (i = 0; i < ARRAY_SIZE(disp->layers); i++) | ||||
| 		zynqmp_disp_layer_release_dma(disp, &disp->layers[i]); | ||||
| } | ||||
| 
 | ||||
|  | @ -1320,6 +1083,9 @@ static int zynqmp_disp_layer_request_dma(struct zynqmp_disp *disp, | |||
| 	unsigned int i; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	if (!disp->dpsub->dma_enabled) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	for (i = 0; i < layer->info->num_channels; i++) { | ||||
| 		struct zynqmp_disp_layer_dma *dma = &layer->dmas[i]; | ||||
| 		char dma_channel_name[16]; | ||||
|  | @ -1347,12 +1113,12 @@ static int zynqmp_disp_layer_request_dma(struct zynqmp_disp *disp, | |||
| static int zynqmp_disp_create_layers(struct zynqmp_disp *disp) | ||||
| { | ||||
| 	static const struct zynqmp_disp_layer_info layer_info[] = { | ||||
| 		[ZYNQMP_DISP_LAYER_VID] = { | ||||
| 		[ZYNQMP_DPSUB_LAYER_VID] = { | ||||
| 			.formats = avbuf_vid_fmts, | ||||
| 			.num_formats = ARRAY_SIZE(avbuf_vid_fmts), | ||||
| 			.num_channels = 3, | ||||
| 		}, | ||||
| 		[ZYNQMP_DISP_LAYER_GFX] = { | ||||
| 		[ZYNQMP_DPSUB_LAYER_GFX] = { | ||||
| 			.formats = avbuf_gfx_fmts, | ||||
| 			.num_formats = ARRAY_SIZE(avbuf_gfx_fmts), | ||||
| 			.num_channels = 1, | ||||
|  | @ -1362,7 +1128,7 @@ static int zynqmp_disp_create_layers(struct zynqmp_disp *disp) | |||
| 	unsigned int i; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	for (i = 0; i < ZYNQMP_DISP_NUM_LAYERS; i++) { | ||||
| 	for (i = 0; i < ARRAY_SIZE(disp->layers); i++) { | ||||
| 		struct zynqmp_disp_layer *layer = &disp->layers[i]; | ||||
| 
 | ||||
| 		layer->id = i; | ||||
|  | @ -1372,6 +1138,8 @@ static int zynqmp_disp_create_layers(struct zynqmp_disp *disp) | |||
| 		ret = zynqmp_disp_layer_request_dma(disp, layer); | ||||
| 		if (ret) | ||||
| 			goto err; | ||||
| 
 | ||||
| 		disp->dpsub->layers[i] = layer; | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
|  | @ -1382,19 +1150,23 @@ err: | |||
| } | ||||
| 
 | ||||
| /* -----------------------------------------------------------------------------
 | ||||
|  * ZynqMP Display & DRM CRTC | ||||
|  * ZynqMP Display | ||||
|  */ | ||||
| 
 | ||||
| /**
 | ||||
|  * zynqmp_disp_enable - Enable the display controller | ||||
|  * @disp: Display controller | ||||
|  */ | ||||
| static void zynqmp_disp_enable(struct zynqmp_disp *disp) | ||||
| void zynqmp_disp_enable(struct zynqmp_disp *disp) | ||||
| { | ||||
| 	zynqmp_disp_blend_set_output_format(disp, ZYNQMP_DPSUB_FORMAT_RGB); | ||||
| 	zynqmp_disp_blend_set_bg_color(disp, 0, 0, 0); | ||||
| 
 | ||||
| 	zynqmp_disp_avbuf_enable(disp); | ||||
| 	/* Choose clock source based on the DT clock handle. */ | ||||
| 	zynqmp_disp_avbuf_set_clocks_sources(disp, disp->pclk_from_ps, | ||||
| 					     disp->audio.clk_from_ps, true); | ||||
| 	zynqmp_disp_avbuf_set_clocks_sources(disp, disp->dpsub->vid_clk_from_ps, | ||||
| 					     disp->dpsub->aud_clk_from_ps, | ||||
| 					     true); | ||||
| 	zynqmp_disp_avbuf_enable_channels(disp); | ||||
| 	zynqmp_disp_avbuf_enable_audio(disp); | ||||
| 
 | ||||
|  | @ -1405,7 +1177,7 @@ static void zynqmp_disp_enable(struct zynqmp_disp *disp) | |||
|  * zynqmp_disp_disable - Disable the display controller | ||||
|  * @disp: Display controller | ||||
|  */ | ||||
| static void zynqmp_disp_disable(struct zynqmp_disp *disp) | ||||
| void zynqmp_disp_disable(struct zynqmp_disp *disp) | ||||
| { | ||||
| 	zynqmp_disp_audio_disable(disp); | ||||
| 
 | ||||
|  | @ -1414,27 +1186,27 @@ static void zynqmp_disp_disable(struct zynqmp_disp *disp) | |||
| 	zynqmp_disp_avbuf_disable(disp); | ||||
| } | ||||
| 
 | ||||
| static inline struct zynqmp_disp *crtc_to_disp(struct drm_crtc *crtc) | ||||
| /**
 | ||||
|  * zynqmp_disp_setup_clock - Configure the display controller pixel clock rate | ||||
|  * @disp: Display controller | ||||
|  * @mode_clock: The pixel clock rate, in Hz | ||||
|  * | ||||
|  * Return: 0 on success, or a negative error clock otherwise | ||||
|  */ | ||||
| int zynqmp_disp_setup_clock(struct zynqmp_disp *disp, | ||||
| 			    unsigned long mode_clock) | ||||
| { | ||||
| 	return container_of(crtc, struct zynqmp_disp, crtc); | ||||
| } | ||||
| 
 | ||||
| static int zynqmp_disp_crtc_setup_clock(struct drm_crtc *crtc, | ||||
| 					struct drm_display_mode *adjusted_mode) | ||||
| { | ||||
| 	struct zynqmp_disp *disp = crtc_to_disp(crtc); | ||||
| 	unsigned long mode_clock = adjusted_mode->clock * 1000; | ||||
| 	unsigned long rate; | ||||
| 	long diff; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	ret = clk_set_rate(disp->pclk, mode_clock); | ||||
| 	ret = clk_set_rate(disp->dpsub->vid_clk, mode_clock); | ||||
| 	if (ret) { | ||||
| 		dev_err(disp->dev, "failed to set a pixel clock\n"); | ||||
| 		dev_err(disp->dev, "failed to set the video clock\n"); | ||||
| 		return ret; | ||||
| 	} | ||||
| 
 | ||||
| 	rate = clk_get_rate(disp->pclk); | ||||
| 	rate = clk_get_rate(disp->dpsub->vid_clk); | ||||
| 	diff = rate - mode_clock; | ||||
| 	if (abs(diff) > mode_clock / 20) | ||||
| 		dev_info(disp->dev, | ||||
|  | @ -1448,245 +1220,63 @@ static int zynqmp_disp_crtc_setup_clock(struct drm_crtc *crtc, | |||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| zynqmp_disp_crtc_atomic_enable(struct drm_crtc *crtc, | ||||
| 			       struct drm_atomic_state *state) | ||||
| { | ||||
| 	struct zynqmp_disp *disp = crtc_to_disp(crtc); | ||||
| 	struct drm_display_mode *adjusted_mode = &crtc->state->adjusted_mode; | ||||
| 	int ret, vrefresh; | ||||
| 
 | ||||
| 	pm_runtime_get_sync(disp->dev); | ||||
| 
 | ||||
| 	zynqmp_disp_crtc_setup_clock(crtc, adjusted_mode); | ||||
| 
 | ||||
| 	ret = clk_prepare_enable(disp->pclk); | ||||
| 	if (ret) { | ||||
| 		dev_err(disp->dev, "failed to enable a pixel clock\n"); | ||||
| 		pm_runtime_put_sync(disp->dev); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	zynqmp_disp_blend_set_output_format(disp, ZYNQMP_DPSUB_FORMAT_RGB); | ||||
| 	zynqmp_disp_blend_set_bg_color(disp, 0, 0, 0); | ||||
| 
 | ||||
| 	zynqmp_disp_enable(disp); | ||||
| 
 | ||||
| 	/* Delay of 3 vblank intervals for timing gen to be stable */ | ||||
| 	vrefresh = (adjusted_mode->clock * 1000) / | ||||
| 		   (adjusted_mode->vtotal * adjusted_mode->htotal); | ||||
| 	msleep(3 * 1000 / vrefresh); | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| zynqmp_disp_crtc_atomic_disable(struct drm_crtc *crtc, | ||||
| 				struct drm_atomic_state *state) | ||||
| { | ||||
| 	struct zynqmp_disp *disp = crtc_to_disp(crtc); | ||||
| 	struct drm_plane_state *old_plane_state; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Disable the plane if active. The old plane state can be NULL in the | ||||
| 	 * .shutdown() path if the plane is already disabled, skip | ||||
| 	 * zynqmp_disp_plane_atomic_disable() in that case. | ||||
| 	 */ | ||||
| 	old_plane_state = drm_atomic_get_old_plane_state(state, crtc->primary); | ||||
| 	if (old_plane_state) | ||||
| 		zynqmp_disp_plane_atomic_disable(crtc->primary, state); | ||||
| 
 | ||||
| 	zynqmp_disp_disable(disp); | ||||
| 
 | ||||
| 	drm_crtc_vblank_off(&disp->crtc); | ||||
| 
 | ||||
| 	spin_lock_irq(&crtc->dev->event_lock); | ||||
| 	if (crtc->state->event) { | ||||
| 		drm_crtc_send_vblank_event(crtc, crtc->state->event); | ||||
| 		crtc->state->event = NULL; | ||||
| 	} | ||||
| 	spin_unlock_irq(&crtc->dev->event_lock); | ||||
| 
 | ||||
| 	clk_disable_unprepare(disp->pclk); | ||||
| 	pm_runtime_put_sync(disp->dev); | ||||
| } | ||||
| 
 | ||||
| static int zynqmp_disp_crtc_atomic_check(struct drm_crtc *crtc, | ||||
| 					 struct drm_atomic_state *state) | ||||
| { | ||||
| 	return drm_atomic_add_affected_planes(state, crtc); | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| zynqmp_disp_crtc_atomic_begin(struct drm_crtc *crtc, | ||||
| 			      struct drm_atomic_state *state) | ||||
| { | ||||
| 	drm_crtc_vblank_on(crtc); | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| zynqmp_disp_crtc_atomic_flush(struct drm_crtc *crtc, | ||||
| 			      struct drm_atomic_state *state) | ||||
| { | ||||
| 	if (crtc->state->event) { | ||||
| 		struct drm_pending_vblank_event *event; | ||||
| 
 | ||||
| 		/* Consume the flip_done event from atomic helper. */ | ||||
| 		event = crtc->state->event; | ||||
| 		crtc->state->event = NULL; | ||||
| 
 | ||||
| 		event->pipe = drm_crtc_index(crtc); | ||||
| 
 | ||||
| 		WARN_ON(drm_crtc_vblank_get(crtc) != 0); | ||||
| 
 | ||||
| 		spin_lock_irq(&crtc->dev->event_lock); | ||||
| 		drm_crtc_arm_vblank_event(crtc, event); | ||||
| 		spin_unlock_irq(&crtc->dev->event_lock); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static const struct drm_crtc_helper_funcs zynqmp_disp_crtc_helper_funcs = { | ||||
| 	.atomic_enable	= zynqmp_disp_crtc_atomic_enable, | ||||
| 	.atomic_disable	= zynqmp_disp_crtc_atomic_disable, | ||||
| 	.atomic_check	= zynqmp_disp_crtc_atomic_check, | ||||
| 	.atomic_begin	= zynqmp_disp_crtc_atomic_begin, | ||||
| 	.atomic_flush	= zynqmp_disp_crtc_atomic_flush, | ||||
| }; | ||||
| 
 | ||||
| static int zynqmp_disp_crtc_enable_vblank(struct drm_crtc *crtc) | ||||
| { | ||||
| 	struct zynqmp_disp *disp = crtc_to_disp(crtc); | ||||
| 
 | ||||
| 	zynqmp_dp_enable_vblank(disp->dpsub->dp); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static void zynqmp_disp_crtc_disable_vblank(struct drm_crtc *crtc) | ||||
| { | ||||
| 	struct zynqmp_disp *disp = crtc_to_disp(crtc); | ||||
| 
 | ||||
| 	zynqmp_dp_disable_vblank(disp->dpsub->dp); | ||||
| } | ||||
| 
 | ||||
| static const struct drm_crtc_funcs zynqmp_disp_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, | ||||
| 	.enable_vblank		= zynqmp_disp_crtc_enable_vblank, | ||||
| 	.disable_vblank		= zynqmp_disp_crtc_disable_vblank, | ||||
| }; | ||||
| 
 | ||||
| static int zynqmp_disp_create_crtc(struct zynqmp_disp *disp) | ||||
| { | ||||
| 	struct drm_plane *plane = &disp->layers[ZYNQMP_DISP_LAYER_GFX].plane; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	ret = drm_crtc_init_with_planes(disp->drm, &disp->crtc, plane, | ||||
| 					NULL, &zynqmp_disp_crtc_funcs, NULL); | ||||
| 	if (ret < 0) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	drm_crtc_helper_add(&disp->crtc, &zynqmp_disp_crtc_helper_funcs); | ||||
| 
 | ||||
| 	/* Start with vertical blanking interrupt reporting disabled. */ | ||||
| 	drm_crtc_vblank_off(&disp->crtc); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static void zynqmp_disp_map_crtc_to_plane(struct zynqmp_disp *disp) | ||||
| { | ||||
| 	u32 possible_crtcs = drm_crtc_mask(&disp->crtc); | ||||
| 	unsigned int i; | ||||
| 
 | ||||
| 	for (i = 0; i < ZYNQMP_DISP_NUM_LAYERS; i++) | ||||
| 		disp->layers[i].plane.possible_crtcs = possible_crtcs; | ||||
| } | ||||
| 
 | ||||
| /* -----------------------------------------------------------------------------
 | ||||
|  * Initialization & Cleanup | ||||
|  */ | ||||
| 
 | ||||
| int zynqmp_disp_drm_init(struct zynqmp_dpsub *dpsub) | ||||
| { | ||||
| 	struct zynqmp_disp *disp = dpsub->disp; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	ret = zynqmp_disp_create_planes(disp); | ||||
| 	if (ret) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	ret = zynqmp_disp_create_crtc(disp); | ||||
| 	if (ret < 0) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	zynqmp_disp_map_crtc_to_plane(disp); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| int zynqmp_disp_probe(struct zynqmp_dpsub *dpsub, struct drm_device *drm) | ||||
| int zynqmp_disp_probe(struct zynqmp_dpsub *dpsub) | ||||
| { | ||||
| 	struct platform_device *pdev = to_platform_device(dpsub->dev); | ||||
| 	struct zynqmp_disp *disp; | ||||
| 	struct zynqmp_disp_layer *layer; | ||||
| 	struct resource *res; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	disp = drmm_kzalloc(drm, sizeof(*disp), GFP_KERNEL); | ||||
| 	disp = kzalloc(sizeof(*disp), GFP_KERNEL); | ||||
| 	if (!disp) | ||||
| 		return -ENOMEM; | ||||
| 
 | ||||
| 	disp->dev = &pdev->dev; | ||||
| 	disp->dpsub = dpsub; | ||||
| 	disp->drm = drm; | ||||
| 
 | ||||
| 	dpsub->disp = disp; | ||||
| 
 | ||||
| 	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "blend"); | ||||
| 	disp->blend.base = devm_ioremap_resource(disp->dev, res); | ||||
| 	if (IS_ERR(disp->blend.base)) | ||||
| 		return PTR_ERR(disp->blend.base); | ||||
| 	if (IS_ERR(disp->blend.base)) { | ||||
| 		ret = PTR_ERR(disp->blend.base); | ||||
| 		goto error; | ||||
| 	} | ||||
| 
 | ||||
| 	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "av_buf"); | ||||
| 	disp->avbuf.base = devm_ioremap_resource(disp->dev, res); | ||||
| 	if (IS_ERR(disp->avbuf.base)) | ||||
| 		return PTR_ERR(disp->avbuf.base); | ||||
| 	if (IS_ERR(disp->avbuf.base)) { | ||||
| 		ret = PTR_ERR(disp->avbuf.base); | ||||
| 		goto error; | ||||
| 	} | ||||
| 
 | ||||
| 	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "aud"); | ||||
| 	disp->audio.base = devm_ioremap_resource(disp->dev, res); | ||||
| 	if (IS_ERR(disp->audio.base)) | ||||
| 		return PTR_ERR(disp->audio.base); | ||||
| 
 | ||||
| 	/* Try the live PL video clock */ | ||||
| 	disp->pclk = devm_clk_get(disp->dev, "dp_live_video_in_clk"); | ||||
| 	if (!IS_ERR(disp->pclk)) | ||||
| 		disp->pclk_from_ps = false; | ||||
| 	else if (PTR_ERR(disp->pclk) == -EPROBE_DEFER) | ||||
| 		return PTR_ERR(disp->pclk); | ||||
| 
 | ||||
| 	/* If the live PL video clock is not valid, fall back to PS clock */ | ||||
| 	if (IS_ERR_OR_NULL(disp->pclk)) { | ||||
| 		disp->pclk = devm_clk_get(disp->dev, "dp_vtc_pixel_clk_in"); | ||||
| 		if (IS_ERR(disp->pclk)) { | ||||
| 			dev_err(disp->dev, "failed to init any video clock\n"); | ||||
| 			return PTR_ERR(disp->pclk); | ||||
| 		} | ||||
| 		disp->pclk_from_ps = true; | ||||
| 	if (IS_ERR(disp->audio.base)) { | ||||
| 		ret = PTR_ERR(disp->audio.base); | ||||
| 		goto error; | ||||
| 	} | ||||
| 
 | ||||
| 	zynqmp_disp_audio_init(disp); | ||||
| 
 | ||||
| 	ret = zynqmp_disp_create_layers(disp); | ||||
| 	if (ret) | ||||
| 		return ret; | ||||
| 		goto error; | ||||
| 
 | ||||
| 	layer = &disp->layers[ZYNQMP_DISP_LAYER_VID]; | ||||
| 	dpsub->dma_align = 1 << layer->dmas[0].chan->device->copy_align; | ||||
| 	if (disp->dpsub->dma_enabled) { | ||||
| 		struct zynqmp_disp_layer *layer; | ||||
| 
 | ||||
| 		layer = &disp->layers[ZYNQMP_DPSUB_LAYER_VID]; | ||||
| 		dpsub->dma_align = 1 << layer->dmas[0].chan->device->copy_align; | ||||
| 	} | ||||
| 
 | ||||
| 	dpsub->disp = disp; | ||||
| 
 | ||||
| 	return 0; | ||||
| 
 | ||||
| error: | ||||
| 	kfree(disp); | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| void zynqmp_disp_remove(struct zynqmp_dpsub *dpsub) | ||||
|  |  | |||
|  | @ -25,18 +25,52 @@ | |||
| #define ZYNQMP_DISP_MAX_DMA_BIT				44 | ||||
| 
 | ||||
| struct device; | ||||
| struct drm_device; | ||||
| struct drm_format_info; | ||||
| struct drm_plane_state; | ||||
| struct platform_device; | ||||
| struct zynqmp_disp; | ||||
| struct zynqmp_disp_layer; | ||||
| struct zynqmp_dpsub; | ||||
| 
 | ||||
| void zynqmp_disp_handle_vblank(struct zynqmp_disp *disp); | ||||
| bool zynqmp_disp_audio_enabled(struct zynqmp_disp *disp); | ||||
| unsigned int zynqmp_disp_get_audio_clk_rate(struct zynqmp_disp *disp); | ||||
| uint32_t zynqmp_disp_get_crtc_mask(struct zynqmp_disp *disp); | ||||
| /**
 | ||||
|  * enum zynqmp_dpsub_layer_id - Layer identifier | ||||
|  * @ZYNQMP_DPSUB_LAYER_VID: Video layer | ||||
|  * @ZYNQMP_DPSUB_LAYER_GFX: Graphics layer | ||||
|  */ | ||||
| enum zynqmp_dpsub_layer_id { | ||||
| 	ZYNQMP_DPSUB_LAYER_VID, | ||||
| 	ZYNQMP_DPSUB_LAYER_GFX, | ||||
| }; | ||||
| 
 | ||||
| int zynqmp_disp_drm_init(struct zynqmp_dpsub *dpsub); | ||||
| int zynqmp_disp_probe(struct zynqmp_dpsub *dpsub, struct drm_device *drm); | ||||
| /**
 | ||||
|  * enum zynqmp_dpsub_layer_mode - Layer mode | ||||
|  * @ZYNQMP_DPSUB_LAYER_NONLIVE: non-live (memory) mode | ||||
|  * @ZYNQMP_DPSUB_LAYER_LIVE: live (stream) mode | ||||
|  */ | ||||
| enum zynqmp_dpsub_layer_mode { | ||||
| 	ZYNQMP_DPSUB_LAYER_NONLIVE, | ||||
| 	ZYNQMP_DPSUB_LAYER_LIVE, | ||||
| }; | ||||
| 
 | ||||
| void zynqmp_disp_enable(struct zynqmp_disp *disp); | ||||
| void zynqmp_disp_disable(struct zynqmp_disp *disp); | ||||
| int zynqmp_disp_setup_clock(struct zynqmp_disp *disp, | ||||
| 			    unsigned long mode_clock); | ||||
| 
 | ||||
| void zynqmp_disp_blend_set_global_alpha(struct zynqmp_disp *disp, | ||||
| 					bool enable, u32 alpha); | ||||
| 
 | ||||
| u32 *zynqmp_disp_layer_drm_formats(struct zynqmp_disp_layer *layer, | ||||
| 				   unsigned int *num_formats); | ||||
| void zynqmp_disp_layer_enable(struct zynqmp_disp_layer *layer, | ||||
| 			      enum zynqmp_dpsub_layer_mode mode); | ||||
| void zynqmp_disp_layer_disable(struct zynqmp_disp_layer *layer); | ||||
| void zynqmp_disp_layer_set_format(struct zynqmp_disp_layer *layer, | ||||
| 				  const struct drm_format_info *info); | ||||
| int zynqmp_disp_layer_update(struct zynqmp_disp_layer *layer, | ||||
| 			     struct drm_plane_state *state); | ||||
| 
 | ||||
| int zynqmp_disp_probe(struct zynqmp_dpsub *dpsub); | ||||
| void zynqmp_disp_remove(struct zynqmp_dpsub *dpsub); | ||||
| 
 | ||||
| #endif /* _ZYNQMP_DISP_H_ */ | ||||
|  |  | |||
|  | @ -11,16 +11,12 @@ | |||
| 
 | ||||
| #include <drm/display/drm_dp_helper.h> | ||||
| #include <drm/drm_atomic_helper.h> | ||||
| #include <drm/drm_connector.h> | ||||
| #include <drm/drm_crtc.h> | ||||
| #include <drm/drm_device.h> | ||||
| #include <drm/drm_edid.h> | ||||
| #include <drm/drm_encoder.h> | ||||
| #include <drm/drm_managed.h> | ||||
| #include <drm/drm_fourcc.h> | ||||
| #include <drm/drm_modes.h> | ||||
| #include <drm/drm_of.h> | ||||
| #include <drm/drm_probe_helper.h> | ||||
| #include <drm/drm_simple_kms_helper.h> | ||||
| 
 | ||||
| #include <linux/clk.h> | ||||
| #include <linux/delay.h> | ||||
|  | @ -31,10 +27,12 @@ | |||
| #include <linux/pm_runtime.h> | ||||
| #include <linux/phy/phy.h> | ||||
| #include <linux/reset.h> | ||||
| #include <linux/slab.h> | ||||
| 
 | ||||
| #include "zynqmp_disp.h" | ||||
| #include "zynqmp_dp.h" | ||||
| #include "zynqmp_dpsub.h" | ||||
| #include "zynqmp_kms.h" | ||||
| 
 | ||||
| static uint zynqmp_dp_aux_timeout_ms = 50; | ||||
| module_param_named(aux_timeout_ms, zynqmp_dp_aux_timeout_ms, uint, 0444); | ||||
|  | @ -277,14 +275,13 @@ struct zynqmp_dp_config { | |||
| 
 | ||||
| /**
 | ||||
|  * struct zynqmp_dp - Xilinx DisplayPort core | ||||
|  * @encoder: the drm encoder structure | ||||
|  * @connector: the drm connector structure | ||||
|  * @dev: device structure | ||||
|  * @dpsub: Display subsystem | ||||
|  * @drm: DRM core | ||||
|  * @iomem: device I/O memory for register access | ||||
|  * @reset: reset controller | ||||
|  * @irq: irq | ||||
|  * @bridge: DRM bridge for the DP encoder | ||||
|  * @next_bridge: The downstream bridge | ||||
|  * @config: IP core configuration from DTS | ||||
|  * @aux: aux channel | ||||
|  * @phy: PHY handles for DP lanes | ||||
|  | @ -298,15 +295,15 @@ struct zynqmp_dp_config { | |||
|  * @train_set: set of training data | ||||
|  */ | ||||
| struct zynqmp_dp { | ||||
| 	struct drm_encoder encoder; | ||||
| 	struct drm_connector connector; | ||||
| 	struct device *dev; | ||||
| 	struct zynqmp_dpsub *dpsub; | ||||
| 	struct drm_device *drm; | ||||
| 	void __iomem *iomem; | ||||
| 	struct reset_control *reset; | ||||
| 	int irq; | ||||
| 
 | ||||
| 	struct drm_bridge bridge; | ||||
| 	struct drm_bridge *next_bridge; | ||||
| 
 | ||||
| 	struct zynqmp_dp_config config; | ||||
| 	struct drm_dp_aux aux; | ||||
| 	struct phy *phy[ZYNQMP_DP_MAX_LANES]; | ||||
|  | @ -321,14 +318,9 @@ struct zynqmp_dp { | |||
| 	u8 train_set[ZYNQMP_DP_MAX_LANES]; | ||||
| }; | ||||
| 
 | ||||
| static inline struct zynqmp_dp *encoder_to_dp(struct drm_encoder *encoder) | ||||
| static inline struct zynqmp_dp *bridge_to_dp(struct drm_bridge *bridge) | ||||
| { | ||||
| 	return container_of(encoder, struct zynqmp_dp, encoder); | ||||
| } | ||||
| 
 | ||||
| static inline struct zynqmp_dp *connector_to_dp(struct drm_connector *connector) | ||||
| { | ||||
| 	return container_of(connector, struct zynqmp_dp, connector); | ||||
| 	return container_of(bridge, struct zynqmp_dp, bridge); | ||||
| } | ||||
| 
 | ||||
| static void zynqmp_dp_write(struct zynqmp_dp *dp, int offset, u32 val) | ||||
|  | @ -1064,7 +1056,7 @@ static int zynqmp_dp_aux_init(struct zynqmp_dp *dp) | |||
| 
 | ||||
| 	dp->aux.name = "ZynqMP DP AUX"; | ||||
| 	dp->aux.dev = dp->dev; | ||||
| 	dp->aux.drm_dev = dp->drm; | ||||
| 	dp->aux.drm_dev = dp->bridge.dev; | ||||
| 	dp->aux.transfer = zynqmp_dp_aux_transfer; | ||||
| 
 | ||||
| 	return drm_dp_aux_register(&dp->aux); | ||||
|  | @ -1101,6 +1093,7 @@ static void zynqmp_dp_update_misc(struct zynqmp_dp *dp) | |||
| /**
 | ||||
|  * zynqmp_dp_set_format - Set the input format | ||||
|  * @dp: DisplayPort IP core structure | ||||
|  * @info: Display info | ||||
|  * @format: input format | ||||
|  * @bpc: bits per component | ||||
|  * | ||||
|  | @ -1109,10 +1102,10 @@ static void zynqmp_dp_update_misc(struct zynqmp_dp *dp) | |||
|  * Return: 0 on success, or -EINVAL. | ||||
|  */ | ||||
| static int zynqmp_dp_set_format(struct zynqmp_dp *dp, | ||||
| 				const struct drm_display_info *info, | ||||
| 				enum zynqmp_dpsub_format format, | ||||
| 				unsigned int bpc) | ||||
| { | ||||
| 	static const struct drm_display_info *display; | ||||
| 	struct zynqmp_dp_config *config = &dp->config; | ||||
| 	unsigned int num_colors; | ||||
| 
 | ||||
|  | @ -1145,12 +1138,11 @@ static int zynqmp_dp_set_format(struct zynqmp_dp *dp, | |||
| 		return -EINVAL; | ||||
| 	} | ||||
| 
 | ||||
| 	display = &dp->connector.display_info; | ||||
| 	if (display->bpc && bpc > display->bpc) { | ||||
| 	if (info && info->bpc && bpc > info->bpc) { | ||||
| 		dev_warn(dp->dev, | ||||
| 			 "downgrading requested %ubpc to display limit %ubpc\n", | ||||
| 			 bpc, display->bpc); | ||||
| 		bpc = display->bpc; | ||||
| 			 bpc, info->bpc); | ||||
| 		bpc = info->bpc; | ||||
| 	} | ||||
| 
 | ||||
| 	config->misc0 &= ~ZYNQMP_DP_MAIN_STREAM_MISC0_BPC_MASK; | ||||
|  | @ -1195,7 +1187,7 @@ static int zynqmp_dp_set_format(struct zynqmp_dp *dp, | |||
|  */ | ||||
| static void | ||||
| zynqmp_dp_encoder_mode_set_transfer_unit(struct zynqmp_dp *dp, | ||||
| 					 struct drm_display_mode *mode) | ||||
| 					 const struct drm_display_mode *mode) | ||||
| { | ||||
| 	u32 tu = ZYNQMP_DP_MSA_TRANSFER_UNIT_SIZE_TU_SIZE_DEF; | ||||
| 	u32 bw, vid_kbytes, avg_bytes_per_tu, init_wait; | ||||
|  | @ -1255,12 +1247,12 @@ static void zynqmp_dp_encoder_mode_set_stream(struct zynqmp_dp *dp, | |||
| 	zynqmp_dp_write(dp, ZYNQMP_DP_MAIN_STREAM_VSTART, | ||||
| 			mode->vtotal - mode->vsync_start); | ||||
| 
 | ||||
| 	/* In synchronous mode, set the diviers */ | ||||
| 	/* In synchronous mode, set the dividers */ | ||||
| 	if (dp->config.misc0 & ZYNQMP_DP_MAIN_STREAM_MISC0_SYNC_LOCK) { | ||||
| 		reg = drm_dp_bw_code_to_link_rate(dp->mode.bw_code); | ||||
| 		zynqmp_dp_write(dp, ZYNQMP_DP_MAIN_STREAM_N_VID, reg); | ||||
| 		zynqmp_dp_write(dp, ZYNQMP_DP_MAIN_STREAM_M_VID, mode->clock); | ||||
| 		rate = zynqmp_disp_get_audio_clk_rate(dp->dpsub->disp); | ||||
| 		rate = zynqmp_dpsub_get_audio_clk_rate(dp->dpsub); | ||||
| 		if (rate) { | ||||
| 			dev_dbg(dp->dev, "Audio rate: %d\n", rate / 512); | ||||
| 			zynqmp_dp_write(dp, ZYNQMP_DP_TX_N_AUD, reg); | ||||
|  | @ -1269,7 +1261,7 @@ static void zynqmp_dp_encoder_mode_set_stream(struct zynqmp_dp *dp, | |||
| 	} | ||||
| 
 | ||||
| 	/* Only 2 channel audio is supported now */ | ||||
| 	if (zynqmp_disp_audio_enabled(dp->dpsub->disp)) | ||||
| 	if (zynqmp_dpsub_audio_enabled(dp->dpsub)) | ||||
| 		zynqmp_dp_write(dp, ZYNQMP_DP_TX_AUDIO_CHANNELS, 1); | ||||
| 
 | ||||
| 	zynqmp_dp_write(dp, ZYNQMP_DP_USER_PIX_WIDTH, 1); | ||||
|  | @ -1281,13 +1273,253 @@ static void zynqmp_dp_encoder_mode_set_stream(struct zynqmp_dp *dp, | |||
| } | ||||
| 
 | ||||
| /* -----------------------------------------------------------------------------
 | ||||
|  * DRM Connector | ||||
|  * DISP Configuration | ||||
|  */ | ||||
| 
 | ||||
| static enum drm_connector_status | ||||
| zynqmp_dp_connector_detect(struct drm_connector *connector, bool force) | ||||
| static void zynqmp_dp_disp_enable(struct zynqmp_dp *dp, | ||||
| 				  struct drm_bridge_state *old_bridge_state) | ||||
| { | ||||
| 	struct zynqmp_dp *dp = connector_to_dp(connector); | ||||
| 	enum zynqmp_dpsub_layer_id layer_id; | ||||
| 	struct zynqmp_disp_layer *layer; | ||||
| 	const struct drm_format_info *info; | ||||
| 
 | ||||
| 	if (dp->dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_LIVE_VIDEO)) | ||||
| 		layer_id = ZYNQMP_DPSUB_LAYER_VID; | ||||
| 	else if (dp->dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_LIVE_GFX)) | ||||
| 		layer_id = ZYNQMP_DPSUB_LAYER_GFX; | ||||
| 	else | ||||
| 		return; | ||||
| 
 | ||||
| 	layer = dp->dpsub->layers[layer_id]; | ||||
| 
 | ||||
| 	/* TODO: Make the format configurable. */ | ||||
| 	info = drm_format_info(DRM_FORMAT_YUV422); | ||||
| 	zynqmp_disp_layer_set_format(layer, info); | ||||
| 	zynqmp_disp_layer_enable(layer, ZYNQMP_DPSUB_LAYER_LIVE); | ||||
| 
 | ||||
| 	if (layer_id == ZYNQMP_DPSUB_LAYER_GFX) | ||||
| 		zynqmp_disp_blend_set_global_alpha(dp->dpsub->disp, true, 255); | ||||
| 	else | ||||
| 		zynqmp_disp_blend_set_global_alpha(dp->dpsub->disp, false, 0); | ||||
| 
 | ||||
| 	zynqmp_disp_enable(dp->dpsub->disp); | ||||
| } | ||||
| 
 | ||||
| static void zynqmp_dp_disp_disable(struct zynqmp_dp *dp, | ||||
| 				   struct drm_bridge_state *old_bridge_state) | ||||
| { | ||||
| 	struct zynqmp_disp_layer *layer; | ||||
| 
 | ||||
| 	if (dp->dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_LIVE_VIDEO)) | ||||
| 		layer = dp->dpsub->layers[ZYNQMP_DPSUB_LAYER_VID]; | ||||
| 	else if (dp->dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_LIVE_GFX)) | ||||
| 		layer = dp->dpsub->layers[ZYNQMP_DPSUB_LAYER_GFX]; | ||||
| 	else | ||||
| 		return; | ||||
| 
 | ||||
| 	zynqmp_disp_disable(dp->dpsub->disp); | ||||
| 	zynqmp_disp_layer_disable(layer); | ||||
| } | ||||
| 
 | ||||
| /* -----------------------------------------------------------------------------
 | ||||
|  * DRM Bridge | ||||
|  */ | ||||
| 
 | ||||
| static int zynqmp_dp_bridge_attach(struct drm_bridge *bridge, | ||||
| 				   enum drm_bridge_attach_flags flags) | ||||
| { | ||||
| 	struct zynqmp_dp *dp = bridge_to_dp(bridge); | ||||
| 	int ret; | ||||
| 
 | ||||
| 	/* Initialize and register the AUX adapter. */ | ||||
| 	ret = zynqmp_dp_aux_init(dp); | ||||
| 	if (ret) { | ||||
| 		dev_err(dp->dev, "failed to initialize DP aux\n"); | ||||
| 		return ret; | ||||
| 	} | ||||
| 
 | ||||
| 	if (dp->next_bridge) { | ||||
| 		ret = drm_bridge_attach(bridge->encoder, dp->next_bridge, | ||||
| 					bridge, flags); | ||||
| 		if (ret < 0) | ||||
| 			goto error; | ||||
| 	} | ||||
| 
 | ||||
| 	/* Now that initialisation is complete, enable interrupts. */ | ||||
| 	zynqmp_dp_write(dp, ZYNQMP_DP_INT_EN, ZYNQMP_DP_INT_ALL); | ||||
| 
 | ||||
| 	return 0; | ||||
| 
 | ||||
| error: | ||||
| 	zynqmp_dp_aux_cleanup(dp); | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| static void zynqmp_dp_bridge_detach(struct drm_bridge *bridge) | ||||
| { | ||||
| 	struct zynqmp_dp *dp = bridge_to_dp(bridge); | ||||
| 
 | ||||
| 	zynqmp_dp_aux_cleanup(dp); | ||||
| } | ||||
| 
 | ||||
| static int zynqmp_dp_bridge_mode_valid(struct drm_bridge *bridge, | ||||
| 				       const struct drm_display_info *info, | ||||
| 				       const struct drm_display_mode *mode) | ||||
| { | ||||
| 	struct zynqmp_dp *dp = bridge_to_dp(bridge); | ||||
| 	int rate; | ||||
| 
 | ||||
| 	if (mode->clock > ZYNQMP_MAX_FREQ) { | ||||
| 		dev_dbg(dp->dev, "filtered mode %s for high pixel rate\n", | ||||
| 			mode->name); | ||||
| 		drm_mode_debug_printmodeline(mode); | ||||
| 		return MODE_CLOCK_HIGH; | ||||
| 	} | ||||
| 
 | ||||
| 	/* Check with link rate and lane count */ | ||||
| 	rate = zynqmp_dp_max_rate(dp->link_config.max_rate, | ||||
| 				  dp->link_config.max_lanes, dp->config.bpp); | ||||
| 	if (mode->clock > rate) { | ||||
| 		dev_dbg(dp->dev, "filtered mode %s for high pixel rate\n", | ||||
| 			mode->name); | ||||
| 		drm_mode_debug_printmodeline(mode); | ||||
| 		return MODE_CLOCK_HIGH; | ||||
| 	} | ||||
| 
 | ||||
| 	return MODE_OK; | ||||
| } | ||||
| 
 | ||||
| static void zynqmp_dp_bridge_atomic_enable(struct drm_bridge *bridge, | ||||
| 					   struct drm_bridge_state *old_bridge_state) | ||||
| { | ||||
| 	struct zynqmp_dp *dp = bridge_to_dp(bridge); | ||||
| 	struct drm_atomic_state *state = old_bridge_state->base.state; | ||||
| 	const struct drm_crtc_state *crtc_state; | ||||
| 	const struct drm_display_mode *adjusted_mode; | ||||
| 	const struct drm_display_mode *mode; | ||||
| 	struct drm_connector *connector; | ||||
| 	struct drm_crtc *crtc; | ||||
| 	unsigned int i; | ||||
| 	int rate; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	pm_runtime_get_sync(dp->dev); | ||||
| 
 | ||||
| 	zynqmp_dp_disp_enable(dp, old_bridge_state); | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Retrieve the CRTC mode and adjusted mode. This requires a little | ||||
| 	 * dance to go from the bridge to the encoder, to the connector and to | ||||
| 	 * the CRTC. | ||||
| 	 */ | ||||
| 	connector = drm_atomic_get_new_connector_for_encoder(state, | ||||
| 							     bridge->encoder); | ||||
| 	crtc = drm_atomic_get_new_connector_state(state, connector)->crtc; | ||||
| 	crtc_state = drm_atomic_get_new_crtc_state(state, crtc); | ||||
| 	adjusted_mode = &crtc_state->adjusted_mode; | ||||
| 	mode = &crtc_state->mode; | ||||
| 
 | ||||
| 	zynqmp_dp_set_format(dp, &connector->display_info, | ||||
| 			     ZYNQMP_DPSUB_FORMAT_RGB, 8); | ||||
| 
 | ||||
| 	/* Check again as bpp or format might have been changed */ | ||||
| 	rate = zynqmp_dp_max_rate(dp->link_config.max_rate, | ||||
| 				  dp->link_config.max_lanes, dp->config.bpp); | ||||
| 	if (mode->clock > rate) { | ||||
| 		dev_err(dp->dev, "mode %s has too high pixel rate\n", | ||||
| 			mode->name); | ||||
| 		drm_mode_debug_printmodeline(mode); | ||||
| 	} | ||||
| 
 | ||||
| 	/* Configure the mode */ | ||||
| 	ret = zynqmp_dp_mode_configure(dp, adjusted_mode->clock, 0); | ||||
| 	if (ret < 0) { | ||||
| 		pm_runtime_put_sync(dp->dev); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	zynqmp_dp_encoder_mode_set_transfer_unit(dp, adjusted_mode); | ||||
| 	zynqmp_dp_encoder_mode_set_stream(dp, adjusted_mode); | ||||
| 
 | ||||
| 	/* Enable the encoder */ | ||||
| 	dp->enabled = true; | ||||
| 	zynqmp_dp_update_misc(dp); | ||||
| 	if (zynqmp_dpsub_audio_enabled(dp->dpsub)) | ||||
| 		zynqmp_dp_write(dp, ZYNQMP_DP_TX_AUDIO_CONTROL, 1); | ||||
| 	zynqmp_dp_write(dp, ZYNQMP_DP_TX_PHY_POWER_DOWN, 0); | ||||
| 	if (dp->status == connector_status_connected) { | ||||
| 		for (i = 0; i < 3; i++) { | ||||
| 			ret = drm_dp_dpcd_writeb(&dp->aux, DP_SET_POWER, | ||||
| 						 DP_SET_POWER_D0); | ||||
| 			if (ret == 1) | ||||
| 				break; | ||||
| 			usleep_range(300, 500); | ||||
| 		} | ||||
| 		/* Some monitors take time to wake up properly */ | ||||
| 		msleep(zynqmp_dp_power_on_delay_ms); | ||||
| 	} | ||||
| 	if (ret != 1) | ||||
| 		dev_dbg(dp->dev, "DP aux failed\n"); | ||||
| 	else | ||||
| 		zynqmp_dp_train_loop(dp); | ||||
| 	zynqmp_dp_write(dp, ZYNQMP_DP_SOFTWARE_RESET, | ||||
| 			ZYNQMP_DP_SOFTWARE_RESET_ALL); | ||||
| 	zynqmp_dp_write(dp, ZYNQMP_DP_MAIN_STREAM_ENABLE, 1); | ||||
| } | ||||
| 
 | ||||
| static void zynqmp_dp_bridge_atomic_disable(struct drm_bridge *bridge, | ||||
| 					    struct drm_bridge_state *old_bridge_state) | ||||
| { | ||||
| 	struct zynqmp_dp *dp = bridge_to_dp(bridge); | ||||
| 
 | ||||
| 	dp->enabled = false; | ||||
| 	cancel_delayed_work(&dp->hpd_work); | ||||
| 	zynqmp_dp_write(dp, ZYNQMP_DP_MAIN_STREAM_ENABLE, 0); | ||||
| 	drm_dp_dpcd_writeb(&dp->aux, DP_SET_POWER, DP_SET_POWER_D3); | ||||
| 	zynqmp_dp_write(dp, ZYNQMP_DP_TX_PHY_POWER_DOWN, | ||||
| 			ZYNQMP_DP_TX_PHY_POWER_DOWN_ALL); | ||||
| 	if (zynqmp_dpsub_audio_enabled(dp->dpsub)) | ||||
| 		zynqmp_dp_write(dp, ZYNQMP_DP_TX_AUDIO_CONTROL, 0); | ||||
| 
 | ||||
| 	zynqmp_dp_disp_disable(dp, old_bridge_state); | ||||
| 
 | ||||
| 	pm_runtime_put_sync(dp->dev); | ||||
| } | ||||
| 
 | ||||
| #define ZYNQMP_DP_MIN_H_BACKPORCH	20 | ||||
| 
 | ||||
| static int zynqmp_dp_bridge_atomic_check(struct drm_bridge *bridge, | ||||
| 					 struct drm_bridge_state *bridge_state, | ||||
| 					 struct drm_crtc_state *crtc_state, | ||||
| 					 struct drm_connector_state *conn_state) | ||||
| { | ||||
| 	struct zynqmp_dp *dp = bridge_to_dp(bridge); | ||||
| 	struct drm_display_mode *mode = &crtc_state->mode; | ||||
| 	struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode; | ||||
| 	int diff = mode->htotal - mode->hsync_end; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * ZynqMP DP requires horizontal backporch to be greater than 12. | ||||
| 	 * This limitation may not be compatible with the sink device. | ||||
| 	 */ | ||||
| 	if (diff < ZYNQMP_DP_MIN_H_BACKPORCH) { | ||||
| 		int vrefresh = (adjusted_mode->clock * 1000) / | ||||
| 			       (adjusted_mode->vtotal * adjusted_mode->htotal); | ||||
| 
 | ||||
| 		dev_dbg(dp->dev, "hbackporch adjusted: %d to %d", | ||||
| 			diff, ZYNQMP_DP_MIN_H_BACKPORCH - diff); | ||||
| 		diff = ZYNQMP_DP_MIN_H_BACKPORCH - diff; | ||||
| 		adjusted_mode->htotal += diff; | ||||
| 		adjusted_mode->clock = adjusted_mode->vtotal * | ||||
| 				       adjusted_mode->htotal * vrefresh / 1000; | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static enum drm_connector_status zynqmp_dp_bridge_detect(struct drm_bridge *bridge) | ||||
| { | ||||
| 	struct zynqmp_dp *dp = bridge_to_dp(bridge); | ||||
| 	struct zynqmp_dp_link_config *link_config = &dp->link_config; | ||||
| 	u32 state, i; | ||||
| 	int ret; | ||||
|  | @ -1327,192 +1559,26 @@ disconnected: | |||
| 	return connector_status_disconnected; | ||||
| } | ||||
| 
 | ||||
| static int zynqmp_dp_connector_get_modes(struct drm_connector *connector) | ||||
| static struct edid *zynqmp_dp_bridge_get_edid(struct drm_bridge *bridge, | ||||
| 					      struct drm_connector *connector) | ||||
| { | ||||
| 	struct zynqmp_dp *dp = connector_to_dp(connector); | ||||
| 	struct edid *edid; | ||||
| 	int ret; | ||||
| 	struct zynqmp_dp *dp = bridge_to_dp(bridge); | ||||
| 
 | ||||
| 	edid = drm_get_edid(connector, &dp->aux.ddc); | ||||
| 	if (!edid) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	drm_connector_update_edid_property(connector, edid); | ||||
| 	ret = drm_add_edid_modes(connector, edid); | ||||
| 	kfree(edid); | ||||
| 
 | ||||
| 	return ret; | ||||
| 	return drm_get_edid(connector, &dp->aux.ddc); | ||||
| } | ||||
| 
 | ||||
| static struct drm_encoder * | ||||
| zynqmp_dp_connector_best_encoder(struct drm_connector *connector) | ||||
| { | ||||
| 	struct zynqmp_dp *dp = connector_to_dp(connector); | ||||
| 
 | ||||
| 	return &dp->encoder; | ||||
| } | ||||
| 
 | ||||
| static int zynqmp_dp_connector_mode_valid(struct drm_connector *connector, | ||||
| 					  struct drm_display_mode *mode) | ||||
| { | ||||
| 	struct zynqmp_dp *dp = connector_to_dp(connector); | ||||
| 	u8 max_lanes = dp->link_config.max_lanes; | ||||
| 	u8 bpp = dp->config.bpp; | ||||
| 	int max_rate = dp->link_config.max_rate; | ||||
| 	int rate; | ||||
| 
 | ||||
| 	if (mode->clock > ZYNQMP_MAX_FREQ) { | ||||
| 		dev_dbg(dp->dev, "filtered the mode, %s,for high pixel rate\n", | ||||
| 			mode->name); | ||||
| 		drm_mode_debug_printmodeline(mode); | ||||
| 		return MODE_CLOCK_HIGH; | ||||
| 	} | ||||
| 
 | ||||
| 	/* Check with link rate and lane count */ | ||||
| 	rate = zynqmp_dp_max_rate(max_rate, max_lanes, bpp); | ||||
| 	if (mode->clock > rate) { | ||||
| 		dev_dbg(dp->dev, "filtered the mode, %s,for high pixel rate\n", | ||||
| 			mode->name); | ||||
| 		drm_mode_debug_printmodeline(mode); | ||||
| 		return MODE_CLOCK_HIGH; | ||||
| 	} | ||||
| 
 | ||||
| 	return MODE_OK; | ||||
| } | ||||
| 
 | ||||
| static const struct drm_connector_funcs zynqmp_dp_connector_funcs = { | ||||
| 	.detect			= zynqmp_dp_connector_detect, | ||||
| 	.fill_modes		= drm_helper_probe_single_connector_modes, | ||||
| 	.destroy		= drm_connector_cleanup, | ||||
| 	.atomic_duplicate_state	= drm_atomic_helper_connector_duplicate_state, | ||||
| 	.atomic_destroy_state	= drm_atomic_helper_connector_destroy_state, | ||||
| 	.reset			= drm_atomic_helper_connector_reset, | ||||
| }; | ||||
| 
 | ||||
| static const struct drm_connector_helper_funcs | ||||
| zynqmp_dp_connector_helper_funcs = { | ||||
| 	.get_modes	= zynqmp_dp_connector_get_modes, | ||||
| 	.best_encoder	= zynqmp_dp_connector_best_encoder, | ||||
| 	.mode_valid	= zynqmp_dp_connector_mode_valid, | ||||
| }; | ||||
| 
 | ||||
| /* -----------------------------------------------------------------------------
 | ||||
|  * DRM Encoder | ||||
|  */ | ||||
| 
 | ||||
| static void zynqmp_dp_encoder_enable(struct drm_encoder *encoder) | ||||
| { | ||||
| 	struct zynqmp_dp *dp = encoder_to_dp(encoder); | ||||
| 	unsigned int i; | ||||
| 	int ret = 0; | ||||
| 
 | ||||
| 	pm_runtime_get_sync(dp->dev); | ||||
| 	dp->enabled = true; | ||||
| 	zynqmp_dp_update_misc(dp); | ||||
| 	if (zynqmp_disp_audio_enabled(dp->dpsub->disp)) | ||||
| 		zynqmp_dp_write(dp, ZYNQMP_DP_TX_AUDIO_CONTROL, 1); | ||||
| 	zynqmp_dp_write(dp, ZYNQMP_DP_TX_PHY_POWER_DOWN, 0); | ||||
| 	if (dp->status == connector_status_connected) { | ||||
| 		for (i = 0; i < 3; i++) { | ||||
| 			ret = drm_dp_dpcd_writeb(&dp->aux, DP_SET_POWER, | ||||
| 						 DP_SET_POWER_D0); | ||||
| 			if (ret == 1) | ||||
| 				break; | ||||
| 			usleep_range(300, 500); | ||||
| 		} | ||||
| 		/* Some monitors take time to wake up properly */ | ||||
| 		msleep(zynqmp_dp_power_on_delay_ms); | ||||
| 	} | ||||
| 	if (ret != 1) | ||||
| 		dev_dbg(dp->dev, "DP aux failed\n"); | ||||
| 	else | ||||
| 		zynqmp_dp_train_loop(dp); | ||||
| 	zynqmp_dp_write(dp, ZYNQMP_DP_SOFTWARE_RESET, | ||||
| 			ZYNQMP_DP_SOFTWARE_RESET_ALL); | ||||
| 	zynqmp_dp_write(dp, ZYNQMP_DP_MAIN_STREAM_ENABLE, 1); | ||||
| } | ||||
| 
 | ||||
| static void zynqmp_dp_encoder_disable(struct drm_encoder *encoder) | ||||
| { | ||||
| 	struct zynqmp_dp *dp = encoder_to_dp(encoder); | ||||
| 
 | ||||
| 	dp->enabled = false; | ||||
| 	cancel_delayed_work(&dp->hpd_work); | ||||
| 	zynqmp_dp_write(dp, ZYNQMP_DP_MAIN_STREAM_ENABLE, 0); | ||||
| 	drm_dp_dpcd_writeb(&dp->aux, DP_SET_POWER, DP_SET_POWER_D3); | ||||
| 	zynqmp_dp_write(dp, ZYNQMP_DP_TX_PHY_POWER_DOWN, | ||||
| 			ZYNQMP_DP_TX_PHY_POWER_DOWN_ALL); | ||||
| 	if (zynqmp_disp_audio_enabled(dp->dpsub->disp)) | ||||
| 		zynqmp_dp_write(dp, ZYNQMP_DP_TX_AUDIO_CONTROL, 0); | ||||
| 	pm_runtime_put_sync(dp->dev); | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| zynqmp_dp_encoder_atomic_mode_set(struct drm_encoder *encoder, | ||||
| 				  struct drm_crtc_state *crtc_state, | ||||
| 				  struct drm_connector_state *connector_state) | ||||
| { | ||||
| 	struct zynqmp_dp *dp = encoder_to_dp(encoder); | ||||
| 	struct drm_display_mode *mode = &crtc_state->mode; | ||||
| 	struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode; | ||||
| 	u8 max_lanes = dp->link_config.max_lanes; | ||||
| 	u8 bpp = dp->config.bpp; | ||||
| 	int rate, max_rate = dp->link_config.max_rate; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	zynqmp_dp_set_format(dp, ZYNQMP_DPSUB_FORMAT_RGB, 8); | ||||
| 
 | ||||
| 	/* Check again as bpp or format might have been chagned */ | ||||
| 	rate = zynqmp_dp_max_rate(max_rate, max_lanes, bpp); | ||||
| 	if (mode->clock > rate) { | ||||
| 		dev_err(dp->dev, "the mode, %s,has too high pixel rate\n", | ||||
| 			mode->name); | ||||
| 		drm_mode_debug_printmodeline(mode); | ||||
| 	} | ||||
| 
 | ||||
| 	ret = zynqmp_dp_mode_configure(dp, adjusted_mode->clock, 0); | ||||
| 	if (ret < 0) | ||||
| 		return; | ||||
| 
 | ||||
| 	zynqmp_dp_encoder_mode_set_transfer_unit(dp, adjusted_mode); | ||||
| 	zynqmp_dp_encoder_mode_set_stream(dp, adjusted_mode); | ||||
| } | ||||
| 
 | ||||
| #define ZYNQMP_DP_MIN_H_BACKPORCH	20 | ||||
| 
 | ||||
| static int | ||||
| zynqmp_dp_encoder_atomic_check(struct drm_encoder *encoder, | ||||
| 			       struct drm_crtc_state *crtc_state, | ||||
| 			       struct drm_connector_state *conn_state) | ||||
| { | ||||
| 	struct drm_display_mode *mode = &crtc_state->mode; | ||||
| 	struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode; | ||||
| 	int diff = mode->htotal - mode->hsync_end; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * ZynqMP DP requires horizontal backporch to be greater than 12. | ||||
| 	 * This limitation may not be compatible with the sink device. | ||||
| 	 */ | ||||
| 	if (diff < ZYNQMP_DP_MIN_H_BACKPORCH) { | ||||
| 		int vrefresh = (adjusted_mode->clock * 1000) / | ||||
| 			       (adjusted_mode->vtotal * adjusted_mode->htotal); | ||||
| 
 | ||||
| 		dev_dbg(encoder->dev->dev, "hbackporch adjusted: %d to %d", | ||||
| 			diff, ZYNQMP_DP_MIN_H_BACKPORCH - diff); | ||||
| 		diff = ZYNQMP_DP_MIN_H_BACKPORCH - diff; | ||||
| 		adjusted_mode->htotal += diff; | ||||
| 		adjusted_mode->clock = adjusted_mode->vtotal * | ||||
| 				       adjusted_mode->htotal * vrefresh / 1000; | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static const struct drm_encoder_helper_funcs zynqmp_dp_encoder_helper_funcs = { | ||||
| 	.enable			= zynqmp_dp_encoder_enable, | ||||
| 	.disable		= zynqmp_dp_encoder_disable, | ||||
| 	.atomic_mode_set	= zynqmp_dp_encoder_atomic_mode_set, | ||||
| 	.atomic_check		= zynqmp_dp_encoder_atomic_check, | ||||
| static const struct drm_bridge_funcs zynqmp_dp_bridge_funcs = { | ||||
| 	.attach = zynqmp_dp_bridge_attach, | ||||
| 	.detach = zynqmp_dp_bridge_detach, | ||||
| 	.mode_valid = zynqmp_dp_bridge_mode_valid, | ||||
| 	.atomic_enable = zynqmp_dp_bridge_atomic_enable, | ||||
| 	.atomic_disable = zynqmp_dp_bridge_atomic_disable, | ||||
| 	.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, | ||||
| 	.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, | ||||
| 	.atomic_reset = drm_atomic_helper_bridge_reset, | ||||
| 	.atomic_check = zynqmp_dp_bridge_atomic_check, | ||||
| 	.detect = zynqmp_dp_bridge_detect, | ||||
| 	.get_edid = zynqmp_dp_bridge_get_edid, | ||||
| }; | ||||
| 
 | ||||
| /* -----------------------------------------------------------------------------
 | ||||
|  | @ -1543,12 +1609,12 @@ void zynqmp_dp_disable_vblank(struct zynqmp_dp *dp) | |||
| 
 | ||||
| static void zynqmp_dp_hpd_work_func(struct work_struct *work) | ||||
| { | ||||
| 	struct zynqmp_dp *dp; | ||||
| 	struct zynqmp_dp *dp = container_of(work, struct zynqmp_dp, | ||||
| 					    hpd_work.work); | ||||
| 	enum drm_connector_status status; | ||||
| 
 | ||||
| 	dp = container_of(work, struct zynqmp_dp, hpd_work.work); | ||||
| 
 | ||||
| 	if (dp->drm) | ||||
| 		drm_helper_hpd_irq_event(dp->drm); | ||||
| 	status = zynqmp_dp_bridge_detect(&dp->bridge); | ||||
| 	drm_bridge_hpd_notify(&dp->bridge, status); | ||||
| } | ||||
| 
 | ||||
| static irqreturn_t zynqmp_dp_irq_handler(int irq, void *data) | ||||
|  | @ -1570,7 +1636,7 @@ static irqreturn_t zynqmp_dp_irq_handler(int irq, void *data) | |||
| 	zynqmp_dp_write(dp, ZYNQMP_DP_INT_STATUS, status); | ||||
| 
 | ||||
| 	if (status & ZYNQMP_DP_INT_VBLANK_START) | ||||
| 		zynqmp_disp_handle_vblank(dp->dpsub->disp); | ||||
| 		zynqmp_dpsub_drm_handle_vblank(dp->dpsub); | ||||
| 
 | ||||
| 	if (status & ZYNQMP_DP_INT_HPD_EVENT) | ||||
| 		schedule_delayed_work(&dp->hpd_work, 0); | ||||
|  | @ -1599,94 +1665,76 @@ handled: | |||
|  * Initialization & Cleanup | ||||
|  */ | ||||
| 
 | ||||
| int zynqmp_dp_drm_init(struct zynqmp_dpsub *dpsub) | ||||
| { | ||||
| 	struct zynqmp_dp *dp = dpsub->dp; | ||||
| 	struct drm_encoder *encoder = &dp->encoder; | ||||
| 	struct drm_connector *connector = &dp->connector; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	dp->config.misc0 &= ~ZYNQMP_DP_MAIN_STREAM_MISC0_SYNC_LOCK; | ||||
| 	zynqmp_dp_set_format(dp, ZYNQMP_DPSUB_FORMAT_RGB, 8); | ||||
| 
 | ||||
| 	/* Create the DRM encoder and connector. */ | ||||
| 	encoder->possible_crtcs |= zynqmp_disp_get_crtc_mask(dpsub->disp); | ||||
| 	drm_simple_encoder_init(dp->drm, encoder, DRM_MODE_ENCODER_TMDS); | ||||
| 	drm_encoder_helper_add(encoder, &zynqmp_dp_encoder_helper_funcs); | ||||
| 
 | ||||
| 	connector->polled = DRM_CONNECTOR_POLL_HPD; | ||||
| 	ret = drm_connector_init(encoder->dev, connector, | ||||
| 				 &zynqmp_dp_connector_funcs, | ||||
| 				 DRM_MODE_CONNECTOR_DisplayPort); | ||||
| 	if (ret) { | ||||
| 		dev_err(dp->dev, "failed to create the DRM connector\n"); | ||||
| 		return ret; | ||||
| 	} | ||||
| 
 | ||||
| 	drm_connector_helper_add(connector, &zynqmp_dp_connector_helper_funcs); | ||||
| 	drm_connector_register(connector); | ||||
| 	drm_connector_attach_encoder(connector, encoder); | ||||
| 
 | ||||
| 	/* Initialize and register the AUX adapter. */ | ||||
| 	ret = zynqmp_dp_aux_init(dp); | ||||
| 	if (ret) { | ||||
| 		dev_err(dp->dev, "failed to initialize DP aux\n"); | ||||
| 		return ret; | ||||
| 	} | ||||
| 
 | ||||
| 	/* Now that initialisation is complete, enable interrupts. */ | ||||
| 	zynqmp_dp_write(dp, ZYNQMP_DP_INT_EN, ZYNQMP_DP_INT_ALL); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| int zynqmp_dp_probe(struct zynqmp_dpsub *dpsub, struct drm_device *drm) | ||||
| int zynqmp_dp_probe(struct zynqmp_dpsub *dpsub) | ||||
| { | ||||
| 	struct platform_device *pdev = to_platform_device(dpsub->dev); | ||||
| 	struct drm_bridge *bridge; | ||||
| 	struct zynqmp_dp *dp; | ||||
| 	struct resource *res; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	dp = drmm_kzalloc(drm, sizeof(*dp), GFP_KERNEL); | ||||
| 	dp = kzalloc(sizeof(*dp), GFP_KERNEL); | ||||
| 	if (!dp) | ||||
| 		return -ENOMEM; | ||||
| 
 | ||||
| 	dp->dev = &pdev->dev; | ||||
| 	dp->dpsub = dpsub; | ||||
| 	dp->status = connector_status_disconnected; | ||||
| 	dp->drm = drm; | ||||
| 
 | ||||
| 	INIT_DELAYED_WORK(&dp->hpd_work, zynqmp_dp_hpd_work_func); | ||||
| 
 | ||||
| 	dpsub->dp = dp; | ||||
| 
 | ||||
| 	/* Acquire all resources (IOMEM, IRQ and PHYs). */ | ||||
| 	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dp"); | ||||
| 	dp->iomem = devm_ioremap_resource(dp->dev, res); | ||||
| 	if (IS_ERR(dp->iomem)) | ||||
| 		return PTR_ERR(dp->iomem); | ||||
| 	if (IS_ERR(dp->iomem)) { | ||||
| 		ret = PTR_ERR(dp->iomem); | ||||
| 		goto err_free; | ||||
| 	} | ||||
| 
 | ||||
| 	dp->irq = platform_get_irq(pdev, 0); | ||||
| 	if (dp->irq < 0) | ||||
| 		return dp->irq; | ||||
| 	if (dp->irq < 0) { | ||||
| 		ret = dp->irq; | ||||
| 		goto err_free; | ||||
| 	} | ||||
| 
 | ||||
| 	dp->reset = devm_reset_control_get(dp->dev, NULL); | ||||
| 	if (IS_ERR(dp->reset)) { | ||||
| 		if (PTR_ERR(dp->reset) != -EPROBE_DEFER) | ||||
| 			dev_err(dp->dev, "failed to get reset: %ld\n", | ||||
| 				PTR_ERR(dp->reset)); | ||||
| 		return PTR_ERR(dp->reset); | ||||
| 		ret = PTR_ERR(dp->reset); | ||||
| 		goto err_free; | ||||
| 	} | ||||
| 
 | ||||
| 	ret = zynqmp_dp_reset(dp, false); | ||||
| 	if (ret < 0) | ||||
| 		return ret; | ||||
| 		goto err_free; | ||||
| 
 | ||||
| 	ret = zynqmp_dp_phy_probe(dp); | ||||
| 	if (ret) | ||||
| 		goto err_reset; | ||||
| 
 | ||||
| 	/* Initialize the bridge. */ | ||||
| 	bridge = &dp->bridge; | ||||
| 	bridge->funcs = &zynqmp_dp_bridge_funcs; | ||||
| 	bridge->ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | ||||
| 		    | DRM_BRIDGE_OP_HPD; | ||||
| 	bridge->type = DRM_MODE_CONNECTOR_DisplayPort; | ||||
| 	dpsub->bridge = bridge; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Acquire the next bridge in the chain. Ignore errors caused by port@5 | ||||
| 	 * not being connected for backward-compatibility with older DTs. | ||||
| 	 */ | ||||
| 	ret = drm_of_find_panel_or_bridge(dp->dev->of_node, 5, 0, NULL, | ||||
| 					  &dp->next_bridge); | ||||
| 	if (ret < 0 && ret != -ENODEV) | ||||
| 		goto err_reset; | ||||
| 
 | ||||
| 	/* Initialize the hardware. */ | ||||
| 	dp->config.misc0 &= ~ZYNQMP_DP_MAIN_STREAM_MISC0_SYNC_LOCK; | ||||
| 	zynqmp_dp_set_format(dp, NULL, ZYNQMP_DPSUB_FORMAT_RGB, 8); | ||||
| 
 | ||||
| 	zynqmp_dp_write(dp, ZYNQMP_DP_TX_PHY_POWER_DOWN, | ||||
| 			ZYNQMP_DP_TX_PHY_POWER_DOWN_ALL); | ||||
| 	zynqmp_dp_set(dp, ZYNQMP_DP_PHY_RESET, ZYNQMP_DP_PHY_RESET_ALL_RESET); | ||||
|  | @ -1710,6 +1758,8 @@ int zynqmp_dp_probe(struct zynqmp_dpsub *dpsub, struct drm_device *drm) | |||
| 	if (ret < 0) | ||||
| 		goto err_phy_exit; | ||||
| 
 | ||||
| 	dpsub->dp = dp; | ||||
| 
 | ||||
| 	dev_dbg(dp->dev, "ZynqMP DisplayPort Tx probed with %u lanes\n", | ||||
| 		dp->num_lanes); | ||||
| 
 | ||||
|  | @ -1719,7 +1769,8 @@ err_phy_exit: | |||
| 	zynqmp_dp_phy_exit(dp); | ||||
| err_reset: | ||||
| 	zynqmp_dp_reset(dp, true); | ||||
| 
 | ||||
| err_free: | ||||
| 	kfree(dp); | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
|  | @ -1731,7 +1782,6 @@ void zynqmp_dp_remove(struct zynqmp_dpsub *dpsub) | |||
| 	disable_irq(dp->irq); | ||||
| 
 | ||||
| 	cancel_delayed_work_sync(&dp->hpd_work); | ||||
| 	zynqmp_dp_aux_cleanup(dp); | ||||
| 
 | ||||
| 	zynqmp_dp_write(dp, ZYNQMP_DP_TRANSMITTER_ENABLE, 0); | ||||
| 	zynqmp_dp_write(dp, ZYNQMP_DP_INT_DS, 0xffffffff); | ||||
|  |  | |||
|  | @ -12,7 +12,6 @@ | |||
| #ifndef _ZYNQMP_DP_H_ | ||||
| #define _ZYNQMP_DP_H_ | ||||
| 
 | ||||
| struct drm_device; | ||||
| struct platform_device; | ||||
| struct zynqmp_dp; | ||||
| struct zynqmp_dpsub; | ||||
|  | @ -20,8 +19,7 @@ struct zynqmp_dpsub; | |||
| void zynqmp_dp_enable_vblank(struct zynqmp_dp *dp); | ||||
| void zynqmp_dp_disable_vblank(struct zynqmp_dp *dp); | ||||
| 
 | ||||
| int zynqmp_dp_drm_init(struct zynqmp_dpsub *dpsub); | ||||
| int zynqmp_dp_probe(struct zynqmp_dpsub *dpsub, struct drm_device *drm); | ||||
| int zynqmp_dp_probe(struct zynqmp_dpsub *dpsub); | ||||
| void zynqmp_dp_remove(struct zynqmp_dpsub *dpsub); | ||||
| 
 | ||||
| #endif /* _ZYNQMP_DP_H_ */ | ||||
|  |  | |||
|  | @ -12,137 +12,21 @@ | |||
| #include <linux/clk.h> | ||||
| #include <linux/dma-mapping.h> | ||||
| #include <linux/module.h> | ||||
| #include <linux/of_graph.h> | ||||
| #include <linux/of_reserved_mem.h> | ||||
| #include <linux/platform_device.h> | ||||
| #include <linux/pm_runtime.h> | ||||
| #include <linux/slab.h> | ||||
| 
 | ||||
| #include <drm/drm_atomic_helper.h> | ||||
| #include <drm/drm_device.h> | ||||
| #include <drm/drm_drv.h> | ||||
| #include <drm/drm_fb_helper.h> | ||||
| #include <drm/drm_fourcc.h> | ||||
| #include <drm/drm_gem_dma_helper.h> | ||||
| #include <drm/drm_gem_framebuffer_helper.h> | ||||
| #include <drm/drm_managed.h> | ||||
| #include <drm/drm_mode_config.h> | ||||
| #include <drm/drm_bridge.h> | ||||
| #include <drm/drm_modeset_helper.h> | ||||
| #include <drm/drm_module.h> | ||||
| #include <drm/drm_probe_helper.h> | ||||
| #include <drm/drm_vblank.h> | ||||
| 
 | ||||
| #include "zynqmp_disp.h" | ||||
| #include "zynqmp_dp.h" | ||||
| #include "zynqmp_dpsub.h" | ||||
| 
 | ||||
| /* -----------------------------------------------------------------------------
 | ||||
|  * Dumb Buffer & Framebuffer Allocation | ||||
|  */ | ||||
| 
 | ||||
| static int zynqmp_dpsub_dumb_create(struct drm_file *file_priv, | ||||
| 				    struct drm_device *drm, | ||||
| 				    struct drm_mode_create_dumb *args) | ||||
| { | ||||
| 	struct zynqmp_dpsub *dpsub = to_zynqmp_dpsub(drm); | ||||
| 	unsigned int pitch = DIV_ROUND_UP(args->width * args->bpp, 8); | ||||
| 
 | ||||
| 	/* Enforce the alignment constraints of the DMA engine. */ | ||||
| 	args->pitch = ALIGN(pitch, dpsub->dma_align); | ||||
| 
 | ||||
| 	return drm_gem_dma_dumb_create_internal(file_priv, drm, args); | ||||
| } | ||||
| 
 | ||||
| static struct drm_framebuffer * | ||||
| zynqmp_dpsub_fb_create(struct drm_device *drm, struct drm_file *file_priv, | ||||
| 		       const struct drm_mode_fb_cmd2 *mode_cmd) | ||||
| { | ||||
| 	struct zynqmp_dpsub *dpsub = to_zynqmp_dpsub(drm); | ||||
| 	struct drm_mode_fb_cmd2 cmd = *mode_cmd; | ||||
| 	unsigned int i; | ||||
| 
 | ||||
| 	/* Enforce the alignment constraints of the DMA engine. */ | ||||
| 	for (i = 0; i < ARRAY_SIZE(cmd.pitches); ++i) | ||||
| 		cmd.pitches[i] = ALIGN(cmd.pitches[i], dpsub->dma_align); | ||||
| 
 | ||||
| 	return drm_gem_fb_create(drm, file_priv, &cmd); | ||||
| } | ||||
| 
 | ||||
| static const struct drm_mode_config_funcs zynqmp_dpsub_mode_config_funcs = { | ||||
| 	.fb_create		= zynqmp_dpsub_fb_create, | ||||
| 	.atomic_check		= drm_atomic_helper_check, | ||||
| 	.atomic_commit		= drm_atomic_helper_commit, | ||||
| }; | ||||
| 
 | ||||
| /* -----------------------------------------------------------------------------
 | ||||
|  * DRM/KMS Driver | ||||
|  */ | ||||
| 
 | ||||
| DEFINE_DRM_GEM_DMA_FOPS(zynqmp_dpsub_drm_fops); | ||||
| 
 | ||||
| static const struct drm_driver zynqmp_dpsub_drm_driver = { | ||||
| 	.driver_features		= DRIVER_MODESET | DRIVER_GEM | | ||||
| 					  DRIVER_ATOMIC, | ||||
| 
 | ||||
| 	DRM_GEM_DMA_DRIVER_OPS_WITH_DUMB_CREATE(zynqmp_dpsub_dumb_create), | ||||
| 
 | ||||
| 	.fops				= &zynqmp_dpsub_drm_fops, | ||||
| 
 | ||||
| 	.name				= "zynqmp-dpsub", | ||||
| 	.desc				= "Xilinx DisplayPort Subsystem Driver", | ||||
| 	.date				= "20130509", | ||||
| 	.major				= 1, | ||||
| 	.minor				= 0, | ||||
| }; | ||||
| 
 | ||||
| static int zynqmp_dpsub_drm_init(struct zynqmp_dpsub *dpsub) | ||||
| { | ||||
| 	struct drm_device *drm = &dpsub->drm; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	/* Initialize mode config, vblank and the KMS poll helper. */ | ||||
| 	ret = drmm_mode_config_init(drm); | ||||
| 	if (ret < 0) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	drm->mode_config.funcs = &zynqmp_dpsub_mode_config_funcs; | ||||
| 	drm->mode_config.min_width = 0; | ||||
| 	drm->mode_config.min_height = 0; | ||||
| 	drm->mode_config.max_width = ZYNQMP_DISP_MAX_WIDTH; | ||||
| 	drm->mode_config.max_height = ZYNQMP_DISP_MAX_HEIGHT; | ||||
| 
 | ||||
| 	ret = drm_vblank_init(drm, 1); | ||||
| 	if (ret) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	drm_kms_helper_poll_init(drm); | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Initialize the DISP and DP components. This will creates planes, | ||||
| 	 * CRTC, encoder and connector. The DISP should be initialized first as | ||||
| 	 * the DP encoder needs the CRTC. | ||||
| 	 */ | ||||
| 	ret = zynqmp_disp_drm_init(dpsub); | ||||
| 	if (ret) | ||||
| 		goto err_poll_fini; | ||||
| 
 | ||||
| 	ret = zynqmp_dp_drm_init(dpsub); | ||||
| 	if (ret) | ||||
| 		goto err_poll_fini; | ||||
| 
 | ||||
| 	/* Reset all components and register the DRM device. */ | ||||
| 	drm_mode_config_reset(drm); | ||||
| 
 | ||||
| 	ret = drm_dev_register(drm, 0); | ||||
| 	if (ret < 0) | ||||
| 		goto err_poll_fini; | ||||
| 
 | ||||
| 	/* Initialize fbdev generic emulation. */ | ||||
| 	drm_fbdev_generic_setup(drm, 24); | ||||
| 
 | ||||
| 	return 0; | ||||
| 
 | ||||
| err_poll_fini: | ||||
| 	drm_kms_helper_poll_fini(drm); | ||||
| 	return ret; | ||||
| } | ||||
| #include "zynqmp_kms.h" | ||||
| 
 | ||||
| /* -----------------------------------------------------------------------------
 | ||||
|  * Power Management | ||||
|  | @ -152,20 +36,56 @@ static int __maybe_unused zynqmp_dpsub_suspend(struct device *dev) | |||
| { | ||||
| 	struct zynqmp_dpsub *dpsub = dev_get_drvdata(dev); | ||||
| 
 | ||||
| 	return drm_mode_config_helper_suspend(&dpsub->drm); | ||||
| 	if (!dpsub->drm) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	return drm_mode_config_helper_suspend(&dpsub->drm->dev); | ||||
| } | ||||
| 
 | ||||
| static int __maybe_unused zynqmp_dpsub_resume(struct device *dev) | ||||
| { | ||||
| 	struct zynqmp_dpsub *dpsub = dev_get_drvdata(dev); | ||||
| 
 | ||||
| 	return drm_mode_config_helper_resume(&dpsub->drm); | ||||
| 	if (!dpsub->drm) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	return drm_mode_config_helper_resume(&dpsub->drm->dev); | ||||
| } | ||||
| 
 | ||||
| static const struct dev_pm_ops zynqmp_dpsub_pm_ops = { | ||||
| 	SET_SYSTEM_SLEEP_PM_OPS(zynqmp_dpsub_suspend, zynqmp_dpsub_resume) | ||||
| }; | ||||
| 
 | ||||
| /* -----------------------------------------------------------------------------
 | ||||
|  * DPSUB Configuration | ||||
|  */ | ||||
| 
 | ||||
| /**
 | ||||
|  * zynqmp_dpsub_audio_enabled - If the audio is enabled | ||||
|  * @dpsub: DisplayPort subsystem | ||||
|  * | ||||
|  * Return if the audio is enabled depending on the audio clock. | ||||
|  * | ||||
|  * Return: true if audio is enabled, or false. | ||||
|  */ | ||||
| bool zynqmp_dpsub_audio_enabled(struct zynqmp_dpsub *dpsub) | ||||
| { | ||||
| 	return !!dpsub->aud_clk; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * zynqmp_dpsub_get_audio_clk_rate - Get the current audio clock rate | ||||
|  * @dpsub: DisplayPort subsystem | ||||
|  * | ||||
|  * Return: the current audio clock rate. | ||||
|  */ | ||||
| unsigned int zynqmp_dpsub_get_audio_clk_rate(struct zynqmp_dpsub *dpsub) | ||||
| { | ||||
| 	if (zynqmp_dpsub_audio_enabled(dpsub)) | ||||
| 		return 0; | ||||
| 	return clk_get_rate(dpsub->aud_clk); | ||||
| } | ||||
| 
 | ||||
| /* -----------------------------------------------------------------------------
 | ||||
|  * Probe & Remove | ||||
|  */ | ||||
|  | @ -184,19 +104,125 @@ static int zynqmp_dpsub_init_clocks(struct zynqmp_dpsub *dpsub) | |||
| 		return ret; | ||||
| 	} | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Try the live PL video clock, and fall back to the PS clock if the | ||||
| 	 * live PL video clock isn't valid. | ||||
| 	 */ | ||||
| 	dpsub->vid_clk = devm_clk_get(dpsub->dev, "dp_live_video_in_clk"); | ||||
| 	if (!IS_ERR(dpsub->vid_clk)) | ||||
| 		dpsub->vid_clk_from_ps = false; | ||||
| 	else if (PTR_ERR(dpsub->vid_clk) == -EPROBE_DEFER) | ||||
| 		return PTR_ERR(dpsub->vid_clk); | ||||
| 
 | ||||
| 	if (IS_ERR_OR_NULL(dpsub->vid_clk)) { | ||||
| 		dpsub->vid_clk = devm_clk_get(dpsub->dev, "dp_vtc_pixel_clk_in"); | ||||
| 		if (IS_ERR(dpsub->vid_clk)) { | ||||
| 			dev_err(dpsub->dev, "failed to init any video clock\n"); | ||||
| 			return PTR_ERR(dpsub->vid_clk); | ||||
| 		} | ||||
| 		dpsub->vid_clk_from_ps = true; | ||||
| 	} | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Try the live PL audio clock, and fall back to the PS clock if the | ||||
| 	 * live PL audio clock isn't valid. Missing audio clock disables audio | ||||
| 	 * but isn't an error. | ||||
| 	 */ | ||||
| 	dpsub->aud_clk = devm_clk_get(dpsub->dev, "dp_live_audio_aclk"); | ||||
| 	if (!IS_ERR(dpsub->aud_clk)) { | ||||
| 		dpsub->aud_clk_from_ps = false; | ||||
| 		return 0; | ||||
| 	} | ||||
| 
 | ||||
| 	dpsub->aud_clk = devm_clk_get(dpsub->dev, "dp_aud_clk"); | ||||
| 	if (!IS_ERR(dpsub->aud_clk)) { | ||||
| 		dpsub->aud_clk_from_ps = true; | ||||
| 		return 0; | ||||
| 	} | ||||
| 
 | ||||
| 	dev_info(dpsub->dev, "audio disabled due to missing clock\n"); | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static int zynqmp_dpsub_parse_dt(struct zynqmp_dpsub *dpsub) | ||||
| { | ||||
| 	struct device_node *np; | ||||
| 	unsigned int i; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * For backward compatibility with old device trees that don't contain | ||||
| 	 * ports, consider that only the DP output port is connected if no | ||||
| 	 * ports child no exists. | ||||
| 	 */ | ||||
| 	np = of_get_child_by_name(dpsub->dev->of_node, "ports"); | ||||
| 	of_node_put(np); | ||||
| 	if (!np) { | ||||
| 		dev_warn(dpsub->dev, "missing ports, update DT bindings\n"); | ||||
| 		dpsub->connected_ports = BIT(ZYNQMP_DPSUB_PORT_OUT_DP); | ||||
| 		dpsub->dma_enabled = true; | ||||
| 		return 0; | ||||
| 	} | ||||
| 
 | ||||
| 	/* Check which ports are connected. */ | ||||
| 	for (i = 0; i < ZYNQMP_DPSUB_NUM_PORTS; ++i) { | ||||
| 		struct device_node *np; | ||||
| 
 | ||||
| 		np = of_graph_get_remote_node(dpsub->dev->of_node, i, -1); | ||||
| 		if (np) { | ||||
| 			dpsub->connected_ports |= BIT(i); | ||||
| 			of_node_put(np); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	/* Sanity checks. */ | ||||
| 	if ((dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_LIVE_VIDEO)) && | ||||
| 	    (dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_LIVE_GFX))) { | ||||
| 		dev_err(dpsub->dev, "only one live video input is supported\n"); | ||||
| 		return -EINVAL; | ||||
| 	} | ||||
| 
 | ||||
| 	if ((dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_LIVE_VIDEO)) || | ||||
| 	    (dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_LIVE_GFX))) { | ||||
| 		if (dpsub->vid_clk_from_ps) { | ||||
| 			dev_err(dpsub->dev, | ||||
| 				"live video input requires PL clock\n"); | ||||
| 			return -EINVAL; | ||||
| 		} | ||||
| 	} else { | ||||
| 		dpsub->dma_enabled = true; | ||||
| 	} | ||||
| 
 | ||||
| 	if (dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_LIVE_AUDIO)) | ||||
| 		dev_warn(dpsub->dev, "live audio unsupported, ignoring\n"); | ||||
| 
 | ||||
| 	if ((dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_OUT_VIDEO)) || | ||||
| 	    (dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_OUT_AUDIO))) | ||||
| 		dev_warn(dpsub->dev, "output to PL unsupported, ignoring\n"); | ||||
| 
 | ||||
| 	if (!(dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_OUT_DP))) { | ||||
| 		dev_err(dpsub->dev, "DP output port not connected\n"); | ||||
| 		return -EINVAL; | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| void zynqmp_dpsub_release(struct zynqmp_dpsub *dpsub) | ||||
| { | ||||
| 	kfree(dpsub->disp); | ||||
| 	kfree(dpsub->dp); | ||||
| 	kfree(dpsub); | ||||
| } | ||||
| 
 | ||||
| static int zynqmp_dpsub_probe(struct platform_device *pdev) | ||||
| { | ||||
| 	struct zynqmp_dpsub *dpsub; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	/* Allocate private data. */ | ||||
| 	dpsub = devm_drm_dev_alloc(&pdev->dev, &zynqmp_dpsub_drm_driver, | ||||
| 				   struct zynqmp_dpsub, drm); | ||||
| 	if (IS_ERR(dpsub)) | ||||
| 		return PTR_ERR(dpsub); | ||||
| 	dpsub = kzalloc(sizeof(*dpsub), GFP_KERNEL); | ||||
| 	if (!dpsub) | ||||
| 		return -ENOMEM; | ||||
| 
 | ||||
| 	dpsub->dev = &pdev->dev; | ||||
| 	platform_set_drvdata(pdev, dpsub); | ||||
|  | @ -210,23 +236,31 @@ static int zynqmp_dpsub_probe(struct platform_device *pdev) | |||
| 	if (ret < 0) | ||||
| 		goto err_mem; | ||||
| 
 | ||||
| 	ret = zynqmp_dpsub_parse_dt(dpsub); | ||||
| 	if (ret < 0) | ||||
| 		goto err_mem; | ||||
| 
 | ||||
| 	pm_runtime_enable(&pdev->dev); | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * DP should be probed first so that the zynqmp_disp can set the output | ||||
| 	 * format accordingly. | ||||
| 	 */ | ||||
| 	ret = zynqmp_dp_probe(dpsub, &dpsub->drm); | ||||
| 	ret = zynqmp_dp_probe(dpsub); | ||||
| 	if (ret) | ||||
| 		goto err_pm; | ||||
| 
 | ||||
| 	ret = zynqmp_disp_probe(dpsub, &dpsub->drm); | ||||
| 	ret = zynqmp_disp_probe(dpsub); | ||||
| 	if (ret) | ||||
| 		goto err_dp; | ||||
| 
 | ||||
| 	ret = zynqmp_dpsub_drm_init(dpsub); | ||||
| 	if (ret) | ||||
| 		goto err_disp; | ||||
| 	if (dpsub->dma_enabled) { | ||||
| 		ret = zynqmp_dpsub_drm_init(dpsub); | ||||
| 		if (ret) | ||||
| 			goto err_disp; | ||||
| 	} else { | ||||
| 		drm_bridge_add(dpsub->bridge); | ||||
| 	} | ||||
| 
 | ||||
| 	dev_info(&pdev->dev, "ZynqMP DisplayPort Subsystem driver probed"); | ||||
| 
 | ||||
|  | @ -241,17 +275,19 @@ err_pm: | |||
| 	clk_disable_unprepare(dpsub->apb_clk); | ||||
| err_mem: | ||||
| 	of_reserved_mem_device_release(&pdev->dev); | ||||
| 	if (!dpsub->drm) | ||||
| 		zynqmp_dpsub_release(dpsub); | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| static int zynqmp_dpsub_remove(struct platform_device *pdev) | ||||
| { | ||||
| 	struct zynqmp_dpsub *dpsub = platform_get_drvdata(pdev); | ||||
| 	struct drm_device *drm = &dpsub->drm; | ||||
| 
 | ||||
| 	drm_dev_unregister(drm); | ||||
| 	drm_atomic_helper_shutdown(drm); | ||||
| 	drm_kms_helper_poll_fini(drm); | ||||
| 	if (dpsub->drm) | ||||
| 		zynqmp_dpsub_drm_cleanup(dpsub); | ||||
| 	else | ||||
| 		drm_bridge_remove(dpsub->bridge); | ||||
| 
 | ||||
| 	zynqmp_disp_remove(dpsub); | ||||
| 	zynqmp_dp_remove(dpsub); | ||||
|  | @ -260,6 +296,9 @@ static int zynqmp_dpsub_remove(struct platform_device *pdev) | |||
| 	clk_disable_unprepare(dpsub->apb_clk); | ||||
| 	of_reserved_mem_device_release(&pdev->dev); | ||||
| 
 | ||||
| 	if (!dpsub->drm) | ||||
| 		zynqmp_dpsub_release(dpsub); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
|  | @ -267,7 +306,10 @@ static void zynqmp_dpsub_shutdown(struct platform_device *pdev) | |||
| { | ||||
| 	struct zynqmp_dpsub *dpsub = platform_get_drvdata(pdev); | ||||
| 
 | ||||
| 	drm_atomic_helper_shutdown(&dpsub->drm); | ||||
| 	if (!dpsub->drm) | ||||
| 		return; | ||||
| 
 | ||||
| 	drm_atomic_helper_shutdown(&dpsub->drm->dev); | ||||
| } | ||||
| 
 | ||||
| static const struct of_device_id zynqmp_dpsub_of_match[] = { | ||||
|  |  | |||
|  | @ -14,9 +14,23 @@ | |||
| 
 | ||||
| struct clk; | ||||
| struct device; | ||||
| struct drm_device; | ||||
| struct drm_bridge; | ||||
| struct zynqmp_disp; | ||||
| struct zynqmp_disp_layer; | ||||
| struct zynqmp_dp; | ||||
| struct zynqmp_dpsub_drm; | ||||
| 
 | ||||
| #define ZYNQMP_DPSUB_NUM_LAYERS				2 | ||||
| 
 | ||||
| enum zynqmp_dpsub_port { | ||||
| 	ZYNQMP_DPSUB_PORT_LIVE_VIDEO, | ||||
| 	ZYNQMP_DPSUB_PORT_LIVE_GFX, | ||||
| 	ZYNQMP_DPSUB_PORT_LIVE_AUDIO, | ||||
| 	ZYNQMP_DPSUB_PORT_OUT_VIDEO, | ||||
| 	ZYNQMP_DPSUB_PORT_OUT_AUDIO, | ||||
| 	ZYNQMP_DPSUB_PORT_OUT_DP, | ||||
| 	ZYNQMP_DPSUB_NUM_PORTS, | ||||
| }; | ||||
| 
 | ||||
| enum zynqmp_dpsub_format { | ||||
| 	ZYNQMP_DPSUB_FORMAT_RGB, | ||||
|  | @ -27,28 +41,46 @@ enum zynqmp_dpsub_format { | |||
| 
 | ||||
| /**
 | ||||
|  * struct zynqmp_dpsub - ZynqMP DisplayPort Subsystem | ||||
|  * @drm: The DRM/KMS device | ||||
|  * @dev: The physical device | ||||
|  * @apb_clk: The APB clock | ||||
|  * @vid_clk: Video clock | ||||
|  * @vid_clk_from_ps: True of the video clock comes from PS, false from PL | ||||
|  * @aud_clk: Audio clock | ||||
|  * @aud_clk_from_ps: True of the audio clock comes from PS, false from PL | ||||
|  * @connected_ports: Bitmask of connected ports in the device tree | ||||
|  * @dma_enabled: True if the DMA interface is enabled, false if the DPSUB is | ||||
|  *	driven by the live input | ||||
|  * @drm: The DRM/KMS device data | ||||
|  * @bridge: The DP encoder bridge | ||||
|  * @disp: The display controller | ||||
|  * @dp: The DisplayPort controller | ||||
|  * @dma_align: DMA alignment constraint (must be a power of 2) | ||||
|  */ | ||||
| struct zynqmp_dpsub { | ||||
| 	struct drm_device drm; | ||||
| 	struct device *dev; | ||||
| 
 | ||||
| 	struct clk *apb_clk; | ||||
| 	struct clk *vid_clk; | ||||
| 	bool vid_clk_from_ps; | ||||
| 	struct clk *aud_clk; | ||||
| 	bool aud_clk_from_ps; | ||||
| 
 | ||||
| 	unsigned int connected_ports; | ||||
| 	bool dma_enabled; | ||||
| 
 | ||||
| 	struct zynqmp_dpsub_drm *drm; | ||||
| 	struct drm_bridge *bridge; | ||||
| 
 | ||||
| 	struct zynqmp_disp *disp; | ||||
| 	struct zynqmp_disp_layer *layers[ZYNQMP_DPSUB_NUM_LAYERS]; | ||||
| 	struct zynqmp_dp *dp; | ||||
| 
 | ||||
| 	unsigned int dma_align; | ||||
| }; | ||||
| 
 | ||||
| static inline struct zynqmp_dpsub *to_zynqmp_dpsub(struct drm_device *drm) | ||||
| { | ||||
| 	return container_of(drm, struct zynqmp_dpsub, drm); | ||||
| } | ||||
| bool zynqmp_dpsub_audio_enabled(struct zynqmp_dpsub *dpsub); | ||||
| unsigned int zynqmp_dpsub_get_audio_clk_rate(struct zynqmp_dpsub *dpsub); | ||||
| 
 | ||||
| void zynqmp_dpsub_release(struct zynqmp_dpsub *dpsub); | ||||
| 
 | ||||
| #endif /* _ZYNQMP_DPSUB_H_ */ | ||||
|  |  | |||
							
								
								
									
										534
									
								
								drivers/gpu/drm/xlnx/zynqmp_kms.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										534
									
								
								drivers/gpu/drm/xlnx/zynqmp_kms.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,534 @@ | |||
| // SPDX-License-Identifier: GPL-2.0
 | ||||
| /*
 | ||||
|  * ZynqMP DisplayPort Subsystem - KMS API | ||||
|  * | ||||
|  * Copyright (C) 2017 - 2021 Xilinx, Inc. | ||||
|  * | ||||
|  * Authors: | ||||
|  * - Hyun Woo Kwon <hyun.kwon@xilinx.com> | ||||
|  * - Laurent Pinchart <laurent.pinchart@ideasonboard.com> | ||||
|  */ | ||||
| 
 | ||||
| #include <drm/drm_atomic.h> | ||||
| #include <drm/drm_atomic_helper.h> | ||||
| #include <drm/drm_blend.h> | ||||
| #include <drm/drm_bridge.h> | ||||
| #include <drm/drm_bridge_connector.h> | ||||
| #include <drm/drm_connector.h> | ||||
| #include <drm/drm_crtc.h> | ||||
| #include <drm/drm_device.h> | ||||
| #include <drm/drm_drv.h> | ||||
| #include <drm/drm_encoder.h> | ||||
| #include <drm/drm_fb_helper.h> | ||||
| #include <drm/drm_fourcc.h> | ||||
| #include <drm/drm_framebuffer.h> | ||||
| #include <drm/drm_gem_dma_helper.h> | ||||
| #include <drm/drm_gem_framebuffer_helper.h> | ||||
| #include <drm/drm_managed.h> | ||||
| #include <drm/drm_mode_config.h> | ||||
| #include <drm/drm_plane.h> | ||||
| #include <drm/drm_plane_helper.h> | ||||
| #include <drm/drm_probe_helper.h> | ||||
| #include <drm/drm_simple_kms_helper.h> | ||||
| #include <drm/drm_vblank.h> | ||||
| 
 | ||||
| #include <linux/clk.h> | ||||
| #include <linux/delay.h> | ||||
| #include <linux/pm_runtime.h> | ||||
| #include <linux/spinlock.h> | ||||
| 
 | ||||
| #include "zynqmp_disp.h" | ||||
| #include "zynqmp_dp.h" | ||||
| #include "zynqmp_dpsub.h" | ||||
| #include "zynqmp_kms.h" | ||||
| 
 | ||||
| static inline struct zynqmp_dpsub *to_zynqmp_dpsub(struct drm_device *drm) | ||||
| { | ||||
| 	return container_of(drm, struct zynqmp_dpsub_drm, dev)->dpsub; | ||||
| } | ||||
| 
 | ||||
| /* -----------------------------------------------------------------------------
 | ||||
|  * DRM Planes | ||||
|  */ | ||||
| 
 | ||||
| static int zynqmp_dpsub_plane_atomic_check(struct drm_plane *plane, | ||||
| 					   struct drm_atomic_state *state) | ||||
| { | ||||
| 	struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, | ||||
| 										 plane); | ||||
| 	struct drm_crtc_state *crtc_state; | ||||
| 
 | ||||
| 	if (!new_plane_state->crtc) | ||||
| 		return 0; | ||||
| 
 | ||||
| 	crtc_state = drm_atomic_get_crtc_state(state, new_plane_state->crtc); | ||||
| 	if (IS_ERR(crtc_state)) | ||||
| 		return PTR_ERR(crtc_state); | ||||
| 
 | ||||
| 	return drm_atomic_helper_check_plane_state(new_plane_state, | ||||
| 						   crtc_state, | ||||
| 						   DRM_PLANE_NO_SCALING, | ||||
| 						   DRM_PLANE_NO_SCALING, | ||||
| 						   false, false); | ||||
| } | ||||
| 
 | ||||
| static void zynqmp_dpsub_plane_atomic_disable(struct drm_plane *plane, | ||||
| 					      struct drm_atomic_state *state) | ||||
| { | ||||
| 	struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state, | ||||
| 									   plane); | ||||
| 	struct zynqmp_dpsub *dpsub = to_zynqmp_dpsub(plane->dev); | ||||
| 	struct zynqmp_disp_layer *layer = dpsub->layers[plane->index]; | ||||
| 
 | ||||
| 	if (!old_state->fb) | ||||
| 		return; | ||||
| 
 | ||||
| 	zynqmp_disp_layer_disable(layer); | ||||
| 
 | ||||
| 	if (plane->index == ZYNQMP_DPSUB_LAYER_GFX) | ||||
| 		zynqmp_disp_blend_set_global_alpha(dpsub->disp, false, | ||||
| 						   plane->state->alpha >> 8); | ||||
| } | ||||
| 
 | ||||
| static void zynqmp_dpsub_plane_atomic_update(struct drm_plane *plane, | ||||
| 					     struct drm_atomic_state *state) | ||||
| { | ||||
| 	struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state, plane); | ||||
| 	struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, plane); | ||||
| 	struct zynqmp_dpsub *dpsub = to_zynqmp_dpsub(plane->dev); | ||||
| 	struct zynqmp_disp_layer *layer = dpsub->layers[plane->index]; | ||||
| 	bool format_changed = false; | ||||
| 
 | ||||
| 	if (!old_state->fb || | ||||
| 	    old_state->fb->format->format != new_state->fb->format->format) | ||||
| 		format_changed = true; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * If the format has changed (including going from a previously | ||||
| 	 * disabled state to any format), reconfigure the format. Disable the | ||||
| 	 * plane first if needed. | ||||
| 	 */ | ||||
| 	if (format_changed) { | ||||
| 		if (old_state->fb) | ||||
| 			zynqmp_disp_layer_disable(layer); | ||||
| 
 | ||||
| 		zynqmp_disp_layer_set_format(layer, new_state->fb->format); | ||||
| 	} | ||||
| 
 | ||||
| 	zynqmp_disp_layer_update(layer, new_state); | ||||
| 
 | ||||
| 	if (plane->index == ZYNQMP_DPSUB_LAYER_GFX) | ||||
| 		zynqmp_disp_blend_set_global_alpha(dpsub->disp, true, | ||||
| 						   plane->state->alpha >> 8); | ||||
| 
 | ||||
| 	/* Enable or re-enable the plane if the format has changed. */ | ||||
| 	if (format_changed) | ||||
| 		zynqmp_disp_layer_enable(layer, ZYNQMP_DPSUB_LAYER_NONLIVE); | ||||
| } | ||||
| 
 | ||||
| static const struct drm_plane_helper_funcs zynqmp_dpsub_plane_helper_funcs = { | ||||
| 	.atomic_check		= zynqmp_dpsub_plane_atomic_check, | ||||
| 	.atomic_update		= zynqmp_dpsub_plane_atomic_update, | ||||
| 	.atomic_disable		= zynqmp_dpsub_plane_atomic_disable, | ||||
| }; | ||||
| 
 | ||||
| static const struct drm_plane_funcs zynqmp_dpsub_plane_funcs = { | ||||
| 	.update_plane		= drm_atomic_helper_update_plane, | ||||
| 	.disable_plane		= drm_atomic_helper_disable_plane, | ||||
| 	.destroy		= drm_plane_cleanup, | ||||
| 	.reset			= drm_atomic_helper_plane_reset, | ||||
| 	.atomic_duplicate_state	= drm_atomic_helper_plane_duplicate_state, | ||||
| 	.atomic_destroy_state	= drm_atomic_helper_plane_destroy_state, | ||||
| }; | ||||
| 
 | ||||
| static int zynqmp_dpsub_create_planes(struct zynqmp_dpsub *dpsub) | ||||
| { | ||||
| 	unsigned int i; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	for (i = 0; i < ARRAY_SIZE(dpsub->drm->planes); i++) { | ||||
| 		struct zynqmp_disp_layer *layer = dpsub->layers[i]; | ||||
| 		struct drm_plane *plane = &dpsub->drm->planes[i]; | ||||
| 		enum drm_plane_type type; | ||||
| 		unsigned int num_formats; | ||||
| 		u32 *formats; | ||||
| 
 | ||||
| 		formats = zynqmp_disp_layer_drm_formats(layer, &num_formats); | ||||
| 		if (!formats) | ||||
| 			return -ENOMEM; | ||||
| 
 | ||||
| 		/* Graphics layer is primary, and video layer is overlay. */ | ||||
| 		type = i == ZYNQMP_DPSUB_LAYER_VID | ||||
| 		     ? DRM_PLANE_TYPE_OVERLAY : DRM_PLANE_TYPE_PRIMARY; | ||||
| 		ret = drm_universal_plane_init(&dpsub->drm->dev, plane, 0, | ||||
| 					       &zynqmp_dpsub_plane_funcs, | ||||
| 					       formats, num_formats, | ||||
| 					       NULL, type, NULL); | ||||
| 		kfree(formats); | ||||
| 		if (ret) | ||||
| 			return ret; | ||||
| 
 | ||||
| 		drm_plane_helper_add(plane, &zynqmp_dpsub_plane_helper_funcs); | ||||
| 
 | ||||
| 		drm_plane_create_zpos_immutable_property(plane, i); | ||||
| 		if (i == ZYNQMP_DPSUB_LAYER_GFX) | ||||
| 			drm_plane_create_alpha_property(plane); | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| /* -----------------------------------------------------------------------------
 | ||||
|  * DRM CRTC | ||||
|  */ | ||||
| 
 | ||||
| static inline struct zynqmp_dpsub *crtc_to_dpsub(struct drm_crtc *crtc) | ||||
| { | ||||
| 	return container_of(crtc, struct zynqmp_dpsub_drm, crtc)->dpsub; | ||||
| } | ||||
| 
 | ||||
| static void zynqmp_dpsub_crtc_atomic_enable(struct drm_crtc *crtc, | ||||
| 					    struct drm_atomic_state *state) | ||||
| { | ||||
| 	struct zynqmp_dpsub *dpsub = crtc_to_dpsub(crtc); | ||||
| 	struct drm_display_mode *adjusted_mode = &crtc->state->adjusted_mode; | ||||
| 	int ret, vrefresh; | ||||
| 
 | ||||
| 	pm_runtime_get_sync(dpsub->dev); | ||||
| 
 | ||||
| 	zynqmp_disp_setup_clock(dpsub->disp, adjusted_mode->clock * 1000); | ||||
| 
 | ||||
| 	ret = clk_prepare_enable(dpsub->vid_clk); | ||||
| 	if (ret) { | ||||
| 		dev_err(dpsub->dev, "failed to enable a pixel clock\n"); | ||||
| 		pm_runtime_put_sync(dpsub->dev); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	zynqmp_disp_enable(dpsub->disp); | ||||
| 
 | ||||
| 	/* Delay of 3 vblank intervals for timing gen to be stable */ | ||||
| 	vrefresh = (adjusted_mode->clock * 1000) / | ||||
| 		   (adjusted_mode->vtotal * adjusted_mode->htotal); | ||||
| 	msleep(3 * 1000 / vrefresh); | ||||
| } | ||||
| 
 | ||||
| static void zynqmp_dpsub_crtc_atomic_disable(struct drm_crtc *crtc, | ||||
| 					     struct drm_atomic_state *state) | ||||
| { | ||||
| 	struct zynqmp_dpsub *dpsub = crtc_to_dpsub(crtc); | ||||
| 	struct drm_plane_state *old_plane_state; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Disable the plane if active. The old plane state can be NULL in the | ||||
| 	 * .shutdown() path if the plane is already disabled, skip | ||||
| 	 * zynqmp_disp_plane_atomic_disable() in that case. | ||||
| 	 */ | ||||
| 	old_plane_state = drm_atomic_get_old_plane_state(state, crtc->primary); | ||||
| 	if (old_plane_state) | ||||
| 		zynqmp_dpsub_plane_atomic_disable(crtc->primary, state); | ||||
| 
 | ||||
| 	zynqmp_disp_disable(dpsub->disp); | ||||
| 
 | ||||
| 	drm_crtc_vblank_off(crtc); | ||||
| 
 | ||||
| 	spin_lock_irq(&crtc->dev->event_lock); | ||||
| 	if (crtc->state->event) { | ||||
| 		drm_crtc_send_vblank_event(crtc, crtc->state->event); | ||||
| 		crtc->state->event = NULL; | ||||
| 	} | ||||
| 	spin_unlock_irq(&crtc->dev->event_lock); | ||||
| 
 | ||||
| 	clk_disable_unprepare(dpsub->vid_clk); | ||||
| 	pm_runtime_put_sync(dpsub->dev); | ||||
| } | ||||
| 
 | ||||
| static int zynqmp_dpsub_crtc_atomic_check(struct drm_crtc *crtc, | ||||
| 					  struct drm_atomic_state *state) | ||||
| { | ||||
| 	return drm_atomic_add_affected_planes(state, crtc); | ||||
| } | ||||
| 
 | ||||
| static void zynqmp_dpsub_crtc_atomic_begin(struct drm_crtc *crtc, | ||||
| 					   struct drm_atomic_state *state) | ||||
| { | ||||
| 	drm_crtc_vblank_on(crtc); | ||||
| } | ||||
| 
 | ||||
| static void zynqmp_dpsub_crtc_atomic_flush(struct drm_crtc *crtc, | ||||
| 					   struct drm_atomic_state *state) | ||||
| { | ||||
| 	if (crtc->state->event) { | ||||
| 		struct drm_pending_vblank_event *event; | ||||
| 
 | ||||
| 		/* Consume the flip_done event from atomic helper. */ | ||||
| 		event = crtc->state->event; | ||||
| 		crtc->state->event = NULL; | ||||
| 
 | ||||
| 		event->pipe = drm_crtc_index(crtc); | ||||
| 
 | ||||
| 		WARN_ON(drm_crtc_vblank_get(crtc) != 0); | ||||
| 
 | ||||
| 		spin_lock_irq(&crtc->dev->event_lock); | ||||
| 		drm_crtc_arm_vblank_event(crtc, event); | ||||
| 		spin_unlock_irq(&crtc->dev->event_lock); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static const struct drm_crtc_helper_funcs zynqmp_dpsub_crtc_helper_funcs = { | ||||
| 	.atomic_enable	= zynqmp_dpsub_crtc_atomic_enable, | ||||
| 	.atomic_disable	= zynqmp_dpsub_crtc_atomic_disable, | ||||
| 	.atomic_check	= zynqmp_dpsub_crtc_atomic_check, | ||||
| 	.atomic_begin	= zynqmp_dpsub_crtc_atomic_begin, | ||||
| 	.atomic_flush	= zynqmp_dpsub_crtc_atomic_flush, | ||||
| }; | ||||
| 
 | ||||
| static int zynqmp_dpsub_crtc_enable_vblank(struct drm_crtc *crtc) | ||||
| { | ||||
| 	struct zynqmp_dpsub *dpsub = crtc_to_dpsub(crtc); | ||||
| 
 | ||||
| 	zynqmp_dp_enable_vblank(dpsub->dp); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static void zynqmp_dpsub_crtc_disable_vblank(struct drm_crtc *crtc) | ||||
| { | ||||
| 	struct zynqmp_dpsub *dpsub = crtc_to_dpsub(crtc); | ||||
| 
 | ||||
| 	zynqmp_dp_disable_vblank(dpsub->dp); | ||||
| } | ||||
| 
 | ||||
| static const struct drm_crtc_funcs zynqmp_dpsub_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, | ||||
| 	.enable_vblank		= zynqmp_dpsub_crtc_enable_vblank, | ||||
| 	.disable_vblank		= zynqmp_dpsub_crtc_disable_vblank, | ||||
| }; | ||||
| 
 | ||||
| static int zynqmp_dpsub_create_crtc(struct zynqmp_dpsub *dpsub) | ||||
| { | ||||
| 	struct drm_plane *plane = &dpsub->drm->planes[ZYNQMP_DPSUB_LAYER_GFX]; | ||||
| 	struct drm_crtc *crtc = &dpsub->drm->crtc; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	ret = drm_crtc_init_with_planes(&dpsub->drm->dev, crtc, plane, | ||||
| 					NULL, &zynqmp_dpsub_crtc_funcs, NULL); | ||||
| 	if (ret < 0) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	drm_crtc_helper_add(crtc, &zynqmp_dpsub_crtc_helper_funcs); | ||||
| 
 | ||||
| 	/* Start with vertical blanking interrupt reporting disabled. */ | ||||
| 	drm_crtc_vblank_off(crtc); | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static void zynqmp_dpsub_map_crtc_to_plane(struct zynqmp_dpsub *dpsub) | ||||
| { | ||||
| 	u32 possible_crtcs = drm_crtc_mask(&dpsub->drm->crtc); | ||||
| 	unsigned int i; | ||||
| 
 | ||||
| 	for (i = 0; i < ARRAY_SIZE(dpsub->drm->planes); i++) | ||||
| 		dpsub->drm->planes[i].possible_crtcs = possible_crtcs; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * zynqmp_dpsub_drm_handle_vblank - Handle the vblank event | ||||
|  * @dpsub: DisplayPort subsystem | ||||
|  * | ||||
|  * This function handles the vblank interrupt, and sends an event to | ||||
|  * CRTC object. This will be called by the DP vblank interrupt handler. | ||||
|  */ | ||||
| void zynqmp_dpsub_drm_handle_vblank(struct zynqmp_dpsub *dpsub) | ||||
| { | ||||
| 	drm_crtc_handle_vblank(&dpsub->drm->crtc); | ||||
| } | ||||
| 
 | ||||
| /* -----------------------------------------------------------------------------
 | ||||
|  * Dumb Buffer & Framebuffer Allocation | ||||
|  */ | ||||
| 
 | ||||
| static int zynqmp_dpsub_dumb_create(struct drm_file *file_priv, | ||||
| 				    struct drm_device *drm, | ||||
| 				    struct drm_mode_create_dumb *args) | ||||
| { | ||||
| 	struct zynqmp_dpsub *dpsub = to_zynqmp_dpsub(drm); | ||||
| 	unsigned int pitch = DIV_ROUND_UP(args->width * args->bpp, 8); | ||||
| 
 | ||||
| 	/* Enforce the alignment constraints of the DMA engine. */ | ||||
| 	args->pitch = ALIGN(pitch, dpsub->dma_align); | ||||
| 
 | ||||
| 	return drm_gem_dma_dumb_create_internal(file_priv, drm, args); | ||||
| } | ||||
| 
 | ||||
| static struct drm_framebuffer * | ||||
| zynqmp_dpsub_fb_create(struct drm_device *drm, struct drm_file *file_priv, | ||||
| 		       const struct drm_mode_fb_cmd2 *mode_cmd) | ||||
| { | ||||
| 	struct zynqmp_dpsub *dpsub = to_zynqmp_dpsub(drm); | ||||
| 	struct drm_mode_fb_cmd2 cmd = *mode_cmd; | ||||
| 	unsigned int i; | ||||
| 
 | ||||
| 	/* Enforce the alignment constraints of the DMA engine. */ | ||||
| 	for (i = 0; i < ARRAY_SIZE(cmd.pitches); ++i) | ||||
| 		cmd.pitches[i] = ALIGN(cmd.pitches[i], dpsub->dma_align); | ||||
| 
 | ||||
| 	return drm_gem_fb_create(drm, file_priv, &cmd); | ||||
| } | ||||
| 
 | ||||
| static const struct drm_mode_config_funcs zynqmp_dpsub_mode_config_funcs = { | ||||
| 	.fb_create		= zynqmp_dpsub_fb_create, | ||||
| 	.atomic_check		= drm_atomic_helper_check, | ||||
| 	.atomic_commit		= drm_atomic_helper_commit, | ||||
| }; | ||||
| 
 | ||||
| /* -----------------------------------------------------------------------------
 | ||||
|  * DRM/KMS Driver | ||||
|  */ | ||||
| 
 | ||||
| DEFINE_DRM_GEM_DMA_FOPS(zynqmp_dpsub_drm_fops); | ||||
| 
 | ||||
| static const struct drm_driver zynqmp_dpsub_drm_driver = { | ||||
| 	.driver_features		= DRIVER_MODESET | DRIVER_GEM | | ||||
| 					  DRIVER_ATOMIC, | ||||
| 
 | ||||
| 	DRM_GEM_DMA_DRIVER_OPS_WITH_DUMB_CREATE(zynqmp_dpsub_dumb_create), | ||||
| 
 | ||||
| 	.fops				= &zynqmp_dpsub_drm_fops, | ||||
| 
 | ||||
| 	.name				= "zynqmp-dpsub", | ||||
| 	.desc				= "Xilinx DisplayPort Subsystem Driver", | ||||
| 	.date				= "20130509", | ||||
| 	.major				= 1, | ||||
| 	.minor				= 0, | ||||
| }; | ||||
| 
 | ||||
| static int zynqmp_dpsub_kms_init(struct zynqmp_dpsub *dpsub) | ||||
| { | ||||
| 	struct drm_encoder *encoder = &dpsub->drm->encoder; | ||||
| 	struct drm_connector *connector; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	/* Create the planes and the CRTC. */ | ||||
| 	ret = zynqmp_dpsub_create_planes(dpsub); | ||||
| 	if (ret) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	ret = zynqmp_dpsub_create_crtc(dpsub); | ||||
| 	if (ret < 0) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	zynqmp_dpsub_map_crtc_to_plane(dpsub); | ||||
| 
 | ||||
| 	/* Create the encoder and attach the bridge. */ | ||||
| 	encoder->possible_crtcs |= drm_crtc_mask(&dpsub->drm->crtc); | ||||
| 	drm_simple_encoder_init(&dpsub->drm->dev, encoder, DRM_MODE_ENCODER_NONE); | ||||
| 
 | ||||
| 	ret = drm_bridge_attach(encoder, dpsub->bridge, NULL, | ||||
| 				DRM_BRIDGE_ATTACH_NO_CONNECTOR); | ||||
| 	if (ret) { | ||||
| 		dev_err(dpsub->dev, "failed to attach bridge to encoder\n"); | ||||
| 		return ret; | ||||
| 	} | ||||
| 
 | ||||
| 	/* Create the connector for the chain of bridges. */ | ||||
| 	connector = drm_bridge_connector_init(&dpsub->drm->dev, encoder); | ||||
| 	if (IS_ERR(connector)) { | ||||
| 		dev_err(dpsub->dev, "failed to created connector\n"); | ||||
| 		return PTR_ERR(connector); | ||||
| 	} | ||||
| 
 | ||||
| 	ret = drm_connector_attach_encoder(connector, encoder); | ||||
| 	if (ret < 0) { | ||||
| 		dev_err(dpsub->dev, "failed to attach connector to encoder\n"); | ||||
| 		return ret; | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| static void zynqmp_dpsub_drm_release(struct drm_device *drm, void *res) | ||||
| { | ||||
| 	struct zynqmp_dpsub_drm *dpdrm = res; | ||||
| 
 | ||||
| 	zynqmp_dpsub_release(dpdrm->dpsub); | ||||
| } | ||||
| 
 | ||||
| int zynqmp_dpsub_drm_init(struct zynqmp_dpsub *dpsub) | ||||
| { | ||||
| 	struct zynqmp_dpsub_drm *dpdrm; | ||||
| 	struct drm_device *drm; | ||||
| 	int ret; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Allocate the drm_device and immediately add a cleanup action to | ||||
| 	 * release the zynqmp_dpsub instance. If any of those operations fail, | ||||
| 	 * dpsub->drm will remain NULL, which tells the caller that it must | ||||
| 	 * cleanup manually. | ||||
| 	 */ | ||||
| 	dpdrm = devm_drm_dev_alloc(dpsub->dev, &zynqmp_dpsub_drm_driver, | ||||
| 				   struct zynqmp_dpsub_drm, dev); | ||||
| 	if (IS_ERR(dpdrm)) | ||||
| 		return PTR_ERR(dpdrm); | ||||
| 
 | ||||
| 	dpdrm->dpsub = dpsub; | ||||
| 	drm = &dpdrm->dev; | ||||
| 
 | ||||
| 	ret = drmm_add_action(drm, zynqmp_dpsub_drm_release, dpdrm); | ||||
| 	if (ret < 0) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	dpsub->drm = dpdrm; | ||||
| 
 | ||||
| 	/* Initialize mode config, vblank and the KMS poll helper. */ | ||||
| 	ret = drmm_mode_config_init(drm); | ||||
| 	if (ret < 0) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	drm->mode_config.funcs = &zynqmp_dpsub_mode_config_funcs; | ||||
| 	drm->mode_config.min_width = 0; | ||||
| 	drm->mode_config.min_height = 0; | ||||
| 	drm->mode_config.max_width = ZYNQMP_DISP_MAX_WIDTH; | ||||
| 	drm->mode_config.max_height = ZYNQMP_DISP_MAX_HEIGHT; | ||||
| 
 | ||||
| 	ret = drm_vblank_init(drm, 1); | ||||
| 	if (ret) | ||||
| 		return ret; | ||||
| 
 | ||||
| 	drm_kms_helper_poll_init(drm); | ||||
| 
 | ||||
| 	ret = zynqmp_dpsub_kms_init(dpsub); | ||||
| 	if (ret < 0) | ||||
| 		goto err_poll_fini; | ||||
| 
 | ||||
| 	/* Reset all components and register the DRM device. */ | ||||
| 	drm_mode_config_reset(drm); | ||||
| 
 | ||||
| 	ret = drm_dev_register(drm, 0); | ||||
| 	if (ret < 0) | ||||
| 		goto err_poll_fini; | ||||
| 
 | ||||
| 	/* Initialize fbdev generic emulation. */ | ||||
| 	drm_fbdev_generic_setup(drm, 24); | ||||
| 
 | ||||
| 	return 0; | ||||
| 
 | ||||
| err_poll_fini: | ||||
| 	drm_kms_helper_poll_fini(drm); | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| void zynqmp_dpsub_drm_cleanup(struct zynqmp_dpsub *dpsub) | ||||
| { | ||||
| 	struct drm_device *drm = &dpsub->drm->dev; | ||||
| 
 | ||||
| 	drm_dev_unregister(drm); | ||||
| 	drm_atomic_helper_shutdown(drm); | ||||
| 	drm_kms_helper_poll_fini(drm); | ||||
| } | ||||
							
								
								
									
										46
									
								
								drivers/gpu/drm/xlnx/zynqmp_kms.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								drivers/gpu/drm/xlnx/zynqmp_kms.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,46 @@ | |||
| /* SPDX-License-Identifier: GPL-2.0 */ | ||||
| /*
 | ||||
|  * ZynqMP DisplayPort Subsystem - KMS API | ||||
|  * | ||||
|  * Copyright (C) 2017 - 2021 Xilinx, Inc. | ||||
|  * | ||||
|  * Authors: | ||||
|  * - Hyun Woo Kwon <hyun.kwon@xilinx.com> | ||||
|  * - Laurent Pinchart <laurent.pinchart@ideasonboard.com> | ||||
|  */ | ||||
| 
 | ||||
| #ifndef _ZYNQMP_KMS_H_ | ||||
| #define _ZYNQMP_KMS_H_ | ||||
| 
 | ||||
| #include <drm/drm_crtc.h> | ||||
| #include <drm/drm_device.h> | ||||
| #include <drm/drm_encoder.h> | ||||
| #include <drm/drm_plane.h> | ||||
| 
 | ||||
| #include "zynqmp_dpsub.h" | ||||
| 
 | ||||
| struct zynqmp_dpsub; | ||||
| 
 | ||||
| /**
 | ||||
|  * struct zynqmp_dpsub - ZynqMP DisplayPort Subsystem DRM/KMS data | ||||
|  * @dpsub: Backpointer to the DisplayPort subsystem | ||||
|  * @drm: The DRM/KMS device | ||||
|  * @planes: The DRM planes | ||||
|  * @crtc: The DRM CRTC | ||||
|  * @encoder: The dummy DRM encoder | ||||
|  */ | ||||
| struct zynqmp_dpsub_drm { | ||||
| 	struct zynqmp_dpsub *dpsub; | ||||
| 
 | ||||
| 	struct drm_device dev; | ||||
| 	struct drm_plane planes[ZYNQMP_DPSUB_NUM_LAYERS]; | ||||
| 	struct drm_crtc crtc; | ||||
| 	struct drm_encoder encoder; | ||||
| }; | ||||
| 
 | ||||
| void zynqmp_dpsub_drm_handle_vblank(struct zynqmp_dpsub *dpsub); | ||||
| 
 | ||||
| int zynqmp_dpsub_drm_init(struct zynqmp_dpsub *dpsub); | ||||
| void zynqmp_dpsub_drm_cleanup(struct zynqmp_dpsub *dpsub); | ||||
| 
 | ||||
| #endif /* _ZYNQMP_KMS_H_ */ | ||||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Dave Airlie
						Dave Airlie