diff options
author | Dave Airlie <airlied@redhat.com> | 2022-10-27 14:44:02 +1000 |
---|---|---|
committer | Dave Airlie <airlied@redhat.com> | 2022-10-27 14:44:15 +1000 |
commit | 7f7a942c0a338c4a2a7b359bdb2b68e9896122ec (patch) | |
tree | 011761cb7040039ec28f132dd72809c60c019dd3 /drivers/gpu | |
parent | 0a20a3ea4259ae761597aacd8a088d7e1304e804 (diff) | |
parent | ddcb8fa6514f2baf0fdb45e7ba12fbf3abb112c7 (diff) |
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
Diffstat (limited to 'drivers/gpu')
-rw-r--r-- | drivers/gpu/drm/xlnx/Makefile | 2 | ||||
-rw-r--r-- | drivers/gpu/drm/xlnx/zynqmp_disp.c | 646 | ||||
-rw-r--r-- | drivers/gpu/drm/xlnx/zynqmp_disp.h | 48 | ||||
-rw-r--r-- | drivers/gpu/drm/xlnx/zynqmp_dp.c | 476 | ||||
-rw-r--r-- | drivers/gpu/drm/xlnx/zynqmp_dp.h | 4 | ||||
-rw-r--r-- | drivers/gpu/drm/xlnx/zynqmp_dpsub.c | 300 | ||||
-rw-r--r-- | drivers/gpu/drm/xlnx/zynqmp_dpsub.h | 46 | ||||
-rw-r--r-- | drivers/gpu/drm/xlnx/zynqmp_kms.c | 534 | ||||
-rw-r--r-- | drivers/gpu/drm/xlnx/zynqmp_kms.h | 46 |
9 files changed, 1214 insertions, 888 deletions
diff --git a/drivers/gpu/drm/xlnx/Makefile b/drivers/gpu/drm/xlnx/Makefile index 51c24b72217b..ea1422a39502 100644 --- a/drivers/gpu/drm/xlnx/Makefile +++ b/drivers/gpu/drm/xlnx/Makefile @@ -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 diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.c b/drivers/gpu/drm/xlnx/zynqmp_disp.c index bbb365f2d087..3b87eebddc97 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_disp.c +++ b/drivers/gpu/drm/xlnx/zynqmp_disp.c @@ -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,46 +62,23 @@ #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 * @xt: Interleaved DMA descriptor template @@ -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 */ @@ -1006,19 +873,46 @@ zynqmp_disp_layer_find_format(struct zynqmp_disp_layer *layer, } /** + * 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) -{ - 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) +/** + * 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) { - 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; + + if (disp->dpsub->dma_enabled) { + struct zynqmp_disp_layer *layer; - layer = &disp->layers[ZYNQMP_DISP_LAYER_VID]; - dpsub->dma_align = 1 << layer->dmas[0].chan->device->copy_align; + 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) diff --git a/drivers/gpu/drm/xlnx/zynqmp_disp.h b/drivers/gpu/drm/xlnx/zynqmp_disp.h index f402901afb23..123cffac08be 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_disp.h +++ b/drivers/gpu/drm/xlnx/zynqmp_disp.h @@ -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, +}; + +/** + * 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_drm_init(struct zynqmp_dpsub *dpsub); -int zynqmp_disp_probe(struct zynqmp_dpsub *dpsub, struct drm_device *drm); +int zynqmp_disp_probe(struct zynqmp_dpsub *dpsub); void zynqmp_disp_remove(struct zynqmp_dpsub *dpsub); #endif /* _ZYNQMP_DISP_H_ */ diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.c b/drivers/gpu/drm/xlnx/zynqmp_dp.c index d14612b34796..7c9ae167eac7 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dp.c +++ b/drivers/gpu/drm/xlnx/zynqmp_dp.c @@ -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) -{ - return container_of(encoder, struct zynqmp_dp, encoder); -} - -static inline struct zynqmp_dp *connector_to_dp(struct drm_connector *connector) +static inline struct zynqmp_dp *bridge_to_dp(struct drm_bridge *bridge) { - 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,97 +1273,114 @@ 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); - struct zynqmp_dp_link_config *link_config = &dp->link_config; - u32 state, i; - int ret; + 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; - /* - * This is from heuristic. It takes some delay (ex, 100 ~ 500 msec) to - * get the HPD signal with some monitors. - */ - for (i = 0; i < 10; i++) { - state = zynqmp_dp_read(dp, ZYNQMP_DP_INTERRUPT_SIGNAL_STATE); - if (state & ZYNQMP_DP_INTERRUPT_SIGNAL_STATE_HPD) - break; - msleep(100); - } + layer = dp->dpsub->layers[layer_id]; - if (state & ZYNQMP_DP_INTERRUPT_SIGNAL_STATE_HPD) { - ret = drm_dp_dpcd_read(&dp->aux, 0x0, dp->dpcd, - sizeof(dp->dpcd)); - if (ret < 0) { - dev_dbg(dp->dev, "DPCD read failed"); - goto disconnected; - } + /* 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); - link_config->max_rate = min_t(int, - drm_dp_max_link_rate(dp->dpcd), - DP_HIGH_BIT_RATE2); - link_config->max_lanes = min_t(u8, - drm_dp_max_lane_count(dp->dpcd), - dp->num_lanes); + 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); - dp->status = connector_status_connected; - return connector_status_connected; - } + zynqmp_disp_enable(dp->dpsub->disp); +} -disconnected: - dp->status = connector_status_disconnected; - return connector_status_disconnected; +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); } -static int zynqmp_dp_connector_get_modes(struct drm_connector *connector) +/* ----------------------------------------------------------------------------- + * DRM Bridge + */ + +static int zynqmp_dp_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) { - struct zynqmp_dp *dp = connector_to_dp(connector); - struct edid *edid; + struct zynqmp_dp *dp = bridge_to_dp(bridge); int ret; - edid = drm_get_edid(connector, &dp->aux.ddc); - if (!edid) - return 0; + /* 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; + } - drm_connector_update_edid_property(connector, edid); - ret = drm_add_edid_modes(connector, edid); - kfree(edid); + 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 struct drm_encoder * -zynqmp_dp_connector_best_encoder(struct drm_connector *connector) +static void zynqmp_dp_bridge_detach(struct drm_bridge *bridge) { - struct zynqmp_dp *dp = connector_to_dp(connector); + struct zynqmp_dp *dp = bridge_to_dp(bridge); - return &dp->encoder; + zynqmp_dp_aux_cleanup(dp); } -static int zynqmp_dp_connector_mode_valid(struct drm_connector *connector, - struct drm_display_mode *mode) +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 = 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; + struct zynqmp_dp *dp = bridge_to_dp(bridge); int rate; if (mode->clock > ZYNQMP_MAX_FREQ) { - dev_dbg(dp->dev, "filtered the mode, %s,for high pixel rate\n", + 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(max_rate, max_lanes, bpp); + 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 the mode, %s,for high pixel rate\n", + dev_dbg(dp->dev, "filtered mode %s for high pixel rate\n", mode->name); drm_mode_debug_printmodeline(mode); return MODE_CLOCK_HIGH; @@ -1380,36 +1389,62 @@ static int zynqmp_dp_connector_mode_valid(struct drm_connector *connector, 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) +static void zynqmp_dp_bridge_atomic_enable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) { - struct zynqmp_dp *dp = encoder_to_dp(encoder); + 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 ret = 0; + 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_disp_audio_enabled(dp->dpsub->disp)) + 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) { @@ -1432,9 +1467,10 @@ static void zynqmp_dp_encoder_enable(struct drm_encoder *encoder) zynqmp_dp_write(dp, ZYNQMP_DP_MAIN_STREAM_ENABLE, 1); } -static void zynqmp_dp_encoder_disable(struct drm_encoder *encoder) +static void zynqmp_dp_bridge_atomic_disable(struct drm_bridge *bridge, + struct drm_bridge_state *old_bridge_state) { - struct zynqmp_dp *dp = encoder_to_dp(encoder); + struct zynqmp_dp *dp = bridge_to_dp(bridge); dp->enabled = false; cancel_delayed_work(&dp->hpd_work); @@ -1442,49 +1478,22 @@ static void zynqmp_dp_encoder_disable(struct drm_encoder *encoder) 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)) + if (zynqmp_dpsub_audio_enabled(dp->dpsub)) 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); + zynqmp_dp_disp_disable(dp, old_bridge_state); - /* 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); + pm_runtime_put_sync(dp->dev); } #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) +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; @@ -1497,7 +1506,7 @@ zynqmp_dp_encoder_atomic_check(struct drm_encoder *encoder, int vrefresh = (adjusted_mode->clock * 1000) / (adjusted_mode->vtotal * adjusted_mode->htotal); - dev_dbg(encoder->dev->dev, "hbackporch adjusted: %d to %d", + 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; @@ -1508,11 +1517,68 @@ zynqmp_dp_encoder_atomic_check(struct drm_encoder *encoder, 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 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; + + /* + * This is from heuristic. It takes some delay (ex, 100 ~ 500 msec) to + * get the HPD signal with some monitors. + */ + for (i = 0; i < 10; i++) { + state = zynqmp_dp_read(dp, ZYNQMP_DP_INTERRUPT_SIGNAL_STATE); + if (state & ZYNQMP_DP_INTERRUPT_SIGNAL_STATE_HPD) + break; + msleep(100); + } + + if (state & ZYNQMP_DP_INTERRUPT_SIGNAL_STATE_HPD) { + ret = drm_dp_dpcd_read(&dp->aux, 0x0, dp->dpcd, + sizeof(dp->dpcd)); + if (ret < 0) { + dev_dbg(dp->dev, "DPCD read failed"); + goto disconnected; + } + + link_config->max_rate = min_t(int, + drm_dp_max_link_rate(dp->dpcd), + DP_HIGH_BIT_RATE2); + link_config->max_lanes = min_t(u8, + drm_dp_max_lane_count(dp->dpcd), + dp->num_lanes); + + dp->status = connector_status_connected; + return connector_status_connected; + } + +disconnected: + dp->status = connector_status_disconnected; + return connector_status_disconnected; +} + +static struct edid *zynqmp_dp_bridge_get_edid(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct zynqmp_dp *dp = bridge_to_dp(bridge); + + return drm_get_edid(connector, &dp->aux.ddc); +} + +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; - - dp = container_of(work, struct zynqmp_dp, hpd_work.work); + struct zynqmp_dp *dp = container_of(work, struct zynqmp_dp, + hpd_work.work); + enum drm_connector_status status; - 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); diff --git a/drivers/gpu/drm/xlnx/zynqmp_dp.h b/drivers/gpu/drm/xlnx/zynqmp_dp.h index 4507740093f6..f077d7fbd0ad 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dp.h +++ b/drivers/gpu/drm/xlnx/zynqmp_dp.h @@ -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_ */ diff --git a/drivers/gpu/drm/xlnx/zynqmp_dpsub.c b/drivers/gpu/drm/xlnx/zynqmp_dpsub.c index 1de2d927c32b..bab862484d42 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dpsub.c +++ b/drivers/gpu/drm/xlnx/zynqmp_dpsub.c @@ -12,191 +12,217 @@ #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" +#include "zynqmp_kms.h" /* ----------------------------------------------------------------------------- - * Dumb Buffer & Framebuffer Allocation + * Power Management */ -static int zynqmp_dpsub_dumb_create(struct drm_file *file_priv, - struct drm_device *drm, - struct drm_mode_create_dumb *args) +static int __maybe_unused zynqmp_dpsub_suspend(struct device *dev) { - struct zynqmp_dpsub *dpsub = to_zynqmp_dpsub(drm); - unsigned int pitch = DIV_ROUND_UP(args->width * args->bpp, 8); + struct zynqmp_dpsub *dpsub = dev_get_drvdata(dev); - /* Enforce the alignment constraints of the DMA engine. */ - args->pitch = ALIGN(pitch, dpsub->dma_align); + if (!dpsub->drm) + return 0; - return drm_gem_dma_dumb_create_internal(file_priv, drm, args); + return drm_mode_config_helper_suspend(&dpsub->drm->dev); } -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) +static int __maybe_unused zynqmp_dpsub_resume(struct device *dev) { - struct zynqmp_dpsub *dpsub = to_zynqmp_dpsub(drm); - struct drm_mode_fb_cmd2 cmd = *mode_cmd; - unsigned int i; + struct zynqmp_dpsub *dpsub = dev_get_drvdata(dev); - /* 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); + if (!dpsub->drm) + return 0; - return drm_gem_fb_create(drm, file_priv, &cmd); + return drm_mode_config_helper_resume(&dpsub->drm->dev); } -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, +static const struct dev_pm_ops zynqmp_dpsub_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(zynqmp_dpsub_suspend, zynqmp_dpsub_resume) }; /* ----------------------------------------------------------------------------- - * DRM/KMS Driver + * DPSUB Configuration */ -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), +/** + * 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; +} - .fops = &zynqmp_dpsub_drm_fops, +/** + * 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); +} - .name = "zynqmp-dpsub", - .desc = "Xilinx DisplayPort Subsystem Driver", - .date = "20130509", - .major = 1, - .minor = 0, -}; +/* ----------------------------------------------------------------------------- + * Probe & Remove + */ -static int zynqmp_dpsub_drm_init(struct zynqmp_dpsub *dpsub) +static int zynqmp_dpsub_init_clocks(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; + dpsub->apb_clk = devm_clk_get(dpsub->dev, "dp_apb_clk"); + if (IS_ERR(dpsub->apb_clk)) + return PTR_ERR(dpsub->apb_clk); - ret = drm_vblank_init(drm, 1); - if (ret) + ret = clk_prepare_enable(dpsub->apb_clk); + if (ret) { + dev_err(dpsub->dev, "failed to enable the APB clock\n"); 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. + * Try the live PL video clock, and fall back to the PS clock if the + * live PL video clock isn't valid. */ - 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); + 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; + } - ret = drm_dev_register(drm, 0); - if (ret < 0) - goto err_poll_fini; + /* + * 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; + } - /* Initialize fbdev generic emulation. */ - drm_fbdev_generic_setup(drm, 24); + 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; - -err_poll_fini: - drm_kms_helper_poll_fini(drm); - return ret; } -/* ----------------------------------------------------------------------------- - * Power Management - */ - -static int __maybe_unused zynqmp_dpsub_suspend(struct device *dev) +static int zynqmp_dpsub_parse_dt(struct zynqmp_dpsub *dpsub) { - struct zynqmp_dpsub *dpsub = dev_get_drvdata(dev); + struct device_node *np; + unsigned int i; - return drm_mode_config_helper_suspend(&dpsub->drm); -} + /* + * 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; + } -static int __maybe_unused zynqmp_dpsub_resume(struct device *dev) -{ - struct zynqmp_dpsub *dpsub = dev_get_drvdata(dev); + /* Check which ports are connected. */ + for (i = 0; i < ZYNQMP_DPSUB_NUM_PORTS; ++i) { + struct device_node *np; - return drm_mode_config_helper_resume(&dpsub->drm); -} + np = of_graph_get_remote_node(dpsub->dev->of_node, i, -1); + if (np) { + dpsub->connected_ports |= BIT(i); + of_node_put(np); + } + } -static const struct dev_pm_ops zynqmp_dpsub_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(zynqmp_dpsub_suspend, zynqmp_dpsub_resume) -}; + /* 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; + } -/* ----------------------------------------------------------------------------- - * Probe & Remove - */ + 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; + } -static int zynqmp_dpsub_init_clocks(struct zynqmp_dpsub *dpsub) -{ - int ret; + if (dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_LIVE_AUDIO)) + dev_warn(dpsub->dev, "live audio unsupported, ignoring\n"); - dpsub->apb_clk = devm_clk_get(dpsub->dev, "dp_apb_clk"); - if (IS_ERR(dpsub->apb_clk)) - return PTR_ERR(dpsub->apb_clk); + 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"); - ret = clk_prepare_enable(dpsub->apb_clk); - if (ret) { - dev_err(dpsub->dev, "failed to enable the APB clock\n"); - return ret; + 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[] = { diff --git a/drivers/gpu/drm/xlnx/zynqmp_dpsub.h b/drivers/gpu/drm/xlnx/zynqmp_dpsub.h index c04026d82639..09ea01878f2a 100644 --- a/drivers/gpu/drm/xlnx/zynqmp_dpsub.h +++ b/drivers/gpu/drm/xlnx/zynqmp_dpsub.h @@ -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_ */ diff --git a/drivers/gpu/drm/xlnx/zynqmp_kms.c b/drivers/gpu/drm/xlnx/zynqmp_kms.c new file mode 100644 index 000000000000..1847792cf13d --- /dev/null +++ b/drivers/gpu/drm/xlnx/zynqmp_kms.c @@ -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); +} diff --git a/drivers/gpu/drm/xlnx/zynqmp_kms.h b/drivers/gpu/drm/xlnx/zynqmp_kms.h new file mode 100644 index 000000000000..01be96b00e3f --- /dev/null +++ b/drivers/gpu/drm/xlnx/zynqmp_kms.h @@ -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_ */ |