Commit 3ec5c157 authored by Tomi Valkeinen's avatar Tomi Valkeinen
Browse files

drm: xlnx: zynqmp_dpsub: Add DP audio support



Add basic DisplayPort audio support.

Support non-live audio playback from two PCMs (DMA channels), and the
volume control in the audio mixer.

As older dtb files may not have the audio DMA channels defined, the
driver will just mark the audio support as disabled if the audio DMA is
missing, and will continue with only display support.

Note: Reset doesn't seem to work (ZYNQMP_DISP_AUD_SOFT_RESET). If we do
a reset, audio playback won't start again even if, afaics, we do set up
all the necessary registers. So, at the moment, resetting the audio
block in dp_dai_hw_free() is commented out.

Tested-by: default avatarAnatoliy Klymenko <anatoliy.klymenko@amd.com>
Reviewed-by: default avatarVishal Sagar <vishal.sagar@amd.com>
Signed-off-by: default avatarTomi Valkeinen <tomi.valkeinen@ideasonboard.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20241023-xilinx-dp-audio-v4-3-5128881457be@ideasonboard.com
parent 0e0ab246
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -17,3 +17,12 @@ config DRM_ZYNQMP_DPSUB
	  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
	select SND_SOC_GENERIC_DMAENGINE_PCM
	help
	  Choose this option to enable DisplayPort audio support in the ZynqMP
	  DisplayPort driver.
+1 −0
Original line number Diff line number Diff line
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
+0 −48
Original line number Diff line number Diff line
@@ -143,7 +143,6 @@ struct zynqmp_disp_layer {
 * @dpsub: Display subsystem
 * @blend: Register I/O base address for the blender
 * @avbuf: Register I/O base address for the audio/video buffer manager
 * @audio: Registers I/O base address for the audio mixer
 * @layers: Layers (planes)
 */
struct zynqmp_disp {
@@ -152,7 +151,6 @@ struct zynqmp_disp {

	void __iomem *blend;
	void __iomem *avbuf;
	void __iomem *audio;

	struct zynqmp_disp_layer layers[ZYNQMP_DPSUB_NUM_LAYERS];
};
@@ -865,42 +863,6 @@ static void zynqmp_disp_blend_layer_disable(struct zynqmp_disp *disp,
					csc_zero_offsets);
}

/* -----------------------------------------------------------------------------
 * Audio Mixer
 */

static void zynqmp_disp_audio_write(struct zynqmp_disp *disp, int reg, u32 val)
{
	writel(val, disp->audio + reg);
}

/**
 * zynqmp_disp_audio_enable - Enable the audio mixer
 * @disp: Display controller
 *
 * Enable the audio mixer by de-asserting the soft reset. The audio state is set to
 * default values by the reset, set the default mixer volume explicitly.
 */
static void zynqmp_disp_audio_enable(struct zynqmp_disp *disp)
{
	/* Clear the audio soft reset register as it's an non-reset flop. */
	zynqmp_disp_audio_write(disp, ZYNQMP_DISP_AUD_SOFT_RESET, 0);
	zynqmp_disp_audio_write(disp, ZYNQMP_DISP_AUD_MIXER_VOLUME,
				ZYNQMP_DISP_AUD_MIXER_VOLUME_NO_SCALE);
}

/**
 * zynqmp_disp_audio_disable - Disable the audio mixer
 * @disp: Display controller
 *
 * Disable the audio mixer by asserting its soft reset.
 */
static void zynqmp_disp_audio_disable(struct zynqmp_disp *disp)
{
	zynqmp_disp_audio_write(disp, ZYNQMP_DISP_AUD_SOFT_RESET,
				ZYNQMP_DISP_AUD_SOFT_RESET_AUD_SRST);
}

/* -----------------------------------------------------------------------------
 * ZynqMP Display Layer & DRM Plane
 */
@@ -1341,8 +1303,6 @@ void zynqmp_disp_enable(struct zynqmp_disp *disp)
					     disp->dpsub->vid_clk_from_ps);
	zynqmp_disp_avbuf_enable_channels(disp);
	zynqmp_disp_avbuf_enable_audio(disp);

	zynqmp_disp_audio_enable(disp);
}

/**
@@ -1351,8 +1311,6 @@ void zynqmp_disp_enable(struct zynqmp_disp *disp)
 */
