Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+29
View File
@@ -0,0 +1,29 @@
config DRM_ZYNQMP_DPSUB
tristate "ZynqMP DisplayPort Controller Driver"
depends on ARCH_ZYNQMP || COMPILE_TEST
depends on COMMON_CLK && DRM && OF
depends on DMADEVICES
depends on PHY_XILINX_ZYNQMP || COMPILE_TEST
depends on XILINX_ZYNQMP_DPDMA || COMPILE_TEST
select DMA_ENGINE
select DRM_CLIENT_SELECTION
select DRM_DISPLAY_DP_HELPER
select DRM_DISPLAY_HELPER
select DRM_BRIDGE_CONNECTOR
select DRM_GEM_DMA_HELPER
select DRM_KMS_HELPER
select GENERIC_PHY
help
This is a DRM/KMS driver for ZynqMP DisplayPort controller. Choose
this option if you have a Xilinx ZynqMP SoC with DisplayPort
subsystem.
config DRM_ZYNQMP_DPSUB_AUDIO
bool "ZynqMP DisplayPort Audio Support"
depends on DRM_ZYNQMP_DPSUB
depends on SND && SND_SOC
depends on SND_SOC=y || DRM_ZYNQMP_DPSUB=m
select SND_SOC_GENERIC_DMAENGINE_PCM
help
Choose this option to enable DisplayPort audio support in the ZynqMP
DisplayPort driver.
@@ -0,0 +1,3 @@
zynqmp-dpsub-y := zynqmp_disp.o zynqmp_dpsub.o zynqmp_dp.o zynqmp_kms.o
zynqmp-dpsub-$(CONFIG_DRM_ZYNQMP_DPSUB_AUDIO) += zynqmp_dp_audio.o
obj-$(CONFIG_DRM_ZYNQMP_DPSUB) += zynqmp-dpsub.o
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,69 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* ZynqMP Display Driver
*
* Copyright (C) 2017 - 2020 Xilinx, Inc.
*
* Authors:
* - Hyun Woo Kwon <hyun.kwon@xilinx.com>
* - Laurent Pinchart <laurent.pinchart@ideasonboard.com>
*/
#ifndef _ZYNQMP_DISP_H_
#define _ZYNQMP_DISP_H_
#include <linux/types.h>
/*
* 3840x2160 is advertised as the maximum resolution, but almost any
* resolutions under a 300Mhz pixel rate would work. Pick 4096x4096.
*/
#define ZYNQMP_DISP_MAX_WIDTH 4096
#define ZYNQMP_DISP_MAX_HEIGHT 4096
/* The DPDMA is limited to 44 bit addressing. */
#define ZYNQMP_DISP_MAX_DMA_BIT 44
struct device;
struct drm_format_info;
struct drm_plane_state;
struct platform_device;
struct zynqmp_disp;
struct zynqmp_disp_layer;
struct zynqmp_dpsub;
/**
* 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_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);
u32 *zynqmp_disp_live_layer_formats(struct zynqmp_disp_layer *layer,
unsigned int *num_formats);
void zynqmp_disp_layer_enable(struct zynqmp_disp_layer *layer);
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);
void zynqmp_disp_layer_set_live_format(struct zynqmp_disp_layer *layer,
u32 media_bus_format);
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_ */
@@ -0,0 +1,196 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* ZynqMP Display Controller Driver - Register Definitions
*
* Copyright (C) 2017 - 2020 Xilinx, Inc.
*
* Authors:
* - Hyun Woo Kwon <hyun.kwon@xilinx.com>
* - Laurent Pinchart <laurent.pinchart@ideasonboard.com>
*/
#ifndef _ZYNQMP_DISP_REGS_H_
#define _ZYNQMP_DISP_REGS_H_
#include <linux/bits.h>
/* Blender registers */
#define ZYNQMP_DISP_V_BLEND_BG_CLR_0 0x0
#define ZYNQMP_DISP_V_BLEND_BG_CLR_1 0x4
#define ZYNQMP_DISP_V_BLEND_BG_CLR_2 0x8
#define ZYNQMP_DISP_V_BLEND_BG_MAX 0xfff
#define ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA 0xc
#define ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA_VALUE(n) ((n) << 1)
#define ZYNQMP_DISP_V_BLEND_SET_GLOBAL_ALPHA_EN BIT(0)
#define ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT 0x14
#define ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_RGB 0x0
#define ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YCBCR444 0x1
#define ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YCBCR422 0x2
#define ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_YONLY 0x3
#define ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_XVYCC 0x4
#define ZYNQMP_DISP_V_BLEND_OUTPUT_VID_FMT_EN_DOWNSAMPLE BIT(4)
#define ZYNQMP_DISP_V_BLEND_LAYER_CONTROL(n) (0x18 + ((n) * 4))
#define ZYNQMP_DISP_V_BLEND_LAYER_CONTROL_EN_US BIT(0)
#define ZYNQMP_DISP_V_BLEND_LAYER_CONTROL_RGB BIT(1)
#define ZYNQMP_DISP_V_BLEND_LAYER_CONTROL_BYPASS BIT(8)
#define ZYNQMP_DISP_V_BLEND_NUM_COEFF 9
#define ZYNQMP_DISP_V_BLEND_NUM_OFFSET 3
#define ZYNQMP_DISP_V_BLEND_RGB2YCBCR_COEFF(n) (0x20 + ((n) * 4))
#define ZYNQMP_DISP_V_BLEND_IN1CSC_COEFF(n) (0x44 + ((n) * 4))
#define ZYNQMP_DISP_V_BLEND_IN1CSC_OFFSET(n) (0x68 + ((n) * 4))
#define ZYNQMP_DISP_V_BLEND_OUTCSC_OFFSET(n) (0x74 + ((n) * 4))
#define ZYNQMP_DISP_V_BLEND_IN2CSC_COEFF(n) (0x80 + ((n) * 4))
#define ZYNQMP_DISP_V_BLEND_IN2CSC_OFFSET(n) (0xa4 + ((n) * 4))
#define ZYNQMP_DISP_V_BLEND_CHROMA_KEY_ENABLE 0x1d0
#define ZYNQMP_DISP_V_BLEND_CHROMA_KEY_COMP1 0x1d4
#define ZYNQMP_DISP_V_BLEND_CHROMA_KEY_COMP2 0x1d8
#define ZYNQMP_DISP_V_BLEND_CHROMA_KEY_COMP3 0x1dc
/* AV buffer manager registers */
#define ZYNQMP_DISP_AV_BUF_FMT 0x0
#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_SHIFT 0
#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_MASK (0x1f << 0)
#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_UYVY (0 << 0)
#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_VYUY (1 << 0)
#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YVYU (2 << 0)
#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YUYV (3 << 0)
#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16 (4 << 0)
#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV24 (5 << 0)
#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI (6 << 0)
#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_MONO (7 << 0)
#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI2 (8 << 0)
#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YUV444 (9 << 0)
#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_RGB888 (10 << 0)
#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_RGBA8880 (11 << 0)
#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_RGB888_10 (12 << 0)
#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YUV444_10 (13 << 0)
#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI2_10 (14 << 0)
#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI_10 (15 << 0)
#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16_10 (16 << 0)
#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV24_10 (17 << 0)
#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YONLY_10 (18 << 0)
#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16_420 (19 << 0)
#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI_420 (20 << 0)
#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI2_420 (21 << 0)
#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16_420_10 (22 << 0)
#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI_420_10 (23 << 0)
#define ZYNQMP_DISP_AV_BUF_FMT_NL_VID_YV16CI2_420_10 (24 << 0)
#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_SHIFT 8
#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_MASK (0xf << 8)
#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGBA8888 (0 << 8)
#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_ABGR8888 (1 << 8)
#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGB888 (2 << 8)
#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_BGR888 (3 << 8)
#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGBA5551 (4 << 8)
#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGBA4444 (5 << 8)
#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_RGB565 (6 << 8)
#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_8BPP (7 << 8)
#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_4BPP (8 << 8)
#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_2BPP (9 << 8)
#define ZYNQMP_DISP_AV_BUF_FMT_NL_GFX_1BPP (10 << 8)
#define ZYNQMP_DISP_AV_BUF_NON_LIVE_LATENCY 0x8
#define ZYNQMP_DISP_AV_BUF_CHBUF(n) (0x10 + ((n) * 4))
#define ZYNQMP_DISP_AV_BUF_CHBUF_EN BIT(0)
#define ZYNQMP_DISP_AV_BUF_CHBUF_FLUSH BIT(1)
#define ZYNQMP_DISP_AV_BUF_CHBUF_BURST_LEN_SHIFT 2
#define ZYNQMP_DISP_AV_BUF_CHBUF_BURST_LEN_MASK (0xf << 2)
#define ZYNQMP_DISP_AV_BUF_CHBUF_BURST_LEN_MAX 0xf
#define ZYNQMP_DISP_AV_BUF_CHBUF_BURST_LEN_AUD_MAX 0x3
#define ZYNQMP_DISP_AV_BUF_STATUS 0x28
#define ZYNQMP_DISP_AV_BUF_STC_CTRL 0x2c
#define ZYNQMP_DISP_AV_BUF_STC_CTRL_EN BIT(0)
#define ZYNQMP_DISP_AV_BUF_STC_CTRL_EVENT_SHIFT 1
#define ZYNQMP_DISP_AV_BUF_STC_CTRL_EVENT_EX_VSYNC 0
#define ZYNQMP_DISP_AV_BUF_STC_CTRL_EVENT_EX_VID 1
#define ZYNQMP_DISP_AV_BUF_STC_CTRL_EVENT_EX_AUD 2
#define ZYNQMP_DISP_AV_BUF_STC_CTRL_EVENT_INT_VSYNC 3
#define ZYNQMP_DISP_AV_BUF_STC_INIT_VALUE0 0x30
#define ZYNQMP_DISP_AV_BUF_STC_INIT_VALUE1 0x34
#define ZYNQMP_DISP_AV_BUF_STC_ADJ 0x38
#define ZYNQMP_DISP_AV_BUF_STC_VID_VSYNC_TS0 0x3c
#define ZYNQMP_DISP_AV_BUF_STC_VID_VSYNC_TS1 0x40
#define ZYNQMP_DISP_AV_BUF_STC_EXT_VSYNC_TS0 0x44
#define ZYNQMP_DISP_AV_BUF_STC_EXT_VSYNC_TS1 0x48
#define ZYNQMP_DISP_AV_BUF_STC_CUSTOM_EVENT_TS0 0x4c
#define ZYNQMP_DISP_AV_BUF_STC_CUSTOM_EVENT_TS1 0x50
#define ZYNQMP_DISP_AV_BUF_STC_CUSTOM_EVENT2_TS0 0x54
#define ZYNQMP_DISP_AV_BUF_STC_CUSTOM_EVENT2_TS1 0x58
#define ZYNQMP_DISP_AV_BUF_STC_SNAPSHOT0 0x60
#define ZYNQMP_DISP_AV_BUF_STC_SNAPSHOT1 0x64
#define ZYNQMP_DISP_AV_BUF_OUTPUT 0x70
#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_SHIFT 0
#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_MASK (0x3 << 0)
#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_LIVE (0 << 0)
#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_MEM (1 << 0)
#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_PATTERN (2 << 0)
#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID1_NONE (3 << 0)
#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_SHIFT 2
#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_MASK (0x3 << 2)
#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_DISABLE (0 << 2)
#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_MEM (1 << 2)
#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_LIVE (2 << 2)
#define ZYNQMP_DISP_AV_BUF_OUTPUT_VID2_NONE (3 << 2)
#define ZYNQMP_DISP_AV_BUF_OUTPUT_AUD1_SHIFT 4
#define ZYNQMP_DISP_AV_BUF_OUTPUT_AUD1_MASK (0x3 << 4)
#define ZYNQMP_DISP_AV_BUF_OUTPUT_AUD1_PL (0 << 4)
#define ZYNQMP_DISP_AV_BUF_OUTPUT_AUD1_MEM (1 << 4)
#define ZYNQMP_DISP_AV_BUF_OUTPUT_AUD1_PATTERN (2 << 4)
#define ZYNQMP_DISP_AV_BUF_OUTPUT_AUD1_DISABLE (3 << 4)
#define ZYNQMP_DISP_AV_BUF_OUTPUT_AUD2_EN BIT(6)
#define ZYNQMP_DISP_AV_BUF_HCOUNT_VCOUNT_INT0 0x74
#define ZYNQMP_DISP_AV_BUF_HCOUNT_VCOUNT_INT1 0x78
#define ZYNQMP_DISP_AV_BUF_PATTERN_GEN_SELECT 0x100
#define ZYNQMP_DISP_AV_BUF_CLK_SRC 0x120
#define ZYNQMP_DISP_AV_BUF_CLK_SRC_VID_FROM_PS BIT(0)
#define ZYNQMP_DISP_AV_BUF_CLK_SRC_AUD_FROM_PS BIT(1)
#define ZYNQMP_DISP_AV_BUF_CLK_SRC_VID_INTERNAL_TIMING BIT(2)
#define ZYNQMP_DISP_AV_BUF_SRST_REG 0x124
#define ZYNQMP_DISP_AV_BUF_SRST_REG_VID_RST BIT(1)
#define ZYNQMP_DISP_AV_BUF_AUDIO_CH_CONFIG 0x12c
#define ZYNQMP_DISP_AV_BUF_GFX_COMP_SF(n) (0x200 + ((n) * 4))
#define ZYNQMP_DISP_AV_BUF_VID_COMP_SF(n) (0x20c + ((n) * 4))
#define ZYNQMP_DISP_AV_BUF_LIVD_VID_COMP_SF(n) (0x218 + ((n) * 4))
#define ZYNQMP_DISP_AV_BUF_LIVE_VID_CONFIG 0x224
#define ZYNQMP_DISP_AV_BUF_LIVD_GFX_COMP_SF(n) (0x228 + ((n) * 4))
#define ZYNQMP_DISP_AV_BUF_LIVE_GFX_CONFIG 0x234
#define ZYNQMP_DISP_AV_BUF_4BIT_SF 0x11111
#define ZYNQMP_DISP_AV_BUF_5BIT_SF 0x10842
#define ZYNQMP_DISP_AV_BUF_6BIT_SF 0x10410
#define ZYNQMP_DISP_AV_BUF_8BIT_SF 0x10101
#define ZYNQMP_DISP_AV_BUF_10BIT_SF 0x10040
#define ZYNQMP_DISP_AV_BUF_NULL_SF 0
#define ZYNQMP_DISP_AV_BUF_NUM_SF 3
#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_BPC_6 0x0
#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_BPC_8 0x1
#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_BPC_10 0x2
#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_BPC_12 0x3
#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_BPC_MASK GENMASK(2, 0)
#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_FMT_RGB (0x0 << 4)
#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_FMT_YUV444 (0x1 << 4)
#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_FMT_YUV422 (0x2 << 4)
#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_FMT_YONLY (0x3 << 4)
#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_FMT_MASK GENMASK(5, 4)
#define ZYNQMP_DISP_AV_BUF_LIVE_CONFIG_CB_FIRST BIT(8)
#define ZYNQMP_DISP_AV_BUF_PALETTE_MEMORY 0x400
/* Audio registers */
#define ZYNQMP_DISP_AUD_MIXER_VOLUME 0x0
#define ZYNQMP_DISP_AUD_MIXER_VOLUME_NO_SCALE 0x20002000
#define ZYNQMP_DISP_AUD_MIXER_META_DATA 0x4
#define ZYNQMP_DISP_AUD_CH_STATUS(x) (0x8 + ((x) * 4))
#define ZYNQMP_DISP_AUD_CH_A_DATA0 0x20
#define ZYNQMP_DISP_AUD_CH_A_DATA1 0x24
#define ZYNQMP_DISP_AUD_CH_A_DATA2 0x28
#define ZYNQMP_DISP_AUD_CH_A_DATA3 0x2c
#define ZYNQMP_DISP_AUD_CH_A_DATA4 0x30
#define ZYNQMP_DISP_AUD_CH_A_DATA5 0x34
#define ZYNQMP_DISP_AUD_CH_B_DATA0 0x38
#define ZYNQMP_DISP_AUD_CH_B_DATA1 0x3c
#define ZYNQMP_DISP_AUD_CH_B_DATA2 0x40
#define ZYNQMP_DISP_AUD_CH_B_DATA3 0x44
#define ZYNQMP_DISP_AUD_CH_B_DATA4 0x48
#define ZYNQMP_DISP_AUD_CH_B_DATA5 0x4c
#define ZYNQMP_DISP_AUD_SOFT_RESET 0xc00
#define ZYNQMP_DISP_AUD_SOFT_RESET_AUD_SRST BIT(0)
#endif /* _ZYNQMP_DISP_REGS_H_ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,32 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* ZynqMP DisplayPort Driver
*
* Copyright (C) 2017 - 2020 Xilinx, Inc.
*
* Authors:
* - Hyun Woo Kwon <hyun.kwon@xilinx.com>
* - Laurent Pinchart <laurent.pinchart@ideasonboard.com>
*/
#ifndef _ZYNQMP_DP_H_
#define _ZYNQMP_DP_H_
struct platform_device;
struct zynqmp_dp;
struct zynqmp_dpsub;
void zynqmp_dp_enable_vblank(struct zynqmp_dp *dp);
void zynqmp_dp_disable_vblank(struct zynqmp_dp *dp);
int zynqmp_dp_probe(struct zynqmp_dpsub *dpsub);
void zynqmp_dp_remove(struct zynqmp_dpsub *dpsub);
void zynqmp_dp_audio_set_channels(struct zynqmp_dp *dp,
unsigned int num_channels);
void zynqmp_dp_audio_enable(struct zynqmp_dp *dp);
void zynqmp_dp_audio_disable(struct zynqmp_dp *dp);
void zynqmp_dp_audio_write_n_m(struct zynqmp_dp *dp);
#endif /* _ZYNQMP_DP_H_ */
@@ -0,0 +1,448 @@
// SPDX-License-Identifier: GPL-2.0
/*
* ZynqMP DisplayPort Subsystem Driver - Audio support
*
* Copyright (C) 2015 - 2024 Xilinx, Inc.
*
* Authors:
* - Hyun Woo Kwon <hyun.kwon@xilinx.com>
* - Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
*/
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/mutex.h>
#include <linux/pm_runtime.h>
#include <sound/asoundef.h>
#include <sound/core.h>
#include <sound/dmaengine_pcm.h>
#include <sound/initval.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <sound/tlv.h>
#include "zynqmp_disp_regs.h"
#include "zynqmp_dp.h"
#include "zynqmp_dpsub.h"
#define ZYNQMP_DISP_AUD_SMPL_RATE_TO_CLK 512
#define ZYNQMP_NUM_PCMS 2
struct zynqmp_dpsub_audio {
void __iomem *base;
struct snd_soc_card card;
const char *dai_name;
const char *link_names[ZYNQMP_NUM_PCMS];
const char *pcm_names[ZYNQMP_NUM_PCMS];
struct snd_soc_dai_driver dai_driver;
struct snd_dmaengine_pcm_config pcm_configs[2];
struct snd_soc_dai_link links[ZYNQMP_NUM_PCMS];
struct {
struct snd_soc_dai_link_component cpu;
struct snd_soc_dai_link_component platform;
} components[ZYNQMP_NUM_PCMS];
/*
* Protects:
* - enabled_streams
* - volumes
* - current_rate
*/
struct mutex enable_lock;
u32 enabled_streams;
u32 current_rate;
u16 volumes[2];
};
static const struct snd_pcm_hardware zynqmp_dp_pcm_hw = {
.info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_PAUSE |
SNDRV_PCM_INFO_RESUME |
SNDRV_PCM_INFO_NO_PERIOD_WAKEUP,
.buffer_bytes_max = 128 * 1024,
.period_bytes_min = 256,
.period_bytes_max = 1024 * 1024,
.periods_min = 2,
.periods_max = 256,
};
static int zynqmp_dp_startup(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
256);
return 0;
}
static const struct snd_soc_ops zynqmp_dp_ops = {
.startup = zynqmp_dp_startup,
};
static void zynqmp_dp_audio_write(struct zynqmp_dpsub_audio *audio, int reg,
u32 val)
{
writel(val, audio->base + reg);
}
static int dp_dai_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *socdai)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct zynqmp_dpsub *dpsub =
snd_soc_dai_get_drvdata(snd_soc_rtd_to_cpu(rtd, 0));
struct zynqmp_dpsub_audio *audio = dpsub->audio;
int ret;
u32 sample_rate;
struct snd_aes_iec958 iec = { 0 };
unsigned long rate;
sample_rate = params_rate(params);
if (sample_rate != 48000 && sample_rate != 44100)
return -EINVAL;
guard(mutex)(&audio->enable_lock);
if (audio->enabled_streams && audio->current_rate != sample_rate) {
dev_err(dpsub->dev,
"Can't change rate while playback enabled\n");
return -EINVAL;
}
if (audio->enabled_streams > 0) {
/* Nothing to do */
audio->enabled_streams++;
return 0;
}
audio->current_rate = sample_rate;
/* Note: clock rate can only be changed if the clock is disabled */
ret = clk_set_rate(dpsub->aud_clk,
sample_rate * ZYNQMP_DISP_AUD_SMPL_RATE_TO_CLK);
if (ret) {
dev_err(dpsub->dev, "can't set aud_clk to %u err:%d\n",
sample_rate * ZYNQMP_DISP_AUD_SMPL_RATE_TO_CLK, ret);
return ret;
}
clk_prepare_enable(dpsub->aud_clk);
rate = clk_get_rate(dpsub->aud_clk);
/* Ignore some offset +- 10 */
if (abs(sample_rate * ZYNQMP_DISP_AUD_SMPL_RATE_TO_CLK - rate) > 10) {
dev_err(dpsub->dev, "aud_clk offset is higher: %ld\n",
sample_rate * ZYNQMP_DISP_AUD_SMPL_RATE_TO_CLK - rate);
clk_disable_unprepare(dpsub->aud_clk);
return -EINVAL;
}
pm_runtime_get_sync(dpsub->dev);
zynqmp_dp_audio_write(audio, ZYNQMP_DISP_AUD_MIXER_VOLUME,
audio->volumes[0] | (audio->volumes[1] << 16));
/* Clear the audio soft reset register as it's an non-reset flop. */
zynqmp_dp_audio_write(audio, ZYNQMP_DISP_AUD_SOFT_RESET, 0);
/* Only 2 channel audio is supported now */
zynqmp_dp_audio_set_channels(dpsub->dp, 2);
zynqmp_dp_audio_write_n_m(dpsub->dp);
/* Channel status */
if (sample_rate == 48000)
iec.status[3] = IEC958_AES3_CON_FS_48000;
else
iec.status[3] = IEC958_AES3_CON_FS_44100;
for (unsigned int i = 0; i < AES_IEC958_STATUS_SIZE / 4; ++i) {
u32 v;
v = (iec.status[(i * 4) + 0] << 0) |
(iec.status[(i * 4) + 1] << 8) |
(iec.status[(i * 4) + 2] << 16) |
(iec.status[(i * 4) + 3] << 24);
zynqmp_dp_audio_write(audio, ZYNQMP_DISP_AUD_CH_STATUS(i), v);
}
zynqmp_dp_audio_enable(dpsub->dp);
audio->enabled_streams++;
return 0;
}
static int dp_dai_hw_free(struct snd_pcm_substream *substream,
struct snd_soc_dai *socdai)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct zynqmp_dpsub *dpsub =
snd_soc_dai_get_drvdata(snd_soc_rtd_to_cpu(rtd, 0));
struct zynqmp_dpsub_audio *audio = dpsub->audio;
guard(mutex)(&audio->enable_lock);
/* Nothing to do */
if (audio->enabled_streams > 1) {
audio->enabled_streams--;
return 0;
}
pm_runtime_put(dpsub->dev);
zynqmp_dp_audio_disable(dpsub->dp);
/*
* Reset doesn't work. If we assert reset between audio stop and start,
* the audio won't start anymore. Probably we are missing writing
* some audio related registers. A/B buf?
*/
/*
zynqmp_disp_audio_write(audio, ZYNQMP_DISP_AUD_SOFT_RESET,
ZYNQMP_DISP_AUD_SOFT_RESET_AUD_SRST);
*/
clk_disable_unprepare(dpsub->aud_clk);
audio->current_rate = 0;
audio->enabled_streams--;
return 0;
}
static const struct snd_soc_dai_ops zynqmp_dp_dai_ops = {
.hw_params = dp_dai_hw_params,
.hw_free = dp_dai_hw_free,
};
/*
* Min = 10 * log10(0x1 / 0x2000) = -39.13
* Max = 10 * log10(0xffffff / 0x2000) = 9.03
*/
static const DECLARE_TLV_DB_RANGE(zynqmp_dp_tlv,
0x0, 0x0, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, -3913, 1),
0x1, 0x2000, TLV_DB_LINEAR_ITEM(-3913, 0),
0x2000, 0xffff, TLV_DB_LINEAR_ITEM(0, 903),
);
static const struct snd_kcontrol_new zynqmp_dp_snd_controls[] = {
SOC_SINGLE_TLV("Input0 Playback Volume", 0,
0, 0xffff, 0, zynqmp_dp_tlv),
SOC_SINGLE_TLV("Input1 Playback Volume", 1,
0, 0xffff, 0, zynqmp_dp_tlv),
};
/*
* Note: these read & write functions only support two "registers", 0 and 1,
* for volume 0 and 1. In other words, these are not real register read/write
* functions.
*
* This is done to support caching the volume value for the case where the
* hardware is not enabled, and also to support locking as volumes 0 and 1
* are in the same register.
*/
static unsigned int zynqmp_dp_dai_read(struct snd_soc_component *component,
unsigned int reg)
{
struct zynqmp_dpsub *dpsub = dev_get_drvdata(component->dev);
struct zynqmp_dpsub_audio *audio = dpsub->audio;
return audio->volumes[reg];
}
static int zynqmp_dp_dai_write(struct snd_soc_component *component,
unsigned int reg, unsigned int val)
{
struct zynqmp_dpsub *dpsub = dev_get_drvdata(component->dev);
struct zynqmp_dpsub_audio *audio = dpsub->audio;
guard(mutex)(&audio->enable_lock);
audio->volumes[reg] = val;
if (audio->enabled_streams)
zynqmp_dp_audio_write(audio, ZYNQMP_DISP_AUD_MIXER_VOLUME,
audio->volumes[0] |
(audio->volumes[1] << 16));
return 0;
}
static const struct snd_soc_component_driver zynqmp_dp_component_driver = {
.idle_bias_on = 1,
.use_pmdown_time = 1,
.endianness = 1,
.controls = zynqmp_dp_snd_controls,
.num_controls = ARRAY_SIZE(zynqmp_dp_snd_controls),
.read = zynqmp_dp_dai_read,
.write = zynqmp_dp_dai_write,
};
int zynqmp_audio_init(struct zynqmp_dpsub *dpsub)
{
struct platform_device *pdev = to_platform_device(dpsub->dev);
struct device *dev = dpsub->dev;
struct zynqmp_dpsub_audio *audio;
struct snd_soc_card *card;
void *dev_data;
int ret;
if (!dpsub->aud_clk)
return 0;
audio = devm_kzalloc(dev, sizeof(*audio), GFP_KERNEL);
if (!audio)
return -ENOMEM;
dpsub->audio = audio;
mutex_init(&audio->enable_lock);
/* 0x2000 is the zero level, no change */
audio->volumes[0] = 0x2000;
audio->volumes[1] = 0x2000;
audio->dai_name = devm_kasprintf(dev, GFP_KERNEL,
"%s-dai", dev_name(dev));
if (!audio->dai_name)
return -ENOMEM;
for (unsigned int i = 0; i < ZYNQMP_NUM_PCMS; ++i) {
audio->link_names[i] = devm_kasprintf(dev, GFP_KERNEL,
"%s-dp-%u", dev_name(dev), i);
audio->pcm_names[i] = devm_kasprintf(dev, GFP_KERNEL,
"%s-pcm-%u", dev_name(dev), i);
if (!audio->link_names[i] || !audio->pcm_names[i])
return -ENOMEM;
}
audio->base = devm_platform_ioremap_resource_byname(pdev, "aud");
if (IS_ERR(audio->base))
return PTR_ERR(audio->base);
/* Create CPU DAI */
audio->dai_driver = (struct snd_soc_dai_driver) {
.name = audio->dai_name,
.ops = &zynqmp_dp_dai_ops,
.playback = {
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
};
ret = devm_snd_soc_register_component(dev, &zynqmp_dp_component_driver,
&audio->dai_driver, 1);
if (ret) {
dev_err(dev, "Failed to register CPU DAI\n");
return ret;
}
/* Create PCMs */
for (unsigned int i = 0; i < ZYNQMP_NUM_PCMS; ++i) {
struct snd_dmaengine_pcm_config *pcm_config =
&audio->pcm_configs[i];
*pcm_config = (struct snd_dmaengine_pcm_config){
.name = audio->pcm_names[i],
.pcm_hardware = &zynqmp_dp_pcm_hw,
.prealloc_buffer_size = 64 * 1024,
.chan_names[SNDRV_PCM_STREAM_PLAYBACK] =
i == 0 ? "aud0" : "aud1",
};
ret = devm_snd_dmaengine_pcm_register(dev, pcm_config, 0);
if (ret) {
dev_err(dev, "Failed to register PCM %u\n", i);
return ret;
}
}
/* Create card */
card = &audio->card;
card->name = "DisplayPort";
card->long_name = "DisplayPort Monitor";
card->driver_name = "zynqmp_dpsub";
card->dev = dev;
card->owner = THIS_MODULE;
card->num_links = ZYNQMP_NUM_PCMS;
card->dai_link = audio->links;
for (unsigned int i = 0; i < ZYNQMP_NUM_PCMS; ++i) {
struct snd_soc_dai_link *link = &card->dai_link[i];
link->ops = &zynqmp_dp_ops;
link->name = audio->link_names[i];
link->stream_name = audio->link_names[i];
link->cpus = &audio->components[i].cpu;
link->num_cpus = 1;
link->cpus[0].dai_name = audio->dai_name;
link->codecs = &snd_soc_dummy_dlc;
link->num_codecs = 1;
link->platforms = &audio->components[i].platform;
link->num_platforms = 1;
link->platforms[0].name = audio->pcm_names[i];
}
/*
* HACK: devm_snd_soc_register_card() overwrites current drvdata
* so we need to hack it back.
*/
dev_data = dev_get_drvdata(dev);
ret = devm_snd_soc_register_card(dev, card);
dev_set_drvdata(dev, dev_data);
if (ret) {
/*
* As older dtbs may not have the audio channel dmas defined,
* instead of returning an error here we'll continue and just
* mark the audio as disabled.
*/
dev_err(dev, "Failed to register sound card, disabling audio support\n");
devm_kfree(dev, audio);
dpsub->audio = NULL;
return 0;
}
return 0;
}
void zynqmp_audio_uninit(struct zynqmp_dpsub *dpsub)
{
struct zynqmp_dpsub_audio *audio = dpsub->audio;
if (!audio)
return;
if (!dpsub->aud_clk)
return;
mutex_destroy(&audio->enable_lock);
}
@@ -0,0 +1,316 @@
// SPDX-License-Identifier: GPL-2.0
/*
* ZynqMP DisplayPort Subsystem Driver
*
* Copyright (C) 2017 - 2020 Xilinx, Inc.
*
* Authors:
* - Hyun Woo Kwon <hyun.kwon@xilinx.com>
* - Laurent Pinchart <laurent.pinchart@ideasonboard.com>
*/
#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_bridge.h>
#include <drm/drm_modeset_helper.h>
#include <drm/drm_module.h>
#include "zynqmp_disp.h"
#include "zynqmp_dp.h"
#include "zynqmp_dpsub.h"
#include "zynqmp_kms.h"
/* -----------------------------------------------------------------------------
* Power Management
*/
static int __maybe_unused zynqmp_dpsub_suspend(struct device *dev)
{
struct zynqmp_dpsub *dpsub = dev_get_drvdata(dev);
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);
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)
};
/* -----------------------------------------------------------------------------
* Probe & Remove
*/
static int zynqmp_dpsub_init_clocks(struct zynqmp_dpsub *dpsub)
{
int ret;
dpsub->apb_clk = devm_clk_get(dpsub->dev, "dp_apb_clk");
if (IS_ERR(dpsub->apb_clk))
return PTR_ERR(dpsub->apb_clk);
ret = clk_prepare_enable(dpsub->apb_clk);
if (ret) {
dev_err(dpsub->dev, "failed to enable the APB clock\n");
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);
}
static int zynqmp_dpsub_probe(struct platform_device *pdev)
{
struct zynqmp_dpsub *dpsub;
int ret;
/* Allocate private data. */
dpsub = kzalloc_obj(*dpsub);
if (!dpsub)
return -ENOMEM;
dpsub->dev = &pdev->dev;
platform_set_drvdata(pdev, dpsub);
ret = dma_set_mask(dpsub->dev, DMA_BIT_MASK(ZYNQMP_DISP_MAX_DMA_BIT));
if (ret)
return ret;
dma_set_max_seg_size(&pdev->dev, DMA_BIT_MASK(32));
/* Try the reserved memory. Proceed if there's none. */
of_reserved_mem_device_init(&pdev->dev);
ret = zynqmp_dpsub_init_clocks(dpsub);
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);
if (ret)
goto err_pm;
ret = zynqmp_disp_probe(dpsub);
if (ret)
goto err_dp;
drm_bridge_add(dpsub->bridge);
if (dpsub->dma_enabled) {
ret = zynqmp_dpsub_drm_init(dpsub);
if (ret)
goto err_disp;
}
ret = zynqmp_audio_init(dpsub);
if (ret)
goto err_drm_cleanup;
dev_info(&pdev->dev, "ZynqMP DisplayPort Subsystem driver probed");
return 0;
err_drm_cleanup:
if (dpsub->drm)
zynqmp_dpsub_drm_cleanup(dpsub);
err_disp:
drm_bridge_remove(dpsub->bridge);
zynqmp_disp_remove(dpsub);
err_dp:
zynqmp_dp_remove(dpsub);
err_pm:
pm_runtime_disable(&pdev->dev);
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 void zynqmp_dpsub_remove(struct platform_device *pdev)
{
struct zynqmp_dpsub *dpsub = platform_get_drvdata(pdev);
zynqmp_audio_uninit(dpsub);
if (dpsub->drm)
zynqmp_dpsub_drm_cleanup(dpsub);
drm_bridge_remove(dpsub->bridge);
zynqmp_disp_remove(dpsub);
zynqmp_dp_remove(dpsub);
pm_runtime_disable(&pdev->dev);
clk_disable_unprepare(dpsub->apb_clk);
of_reserved_mem_device_release(&pdev->dev);
if (!dpsub->drm)
zynqmp_dpsub_release(dpsub);
}
static void zynqmp_dpsub_shutdown(struct platform_device *pdev)
{
struct zynqmp_dpsub *dpsub = platform_get_drvdata(pdev);
if (!dpsub->drm)
return;
drm_atomic_helper_shutdown(&dpsub->drm->dev);
}
static const struct of_device_id zynqmp_dpsub_of_match[] = {
{ .compatible = "xlnx,zynqmp-dpsub-1.7", },
{ /* end of table */ },
};
MODULE_DEVICE_TABLE(of, zynqmp_dpsub_of_match);
static struct platform_driver zynqmp_dpsub_driver = {
.probe = zynqmp_dpsub_probe,
.remove = zynqmp_dpsub_remove,
.shutdown = zynqmp_dpsub_shutdown,
.driver = {
.name = "zynqmp-dpsub",
.pm = &zynqmp_dpsub_pm_ops,
.of_match_table = zynqmp_dpsub_of_match,
},
};
drm_module_platform_driver(zynqmp_dpsub_driver);
MODULE_AUTHOR("Xilinx, Inc.");
MODULE_DESCRIPTION("ZynqMP DP Subsystem Driver");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,99 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* ZynqMP DPSUB Subsystem Driver
*
* Copyright (C) 2017 - 2020 Xilinx, Inc.
*
* Authors:
* - Hyun Woo Kwon <hyun.kwon@xilinx.com>
* - Laurent Pinchart <laurent.pinchart@ideasonboard.com>
*/
#ifndef _ZYNQMP_DPSUB_H_
#define _ZYNQMP_DPSUB_H_
#include <linux/types.h>
struct clk;
struct 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,
ZYNQMP_DPSUB_FORMAT_YCRCB444,
ZYNQMP_DPSUB_FORMAT_YCRCB422,
ZYNQMP_DPSUB_FORMAT_YONLY,
};
struct zynqmp_dpsub_audio;
/**
* struct zynqmp_dpsub - ZynqMP DisplayPort Subsystem
* @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
* @layers: Video and graphics layers
* @dp: The DisplayPort controller
* @dma_align: DMA alignment constraint (must be a power of 2)
* @audio: DP audio data
*/
struct zynqmp_dpsub {
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;
struct zynqmp_dpsub_audio *audio;
};
#ifdef CONFIG_DRM_ZYNQMP_DPSUB_AUDIO
int zynqmp_audio_init(struct zynqmp_dpsub *dpsub);
void zynqmp_audio_uninit(struct zynqmp_dpsub *dpsub);
#else
static inline int zynqmp_audio_init(struct zynqmp_dpsub *dpsub) { return 0; }
static inline void zynqmp_audio_uninit(struct zynqmp_dpsub *dpsub) { }
#endif
void zynqmp_dpsub_release(struct zynqmp_dpsub *dpsub);
#endif /* _ZYNQMP_DPSUB_H_ */
@@ -0,0 +1,542 @@
// 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/clients/drm_client_setup.h>
#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_dumb_buffers.h>
#include <drm/drm_encoder.h>
#include <drm/drm_fbdev_dma.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_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_commit *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_commit *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_commit *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);
/*
* Unconditionally enable the layer, as it may have been disabled
* previously either explicitly to reconfigure layer format, or
* implicitly after DPSUB reset during display mode change. DRM
* framework calls this callback for enabled planes only.
*/
zynqmp_disp_layer_enable(layer);
}
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_commit *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_commit *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_commit *state)
{
return drm_atomic_add_affected_planes(state, crtc);
}
static void zynqmp_dpsub_crtc_atomic_begin(struct drm_crtc *crtc,
struct drm_atomic_commit *state)
{
drm_crtc_vblank_on(crtc);
}
static void zynqmp_dpsub_crtc_atomic_flush(struct drm_crtc *crtc,
struct drm_atomic_commit *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);
int ret;
/* Enforce the alignment constraints of the DMA engine. */
ret = drm_mode_size_dumb(drm, args, dpsub->dma_align, 0);
if (ret)
return ret;
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_format_info *info,
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, info, &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),
DRM_FBDEV_DMA_DRIVER_OPS,
.fops = &zynqmp_dpsub_drm_fops,
.name = "zynqmp-dpsub",
.desc = "Xilinx DisplayPort Subsystem Driver",
.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");
goto err_encoder;
}
/* 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");
ret = PTR_ERR(connector);
goto err_encoder;
}
return 0;
err_encoder:
drm_encoder_cleanup(encoder);
return ret;
}
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;
ret = zynqmp_dpsub_kms_init(dpsub);
if (ret < 0)
goto err_poll_fini;
drm_kms_helper_poll_init(drm);
/* 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_client_setup_with_fourcc(drm, DRM_FORMAT_RGB888);
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_unplug(drm);
drm_atomic_helper_shutdown(drm);
drm_encoder_cleanup(&dpsub->drm->encoder);
drm_kms_helper_poll_fini(drm);
}
@@ -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_drm - ZynqMP DisplayPort Subsystem DRM/KMS data
* @dpsub: Backpointer to the DisplayPort subsystem
* @dev: 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_ */