mirror of
https://github.com/torvalds/linux.git
synced 2024-11-11 06:31:49 +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
@ -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…
Reference in New Issue
Block a user