void zynqmp_disp_disable(struct zynqmp_disp *disp)
{
	zynqmp_disp_audio_disable(disp);

	zynqmp_disp_avbuf_disable_audio(disp);
	zynqmp_disp_avbuf_disable_channels(disp);
	zynqmp_disp_avbuf_disable(disp);
@@ -1421,12 +1379,6 @@ int zynqmp_disp_probe(struct zynqmp_dpsub *dpsub)
		goto error;
	}

	disp->audio = devm_platform_ioremap_resource_byname(pdev, "aud");
	if (IS_ERR(disp->audio)) {
		ret = PTR_ERR(disp->audio);
		goto error;
	}

	ret = zynqmp_disp_create_layers(disp);
	if (ret)
		goto error;
+1 −6
Original line number Diff line number Diff line
@@ -177,12 +177,7 @@
#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_STATUS0			0x8
#define ZYNQMP_DISP_AUD_CH_STATUS1			0xc
#define ZYNQMP_DISP_AUD_CH_STATUS2			0x10
#define ZYNQMP_DISP_AUD_CH_STATUS3			0x14
#define ZYNQMP_DISP_AUD_CH_STATUS4			0x18
#define ZYNQMP_DISP_AUD_CH_STATUS5			0x1c
#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
+39 −15
Original line number Diff line number Diff line
@@ -1342,7 +1342,6 @@ static void zynqmp_dp_encoder_mode_set_stream(struct zynqmp_dp *dp,
{
	u8 lane_cnt = dp->mode.lane_cnt;
	u32 reg, wpl;
	unsigned int rate;

	zynqmp_dp_write(dp, ZYNQMP_DP_MAIN_STREAM_HTOTAL, mode->htotal);
	zynqmp_dp_write(dp, ZYNQMP_DP_MAIN_STREAM_VTOTAL, mode->vtotal);
@@ -1367,17 +1366,7 @@ static void zynqmp_dp_encoder_mode_set_stream(struct zynqmp_dp *dp,
		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_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);
			zynqmp_dp_write(dp, ZYNQMP_DP_TX_M_AUD, rate / 1000);
	}
	}

	/* Only 2 channel audio is supported now */
	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);

@@ -1387,6 +1376,44 @@ static void zynqmp_dp_encoder_mode_set_stream(struct zynqmp_dp *dp,
	zynqmp_dp_write(dp, ZYNQMP_DP_USER_DATA_COUNT_PER_LANE, reg);
}

/* -----------------------------------------------------------------------------
 * Audio
 */

void zynqmp_dp_audio_set_channels(struct zynqmp_dp *dp,
				  unsigned int num_channels)
{
	zynqmp_dp_write(dp, ZYNQMP_DP_TX_AUDIO_CHANNELS, num_channels - 1);
}

void zynqmp_dp_audio_enable(struct zynqmp_dp *dp)
{
	zynqmp_dp_write(dp, ZYNQMP_DP_TX_AUDIO_CONTROL, 1);
}

void zynqmp_dp_audio_disable(struct zynqmp_dp *dp)
{
	zynqmp_dp_write(dp, ZYNQMP_DP_TX_AUDIO_CONTROL, 0);
}

void zynqmp_dp_audio_write_n_m(struct zynqmp_dp *dp)
{
	unsigned int rate;
	u32 link_rate;

	if (!(dp->config.misc0 & ZYNQMP_DP_MAIN_STREAM_MISC0_SYNC_LOCK))
		return;

	link_rate = drm_dp_bw_code_to_link_rate(dp->mode.bw_code);

	rate = clk_get_rate(dp->dpsub->aud_clk);

	dev_dbg(dp->dev, "Audio rate: %d\n", rate / 512);

	zynqmp_dp_write(dp, ZYNQMP_DP_TX_N_AUD, link_rate);
	zynqmp_dp_write(dp, ZYNQMP_DP_TX_M_AUD, rate / 1000);
}

/* -----------------------------------------------------------------------------
 * DISP Configuration
 */
@@ -1577,8 +1604,7 @@ static void zynqmp_dp_bridge_atomic_enable(struct drm_bridge *bridge,
	/* 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++) {
@@ -1613,8 +1639,6 @@ static void zynqmp_dp_bridge_atomic_disable(struct drm_bridge *bridge,
	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);
	mutex_unlock(&dp->lock);
Loading