drm: xlnx: zynqmp_dpsub: Move CRTC handling to zynqmp_kms.c

Decouple the CRTC handling from the display controller programming by
moving the corresponding code from zynqmp_disp.c to zynqmp_kms.c. This
prepares for using the DPSUB with a live video input, without creating a
DRM CRTC in the DPSUB driver.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
This commit is contained in:
Laurent Pinchart 2021-08-06 14:29:52 +03:00
parent 76c8eeb72d
commit 83a956d3c3
5 changed files with 232 additions and 235 deletions

View file

@ -11,27 +11,21 @@
#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 "zynqmp_disp.h"
#include "zynqmp_disp_regs.h"
@ -88,16 +82,6 @@ struct zynqmp_disp_format {
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
@ -143,7 +127,7 @@ struct zynqmp_disp_layer_info {
* @mode: Current operation mode
*/
struct zynqmp_disp_layer {
enum zynqmp_disp_layer_id id;
enum zynqmp_dpsub_layer_id id;
struct zynqmp_disp *disp;
const struct zynqmp_disp_layer_info *info;
@ -398,12 +382,12 @@ static void zynqmp_disp_avbuf_write(struct zynqmp_disp *disp, int reg, u32 val)
static bool zynqmp_disp_layer_is_gfx(const struct zynqmp_disp_layer *layer)
{
return layer->id == ZYNQMP_DISP_LAYER_GFX;
return layer->id == ZYNQMP_DPSUB_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;
}
/**
@ -882,35 +866,6 @@ static void zynqmp_disp_audio_disable(struct zynqmp_disp *disp)
ZYNQMP_DISP_AUD_SOFT_RESET_AUD_SRST);
}
/* -----------------------------------------------------------------------------
* 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->dpsub->crtc;
drm_crtc_handle_vblank(crtc);
}
/**
* 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->dpsub->crtc);
}
/* -----------------------------------------------------------------------------
* ZynqMP Display Layer & DRM Plane
*/
@ -1110,7 +1065,7 @@ zynqmp_disp_plane_atomic_check(struct drm_plane *plane,
false, false);
}
static void
void
zynqmp_disp_plane_atomic_disable(struct drm_plane *plane,
struct drm_atomic_state *state)
{
@ -1300,12 +1255,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,
@ -1335,14 +1290,14 @@ static int zynqmp_disp_create_layers(struct zynqmp_disp *disp)
}
/* -----------------------------------------------------------------------------
* 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);
@ -1362,7 +1317,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);
@ -1371,8 +1326,15 @@ static void zynqmp_disp_disable(struct zynqmp_disp *disp)
zynqmp_disp_avbuf_disable(disp);
}
static int zynqmp_disp_setup_clock(struct zynqmp_disp *disp,
unsigned long mode_clock)
/**
* 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)
{
unsigned long rate;
long diff;
@ -1398,186 +1360,13 @@ static int zynqmp_disp_setup_clock(struct zynqmp_disp *disp,
return 0;
}
static inline struct zynqmp_disp *crtc_to_disp(struct drm_crtc *crtc)
{
return container_of(crtc, struct zynqmp_dpsub, crtc)->disp;
}
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_setup_clock(disp, adjusted_mode->clock * 1000);
ret = clk_prepare_enable(disp->dpsub->vid_clk);
if (ret) {
dev_err(disp->dev, "failed to enable the video clock\n");
pm_runtime_put_sync(disp->dev);
return;
}
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(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->dpsub->vid_clk);
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->dpsub->planes[ZYNQMP_DISP_LAYER_GFX];
struct drm_crtc *crtc = &disp->dpsub->crtc;
int ret;
ret = drm_crtc_init_with_planes(disp->drm, crtc, plane,
NULL, &zynqmp_disp_crtc_funcs, NULL);
if (ret < 0)
return ret;
drm_crtc_helper_add(crtc, &zynqmp_disp_crtc_helper_funcs);
/* Start with vertical blanking interrupt reporting disabled. */
drm_crtc_vblank_off(crtc);
return 0;
}
static void zynqmp_disp_map_crtc_to_plane(struct zynqmp_disp *disp)
{
u32 possible_crtcs = drm_crtc_mask(&disp->dpsub->crtc);
unsigned int i;
for (i = 0; i < ARRAY_SIZE(disp->layers); i++)
disp->dpsub->planes[i].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;
return zynqmp_disp_create_planes(dpsub->disp);
}
int zynqmp_disp_probe(struct zynqmp_dpsub *dpsub, struct drm_device *drm)
@ -1617,7 +1406,7 @@ int zynqmp_disp_probe(struct zynqmp_dpsub *dpsub, struct drm_device *drm)
if (ret)
return ret;
layer = &disp->layers[ZYNQMP_DISP_LAYER_VID];
layer = &disp->layers[ZYNQMP_DPSUB_LAYER_VID];
dpsub->dma_align = 1 << layer->dmas[0].chan->device->copy_align;
return 0;

View file

@ -25,13 +25,30 @@
#define ZYNQMP_DISP_MAX_DMA_BIT 44
struct device;
struct drm_atomic_state;
struct drm_device;
struct drm_plane;
struct platform_device;
struct zynqmp_disp;
struct zynqmp_dpsub;
void zynqmp_disp_handle_vblank(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,
};
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_plane_atomic_disable(struct drm_plane *plane,
struct drm_atomic_state *state);
int zynqmp_disp_drm_init(struct zynqmp_dpsub *dpsub);
int zynqmp_disp_probe(struct zynqmp_dpsub *dpsub, struct drm_device *drm);

View file

@ -31,6 +31,7 @@
#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);
@ -1560,7 +1561,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_handle_vblank(dp->dpsub);
if (status & ZYNQMP_DP_INT_HPD_EVENT)
schedule_delayed_work(&dp->hpd_work, 0);

View file

@ -9,17 +9,199 @@
* - Laurent Pinchart <laurent.pinchart@ideasonboard.com>
*/
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.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_encoder.h>
#include <drm/drm_plane.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"
/* -----------------------------------------------------------------------------
* DRM CRTC
*/
static inline struct zynqmp_dpsub *crtc_to_dpsub(struct drm_crtc *crtc)
{
return container_of(crtc, struct zynqmp_dpsub, crtc);
}
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_disp_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->planes[ZYNQMP_DPSUB_LAYER_GFX];
struct drm_crtc *crtc = &dpsub->crtc;
int ret;
ret = drm_crtc_init_with_planes(&dpsub->drm, 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->crtc);
unsigned int i;
for (i = 0; i < ARRAY_SIZE(dpsub->planes); i++)
dpsub->planes[i].possible_crtcs = possible_crtcs;
}
/**
* zynqmp_dpsub_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_handle_vblank(struct zynqmp_dpsub *dpsub)
{
drm_crtc_handle_vblank(&dpsub->crtc);
}
/* -----------------------------------------------------------------------------
* Initialization
*/
@ -38,12 +220,18 @@ int zynqmp_dpsub_kms_init(struct zynqmp_dpsub *dpsub)
if (ret)
return ret;
ret = zynqmp_dpsub_create_crtc(dpsub);
if (ret < 0)
return ret;
zynqmp_dpsub_map_crtc_to_plane(dpsub);
ret = zynqmp_dp_drm_init(dpsub);
if (ret)
return ret;
/* Create the encoder and attach the bridge. */
encoder->possible_crtcs |= zynqmp_disp_get_crtc_mask(dpsub->disp);
encoder->possible_crtcs |= drm_crtc_mask(&dpsub->crtc);
drm_simple_encoder_init(&dpsub->drm, encoder, DRM_MODE_ENCODER_NONE);
ret = drm_bridge_attach(encoder, dpsub->bridge, NULL,

View file

@ -14,6 +14,8 @@
struct zynqmp_dpsub;
void zynqmp_dpsub_handle_vblank(struct zynqmp_dpsub *dpsub);
int zynqmp_dpsub_kms_init(struct zynqmp_dpsub *dpsub);
#endif /* _ZYNQMP_KMS_H_ */