[v2 06/10] drm: bridge: cadence: Add MHDP HDMI driver for i.MX8MQ

Alexander Stein alexander.stein at ew.tq-group.com
Tue Nov 8 05:16:50 PST 2022


Hello,

thanks for working on this and the updated version.

Am Freitag, 4. November 2022, 07:44:56 CET schrieb Sandor Yu:
> Add a new DRM HDMI bridge driver for Candence MHDP used in i.MX8MQ
> SOC. MHDP IP could support HDMI or DisplayPort standards according
> embedded Firmware running in the uCPU.
> 
> For iMX8MQ SOC, the HDMI FW was loaded and activated by SOC ROM code.
> Bootload binary included HDMI FW was required for the driver.
> 
> Signed-off-by: Sandor Yu <Sandor.yu at nxp.com>
> ---
>  drivers/gpu/drm/bridge/cadence/Kconfig        |   12 +
>  .../gpu/drm/bridge/cadence/cdns-hdmi-core.c   | 1038 +++++++++++++++++
>  2 files changed, 1050 insertions(+)
>  create mode 100644 drivers/gpu/drm/bridge/cadence/cdns-hdmi-core.c
> 
> diff --git a/drivers/gpu/drm/bridge/cadence/Kconfig
> b/drivers/gpu/drm/bridge/cadence/Kconfig index e79ae1af3765..377452d09992
> 100644
> --- a/drivers/gpu/drm/bridge/cadence/Kconfig
> +++ b/drivers/gpu/drm/bridge/cadence/Kconfig
> @@ -26,6 +26,18 @@ config DRM_CDNS_MHDP8546_J721E
>  	  clock and data muxes.
>  endif
> 
> +config DRM_CDNS_HDMI
> +	tristate "Cadence HDMI DRM driver"
> +	select DRM_KMS_HELPER
> +	select DRM_PANEL_BRIDGE
> +	select DRM_DISPLAY_HELPER
> +	select DRM_CDNS_AUDIO
> +	depends on OF
> +	help
> +	  Support Cadence MHDP HDMI driver.
> +	  Cadence MHDP Controller support one or more protocols,
> +	  HDMI firmware is required for this driver.
> +
>  config DRM_CDNS_DP
>  	tristate "Cadence DP DRM driver"
>  	select DRM_KMS_HELPER
> diff --git a/drivers/gpu/drm/bridge/cadence/cdns-hdmi-core.c
> b/drivers/gpu/drm/bridge/cadence/cdns-hdmi-core.c new file mode 100644
> index 000000000000..b392aac015bd
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/cadence/cdns-hdmi-core.c
> @@ -0,0 +1,1038 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Cadence High-Definition Multimedia Interface (HDMI) driver
> + *
> + * Copyright (C) 2019-2022 NXP Semiconductor, Inc.
> + *
> + */
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/hdmi.h>
> +#include <linux/irq.h>
> +#include <linux/module.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/mutex.h>
> +#include <linux/of_device.h>
> +#include <linux/phy/phy.h>
> +#include <linux/phy/phy-hdmi.h>
> +
> +#include <drm/bridge/cdns-mhdp-mailbox.h>
> +#include <drm/display/drm_hdmi_helper.h>
> +#include <drm/display/drm_scdc_helper.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_edid.h>
> +#include <drm/drm_encoder_slave.h>
> +#include <drm/drm_of.h>
> +#include <drm/drm_probe_helper.h>
> +#include <drm/drm_print.h>
> +#include <drm/drm_vblank.h>
> +
> +#include "cdns-mhdp-common.h"
> +
> +void cdns_mhdp_infoframe_set(struct cdns_mhdp_device *mhdp,
> +					u8 entry_id, u8 packet_len, 
u8 *packet, u8 packet_type)
> +{
> +	u32 *packet32, len32;
> +	u32 val, i;
> +
> +	/* invalidate entry */
> +	val = F_ACTIVE_IDLE_TYPE(1) | F_PKT_ALLOC_ADDRESS(entry_id);
> +	writel(val, mhdp->regs + SOURCE_PIF_PKT_ALLOC_REG);
> +	writel(F_PKT_ALLOC_WR_EN(1), mhdp->regs + 
SOURCE_PIF_PKT_ALLOC_WR_EN);
> +
> +	/* flush fifo 1 */
> +	writel(F_FIFO1_FLUSH(1), mhdp->regs + SOURCE_PIF_FIFO1_FLUSH);
> +
> +	/* write packet into memory */
> +	packet32 = (u32 *)packet;

This only works on little-endian machines, no?

> +	len32 = packet_len / 4;
> +	for (i = 0; i < len32; i++)
> +		writel(F_DATA_WR(packet32[i]), mhdp->regs + 
SOURCE_PIF_DATA_WR);
> +
> +	/* write entry id */
> +	writel(F_WR_ADDR(entry_id), mhdp->regs + SOURCE_PIF_WR_ADDR);
> +
> +	/* write request */
> +	writel(F_HOST_WR(1), mhdp->regs + SOURCE_PIF_WR_REQ);
> +
> +	/* update entry */
> +	val =  F_ACTIVE_IDLE_TYPE(1) | F_TYPE_VALID(1) |
> +			F_PACKET_TYPE(packet_type) | 
F_PKT_ALLOC_ADDRESS(entry_id);
> +	writel(val, mhdp->regs + SOURCE_PIF_PKT_ALLOC_REG);
> +
> +	writel(F_PKT_ALLOC_WR_EN(1), mhdp->regs + 
SOURCE_PIF_PKT_ALLOC_WR_EN);
> +}
> +
> +static int cdns_hdmi_get_edid_block(void *data, u8 *edid,
> +			  u32 block, size_t length)
> +{
> +	struct cdns_mhdp_device *mhdp = data;
> +	u8 msg[2], reg[5], i;
> +	int ret;
> +
> +	mutex_lock(&mhdp->mbox_mutex);
> +
> +	for (i = 0; i < 4; i++) {

What is i? It is not used inside the loop.

> +		msg[0] = block / 2;
> +		msg[1] = block % 2;
> +
> +		ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_HDMI_TX, 
HDMI_TX_EDID,
> +					  sizeof(msg), msg);
> +		if (ret)
> +			continue;
> +
> +		ret = cdns_mhdp_mailbox_recv_header(mhdp, 
MB_MODULE_ID_HDMI_TX,
> +						      
HDMI_TX_EDID, sizeof(reg) + length);
> +		if (ret)
> +			continue;
> +
> +		ret = cdns_mhdp_mailbox_recv_data(mhdp, reg, sizeof(reg));
> +		if (ret)
> +			continue;
> +
> +		ret = cdns_mhdp_mailbox_recv_data(mhdp, edid, length);
> +		if (ret)
> +			continue;
> +
> +		if ((reg[3] << 8 | reg[4]) == length)
> +			break;
> +	}
> +
> +	mutex_unlock(&mhdp->mbox_mutex);
> +
> +	if (ret)
> +		DRM_ERROR("get block[%d] edid failed: %d\n", block, ret);
> +	return ret;
> +}
> +
> +int cdns_hdmi_scdc_read(struct cdns_mhdp_device *mhdp, u8 addr, u8 *data)
> +{
> +	u8 msg[4], reg[6];
> +	int ret;
> +
> +	msg[0] = 0x54;
> +	msg[1] = addr;
> +	msg[2] = 0;
> +	msg[3] = 1;
> +
> +	mutex_lock(&mhdp->mbox_mutex);
> +
> +	ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_HDMI_TX, 
HDMI_TX_READ,
> +				  sizeof(msg), msg);
> +	if (ret)
> +		goto err_scdc_read;
> +
> +	ret = cdns_mhdp_mailbox_recv_header(mhdp, MB_MODULE_ID_HDMI_TX,
> +					      HDMI_TX_READ, 
sizeof(reg));
> +	if (ret)
> +		goto err_scdc_read;
> +
> +	ret = cdns_mhdp_mailbox_recv_data(mhdp, reg, sizeof(reg));
> +	if (ret)
> +		goto err_scdc_read;
> +
> +	*data = reg[5];
> +
> +err_scdc_read:
> +
> +	mutex_unlock(&mhdp->mbox_mutex);
> +
> +	if (ret)
> +		DRM_ERROR("scdc read failed: %d\n", ret);
> +	return ret;
> +}
> +
> +int cdns_hdmi_scdc_write(struct cdns_mhdp_device *mhdp, u8 addr, u8 value)
> +{
> +	u8 msg[5], reg[5];
> +	int ret;
> +
> +	msg[0] = 0x54;
> +	msg[1] = addr;
> +	msg[2] = 0;
> +	msg[3] = 1;
> +	msg[4] = value;
> +
> +	mutex_lock(&mhdp->mbox_mutex);
> +
> +	ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_HDMI_TX, 
HDMI_TX_WRITE,
> +				  sizeof(msg), msg);
> +	if (ret)
> +		goto err_scdc_write;
> +
> +	ret = cdns_mhdp_mailbox_recv_header(mhdp, MB_MODULE_ID_HDMI_TX,
> +					      HDMI_TX_WRITE, 
sizeof(reg));
> +	if (ret)
> +		goto err_scdc_write;
> +
> +	ret = cdns_mhdp_mailbox_recv_data(mhdp, reg, sizeof(reg));
> +	if (ret)
> +		goto err_scdc_write;
> +
> +	if (reg[0] != 0)
> +		ret = -EINVAL;
> +
> +err_scdc_write:
> +
> +	mutex_unlock(&mhdp->mbox_mutex);
> +
> +	if (ret)
> +		DRM_ERROR("scdc write failed: %d\n", ret);
> +	return ret;
> +}
> +
> +int cdns_hdmi_ctrl_init(struct cdns_mhdp_device *mhdp,
> +				 int protocol, u32 char_rate)
> +{
> +	u32 reg0;
> +	u32 reg1;
> +	u32 val;
> +	int ret;
> +
> +	/* Set PHY to HDMI data */
> +	ret = cdns_mhdp_reg_write(mhdp, PHY_DATA_SEL, 
F_SOURCE_PHY_MHDP_SEL(1));
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = cdns_mhdp_reg_write(mhdp, HDTX_HPD,
> +					F_HPD_VALID_WIDTH(4) | 
F_HPD_GLITCH_WIDTH(0));
> +	if (ret < 0)
> +		return ret;
> +
> +	/* open CARS */
> +	ret = cdns_mhdp_reg_write(mhdp, SOURCE_PHY_CAR, 0xF);
> +	if (ret < 0)
> +		return ret;
> +	ret = cdns_mhdp_reg_write(mhdp, SOURCE_HDTX_CAR, 0xFF);
> +	if (ret < 0)
> +		return ret;
> +	ret = cdns_mhdp_reg_write(mhdp, SOURCE_PKT_CAR, 0xF);
> +	if (ret < 0)
> +		return ret;
> +	ret = cdns_mhdp_reg_write(mhdp, SOURCE_AIF_CAR, 0xF);
> +	if (ret < 0)
> +		return ret;
> +	ret = cdns_mhdp_reg_write(mhdp, SOURCE_CIPHER_CAR, 0xF);
> +	if (ret < 0)
> +		return ret;
> +	ret = cdns_mhdp_reg_write(mhdp, SOURCE_CRYPTO_CAR, 0xF);
> +	if (ret < 0)
> +		return ret;
> +	ret = cdns_mhdp_reg_write(mhdp, SOURCE_CEC_CAR, 3);
> +	if (ret < 0)
> +		return ret;
> +
> +	reg0 = reg1 = 0x7c1f;
> +	if (protocol == MODE_HDMI_2_0 && char_rate >= 340000) {
> +		reg0 = 0;
> +		reg1 = 0xFFFFF;
> +	}
> +	ret = cdns_mhdp_reg_write(mhdp, HDTX_CLOCK_REG_0, reg0);
> +	if (ret < 0)
> +		return ret;
> +	ret = cdns_mhdp_reg_write(mhdp, HDTX_CLOCK_REG_1, reg1);
> +	if (ret < 0)
> +		return ret;
> +
> +	/* set hdmi mode and preemble mode data enable */
> +	val = F_HDMI_MODE(protocol) | F_HDMI2_PREAMBLE_EN(1) |  F_DATA_EN(1) 
|
> +			F_HDMI2_CTRL_IL_MODE(1) | F_BCH_EN(1) | 
F_PIC_3D(0XF);
> +	ret = cdns_mhdp_reg_write(mhdp, HDTX_CONTROLLER, val);
> +
> +	return ret;
> +}
> +
> +int cdns_hdmi_mode_config(struct cdns_mhdp_device *mhdp,
> +					      struct 
drm_display_mode *mode,
> +						  struct 
video_info *video_info)
> +{
> +	int ret;
> +	u32 val;
> +	u32 vsync_lines = mode->vsync_end - mode->vsync_start;
> +	u32 eof_lines = mode->vsync_start - mode->vdisplay;
> +	u32 sof_lines = mode->vtotal - mode->vsync_end;
> +	u32 hblank = mode->htotal - mode->hdisplay;
> +	u32 hactive = mode->hdisplay;
> +	u32 vblank = mode->vtotal - mode->vdisplay;
> +	u32 vactive = mode->vdisplay;
> +	u32 hfront = mode->hsync_start - mode->hdisplay;
> +	u32 hback = mode->htotal - mode->hsync_end;
> +	u32 vfront = eof_lines;
> +	u32 hsync = hblank - hfront - hback;
> +	u32 vsync = vsync_lines;
> +	u32 vback = sof_lines;
> +	u32 v_h_polarity = ((mode->flags & DRM_MODE_FLAG_NHSYNC) ? 0 : 1) +
> +						((mode->flags & 
DRM_MODE_FLAG_NVSYNC) ? 0 : 2);

Please fix the alignment.

> +
> +	ret = cdns_mhdp_reg_write(mhdp, SCHEDULER_H_SIZE, (hactive << 16) +
> hblank); +	if (ret < 0)
> +		return ret;
> +
> +	ret = cdns_mhdp_reg_write(mhdp, SCHEDULER_V_SIZE, (vactive << 16) +
> vblank); +	if (ret < 0)
> +		return ret;
> +
> +	ret = cdns_mhdp_reg_write(mhdp, HDTX_SIGNAL_FRONT_WIDTH, (vfront << 
16) +
> hfront); +	if (ret < 0)
> +		return ret;
> +
> +	ret = cdns_mhdp_reg_write(mhdp, HDTX_SIGNAL_SYNC_WIDTH, (vsync << 
16) +
> hsync); +	if (ret < 0)
> +		return ret;
> +
> +	ret = cdns_mhdp_reg_write(mhdp, HDTX_SIGNAL_BACK_WIDTH, (vback << 
16) +
> hback); +	if (ret < 0)
> +		return ret;
> +
> +	ret = cdns_mhdp_reg_write(mhdp, HSYNC2VSYNC_POL_CTRL, v_h_polarity);
> +	if (ret < 0)
> +		return ret;
> +
> +	/* Reset Data Enable */
> +	cdns_mhdp_reg_read(mhdp, HDTX_CONTROLLER, &val);
> +	val &= ~F_DATA_EN(1);
> +	ret = cdns_mhdp_reg_write(mhdp, HDTX_CONTROLLER, val);
> +	if (ret < 0)
> +		return ret;
> +
> +	/* Set bpc */
> +	val &= ~F_VIF_DATA_WIDTH(3);
> +	switch (video_info->bpc) {
> +	case 10:
> +		val |= F_VIF_DATA_WIDTH(1);
> +		break;
> +	case 12:
> +		val |= F_VIF_DATA_WIDTH(2);
> +		break;
> +	case 16:
> +		val |= F_VIF_DATA_WIDTH(3);
> +		break;
> +	case 8:
> +	default:
> +		val |= F_VIF_DATA_WIDTH(0);
> +		break;
> +	}
> +
> +	/* select color encoding */
> +	val &= ~F_HDMI_ENCODING(3);
> +	switch (video_info->color_fmt) {
> +	case DRM_COLOR_FORMAT_YCBCR444:
> +		val |= F_HDMI_ENCODING(2);
> +		break;
> +	case DRM_COLOR_FORMAT_YCBCR422:
> +		val |= F_HDMI_ENCODING(1);
> +		break;
> +	case DRM_COLOR_FORMAT_YCBCR420:
> +		val |= F_HDMI_ENCODING(3);
> +		break;
> +	case DRM_COLOR_FORMAT_RGB444:
> +	default:
> +		val |= F_HDMI_ENCODING(0);
> +		break;
> +	}
> +
> +	ret = cdns_mhdp_reg_write(mhdp, HDTX_CONTROLLER, val);
> +	if (ret < 0)
> +		return ret;
> +
> +	/* set data enable */
> +	val |= F_DATA_EN(1);
> +	ret = cdns_mhdp_reg_write(mhdp, HDTX_CONTROLLER, val);
> +
> +	return ret;
> +}
> +
> +int cdns_hdmi_disable_gcp(struct cdns_mhdp_device *mhdp)
> +{
> +	u32 val;
> +
> +	cdns_mhdp_reg_read(mhdp, HDTX_CONTROLLER, &val);
> +	val &= ~F_GCP_EN(1);
> +
> +	return cdns_mhdp_reg_write(mhdp, HDTX_CONTROLLER, val);
> +}
> +
> +int cdns_hdmi_enable_gcp(struct cdns_mhdp_device *mhdp)
> +{
> +	u32 val;
> +
> +	cdns_mhdp_reg_read(mhdp, HDTX_CONTROLLER, &val);
> +	val |= F_GCP_EN(1);
> +
> +	return cdns_mhdp_reg_write(mhdp, HDTX_CONTROLLER, val);
> +}
> +
> +static void hdmi_sink_config(struct cdns_mhdp_device *mhdp)
> +{
> +	struct drm_scdc *scdc = &mhdp->curr_conn->display_info.hdmi.scdc;
> +	u8 buff = 0;
> +
> +	/* Default work in HDMI1.4 */
> +	mhdp->hdmi.hdmi_type = MODE_HDMI_1_4;
> +
> +	/* check sink support SCDC or not */
> +	if (scdc->supported != true) {
> +		DRM_INFO("Sink Not Support SCDC\n");
> +		return;
> +	}
> +
> +	if (mhdp->hdmi.char_rate > 340000) {
> +		/*
> +		 * TMDS Character Rate above 340MHz should working in 
HDMI2.0
> +		 * Enable scrambling and TMDS_Bit_Clock_Ratio
> +		 */
> +		buff = SCDC_TMDS_BIT_CLOCK_RATIO_BY_40 | 
SCDC_SCRAMBLING_ENABLE;
> +		mhdp->hdmi.hdmi_type = MODE_HDMI_2_0;
> +	} else  if (scdc->scrambling.low_rates) {
> +		/*
> +		 * Enable scrambling and HDMI2.0 when scrambling 
capability of sink
> +		 * be indicated in the HF-VSDB LTE_340Mcsc_scramble bit
> +		 */
> +		buff = SCDC_SCRAMBLING_ENABLE;
> +		mhdp->hdmi.hdmi_type = MODE_HDMI_2_0;
> +	}
> +
> +	/* TMDS config */
> +	cdns_hdmi_scdc_write(mhdp, SCDC_TMDS_CONFIG, buff);
> +}
> +
> +static void hdmi_lanes_config(struct cdns_mhdp_device *mhdp)
> +{
> +	u32 lane_mapping = mhdp->plat_data->lane_mapping;
> +	/* Line swapping */
> +	cdns_mhdp_reg_write(mhdp, LANES_CONFIG, 0x00400000 | lane_mapping);
> +}
> +
> +int cdns_mhdp_hdmi_read_hpd(struct cdns_mhdp_device *mhdp)
> +{
> +	u8 status;
> +	int ret;
> +
> +	mutex_lock(&mhdp->mbox_mutex);
> +
> +	ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_HDMI_TX,
> HDMI_TX_HPD_STATUS, +				  0, NULL);
> +	if (ret)
> +		goto err_get_hpd;
> +
> +	ret = cdns_mhdp_mailbox_recv_header(mhdp, MB_MODULE_ID_HDMI_TX,
> +							
HDMI_TX_HPD_STATUS, sizeof(status));
> +	if (ret)
> +		goto err_get_hpd;
> +
> +	ret = cdns_mhdp_mailbox_recv_data(mhdp, &status, sizeof(status));
> +	if (ret)
> +		goto err_get_hpd;
> +
> +	mutex_unlock(&mhdp->mbox_mutex);
> +	return status;
> +
> +err_get_hpd:
> +	mutex_unlock(&mhdp->mbox_mutex);
> +	DRM_ERROR("read hpd  failed: %d\n", ret);
> +	return ret;
> +}
> +
> +static int hdmi_avi_info_set(struct cdns_mhdp_device *mhdp,
> +			     struct drm_display_mode *mode)
> +{
> +	struct hdmi_avi_infoframe frame;
> +	int format = mhdp->video_info.color_fmt;
> +	struct drm_connector_state *conn_state = mhdp->curr_conn->state;
> +	struct drm_display_mode *adj_mode;
> +	enum hdmi_quantization_range qr;
> +	u8 buf[32];
> +	int ret;
> +
> +	/* Initialise info frame from DRM mode */
> +	drm_hdmi_avi_infoframe_from_display_mode(&frame,
> +						mhdp->curr_conn, 
mode);
> +
> +	switch (format) {
> +	case DRM_COLOR_FORMAT_YCBCR444:
> +		frame.colorspace = HDMI_COLORSPACE_YUV444;
> +		break;
> +	case DRM_COLOR_FORMAT_YCBCR422:
> +		frame.colorspace = HDMI_COLORSPACE_YUV422;
> +		break;
> +	case DRM_COLOR_FORMAT_YCBCR420:
> +		frame.colorspace = HDMI_COLORSPACE_YUV420;
> +		break;
> +	default:
> +		frame.colorspace = HDMI_COLORSPACE_RGB;
> +		break;
> +	}
> +
> +	drm_hdmi_avi_infoframe_colorimetry(&frame, conn_state);
> +
> +	adj_mode = &mhdp->bridge.encoder->crtc->state->adjusted_mode;
> +
> +	qr = drm_default_rgb_quant_range(adj_mode);
> +
> +	drm_hdmi_avi_infoframe_quant_range(&frame, mhdp->curr_conn,
> +					   adj_mode, qr);
> +
> +	ret = hdmi_avi_infoframe_check(&frame);
> +	if (WARN_ON(ret))
> +		return -EINVAL;
> +
> +	ret = hdmi_avi_infoframe_pack(&frame, buf + 1, sizeof(buf) - 1);
> +	if (ret < 0) {
> +		DRM_ERROR("failed to pack AVI infoframe: %d\n", ret);
> +		return -1;
> +	}
> +
> +	buf[0] = 0;
> +	cdns_mhdp_infoframe_set(mhdp, 0, sizeof(buf), buf,
> HDMI_INFOFRAME_TYPE_AVI); +
> +	return 0;
> +}
> +
> +static void hdmi_vendor_info_set(struct cdns_mhdp_device *mhdp,
> +				struct drm_display_mode *mode)
> +{
> +	struct hdmi_vendor_infoframe frame;
> +	u8 buf[32];
> +	int ret;
> +
> +	/* Initialise vendor frame from DRM mode */
> +	ret = drm_hdmi_vendor_infoframe_from_display_mode(&frame, mhdp-
>curr_conn,
> mode); +	if (ret < 0) {
> +		DRM_INFO("No vendor infoframe\n");
> +		return;
> +	}
> +
> +	ret = hdmi_vendor_infoframe_pack(&frame, buf + 1, sizeof(buf) - 1);
> +	if (ret < 0) {
> +		DRM_WARN("Unable to pack vendor infoframe: %d\n", ret);
> +		return;
> +	}
> +
> +	buf[0] = 0;
> +	cdns_mhdp_infoframe_set(mhdp, 3, sizeof(buf), buf,
> HDMI_INFOFRAME_TYPE_VENDOR); +}
> +
> +static void hdmi_drm_info_set(struct cdns_mhdp_device *mhdp)
> +{
> +	struct drm_connector_state *conn_state;
> +	struct hdmi_drm_infoframe frame;
> +	u8 buf[32];
> +	int ret;
> +
> +	conn_state = mhdp->curr_conn->state;
> +
> +	if (!conn_state->hdr_output_metadata)
> +		return;
> +
> +	ret = drm_hdmi_infoframe_set_hdr_metadata(&frame, conn_state);
> +	if (ret < 0) {
> +		DRM_DEBUG_KMS("couldn't set HDR metadata in infoframe\n");
> +		return;
> +	}
> +
> +	ret = hdmi_drm_infoframe_pack(&frame, buf + 1, sizeof(buf) - 1);
> +	if (ret < 0) {
> +		DRM_DEBUG_KMS("couldn't pack HDR infoframe\n");
> +		return;
> +	}
> +
> +	buf[0] = 0;
> +	cdns_mhdp_infoframe_set(mhdp, 3, sizeof(buf), buf,
> HDMI_INFOFRAME_TYPE_DRM); +}
> +
> +static int hdmi_phy_colorspace(int color_fmt)
> +{
> +	int color_space;
> +
> +	switch (color_fmt) {
> +	case DRM_COLOR_FORMAT_YCBCR444:
> +		color_space = HDMI_PHY_COLORSPACE_YUV444;
> +		break;
> +	case DRM_COLOR_FORMAT_YCBCR422:
> +		color_space = HDMI_PHY_COLORSPACE_YUV422;
> +		break;
> +	case DRM_COLOR_FORMAT_YCBCR420:
> +		color_space = HDMI_PHY_COLORSPACE_YUV420;
> +		break;
> +	case DRM_COLOR_FORMAT_RGB444:
> +	default:
> +		color_space = HDMI_PHY_COLORSPACE_RGB;
> +		break;
> +	}
> +
> +	return color_space;
> +}
> +
> +void cdns_hdmi_mode_set(struct cdns_mhdp_device *mhdp)
> +{
> +	struct drm_display_mode *mode = &mhdp->mode;
> +	union phy_configure_opts phy_cfg;
> +	int ret;
> +
> +	/* video mode check */
> +	if (mode->clock == 0 || mode->hdisplay == 0 ||  mode->vdisplay == 0)
> +		return;
> +
> +	hdmi_lanes_config(mhdp);
> +
> +	phy_cfg.hdmi.pixel_clk_rate = mode->clock;
> +	phy_cfg.hdmi.bpc = mhdp->video_info.bpc;
> +	phy_cfg.hdmi.color_space =
> hdmi_phy_colorspace(mhdp->video_info.color_fmt); +	ret =
> phy_configure(mhdp->phy,  &phy_cfg);
> +	if (ret) {
> +		dev_err(mhdp->dev, "%s: phy_configure() failed: %d\n",
> +			__func__, ret);
> +		return;
> +	}
> +
> +	hdmi_sink_config(mhdp);
> +
> +	ret = cdns_hdmi_ctrl_init(mhdp, mhdp->hdmi.hdmi_type,
> mhdp->hdmi.char_rate); +	if (ret < 0) {
> +		DRM_ERROR("%s, ret = %d\n", __func__, ret);
> +		return;
> +	}
> +
> +	/* Config GCP */
> +	if (mhdp->video_info.bpc == 8)
> +		cdns_hdmi_disable_gcp(mhdp);
> +	else
> +		cdns_hdmi_enable_gcp(mhdp);
> +
> +	ret = hdmi_avi_info_set(mhdp, mode);
> +	if (ret < 0) {
> +		DRM_ERROR("%s ret = %d\n", __func__, ret);
> +		return;
> +	}
> +
> +	/* vendor info frame is enabled only for HDMI1.4 4K mode */
> +	hdmi_vendor_info_set(mhdp, mode);
> +
> +	hdmi_drm_info_set(mhdp);
> +
> +	ret = cdns_hdmi_mode_config(mhdp, mode, &mhdp->video_info);
> +	if (ret < 0) {
> +		DRM_ERROR("CDN_API_HDMITX_SetVic_blocking ret = %d\n", 
ret);
> +		return;
> +	}
> +}
> +static enum drm_connector_status
> +cdns_hdmi_detect(struct cdns_mhdp_device *mhdp)
> +{
> +	u8 hpd = 0xf;
> +
> +	hpd = cdns_mhdp_hdmi_read_hpd(mhdp);
> +	if (hpd == 1)
> +		return connector_status_connected;
> +	else if (hpd == 0)
> +		return connector_status_disconnected;
> +
> +	DRM_INFO("Unknown cable status, hdp=%u\n", hpd);
> +	return connector_status_unknown;
> +}
> +
> +static enum drm_connector_status
> +cdns_hdmi_bridge_detect(struct drm_bridge *bridge)
> +{
> +	struct cdns_mhdp_device *mhdp = bridge->driver_private;
> +
> +	return cdns_hdmi_detect(mhdp);
> +}
> +
> +static int cdns_hdmi_connector_get_modes(struct drm_connector *connector)
> +{
> +	struct cdns_mhdp_device *mhdp =
> +				container_of(connector, struct 
cdns_mhdp_device, connector);
> +	int num_modes = 0;
> +	struct edid *edid;
> +
> +	edid = drm_do_get_edid(connector,
> +				   cdns_hdmi_get_edid_block, mhdp);
> +	if (edid) {
> +		dev_info(mhdp->dev, "%x,%x,%x,%x,%x,%x,%x,%x\n",
> +			 edid->header[0], edid->header[1],
> +			 edid->header[2], edid->header[3],
> +			 edid->header[4], edid->header[5],
> +			 edid->header[6], edid->header[7]);
> +		drm_connector_update_edid_property(connector, edid);
> +		num_modes = drm_add_edid_modes(connector, edid);
> +		kfree(edid);
> +	}
> +
> +	if (num_modes == 0)
> +		DRM_ERROR("Invalid edid\n");
> +	return num_modes;
> +}
> +
> +static bool blob_equal(const struct drm_property_blob *a,
> +		       const struct drm_property_blob *b)
> +{
> +	if (a && b)
> +		return a->length == b->length &&
> +			!memcmp(a->data, b->data, a->length);
> +
> +	return !a == !b;
> +}
> +
> +static int cdns_hdmi_connector_atomic_check(struct drm_connector
> *connector, +					    struct 
drm_atomic_state *state)
> +{
> +	struct drm_connector_state *new_con_state =
> +		drm_atomic_get_new_connector_state(state, connector);
> +	struct drm_connector_state *old_con_state =
> +		drm_atomic_get_old_connector_state(state, connector);
> +	struct drm_crtc *crtc = new_con_state->crtc;
> +	struct drm_crtc_state *new_crtc_state;
> +
> +	if (!blob_equal(new_con_state->hdr_output_metadata,
> +			old_con_state->hdr_output_metadata) ||
> +	    new_con_state->colorspace != old_con_state->colorspace) {
> +		new_crtc_state = drm_atomic_get_crtc_state(state, crtc);
> +		if (IS_ERR(new_crtc_state))
> +			return PTR_ERR(new_crtc_state);
> +
> +		new_crtc_state->mode_changed =
> +			!new_con_state->hdr_output_metadata ||
> +			!old_con_state->hdr_output_metadata ||
> +			new_con_state->colorspace != old_con_state-
>colorspace;
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct drm_connector_funcs cdns_hdmi_connector_funcs = {
> +	.fill_modes = drm_helper_probe_single_connector_modes,
> +	.destroy = drm_connector_cleanup,
> +	.reset = drm_atomic_helper_connector_reset,
> +	.atomic_duplicate_state = 
drm_atomic_helper_connector_duplicate_state,
> +	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> +};
> +
> +static const struct drm_connector_helper_funcs
> cdns_hdmi_connector_helper_funcs = { +	.get_modes =
> cdns_hdmi_connector_get_modes,
> +	.atomic_check = cdns_hdmi_connector_atomic_check,
> +};
> +
> +static int cdns_hdmi_bridge_attach(struct drm_bridge *bridge,
> +				 enum drm_bridge_attach_flags flags)
> +{
> +	struct cdns_mhdp_device *mhdp = bridge->driver_private;
> +	struct drm_mode_config *config = &bridge->dev->mode_config;
> +	struct drm_encoder *encoder = bridge->encoder;
> +	struct drm_connector *connector = &mhdp->connector;
> +
> +	if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) {
> +		connector->interlace_allowed = 0;
> +		connector->polled = DRM_CONNECTOR_POLL_HPD;
> +
> +		drm_connector_helper_add(connector, 
&cdns_hdmi_connector_helper_funcs);
> +
> +		drm_connector_init(bridge->dev, connector, 
&cdns_hdmi_connector_funcs,
> +				   DRM_MODE_CONNECTOR_HDMIA);
> +
> +		drm_object_attach_property(&connector->base,
> +					   config-
>hdr_output_metadata_property, 0);
> +
> +		if (!drm_mode_create_hdmi_colorspace_property(connector))
> +			drm_object_attach_property(&connector->base,
> +						connector-
>colorspace_property, 0);
> +
> +		drm_connector_attach_encoder(connector, encoder);
> +	}
> +
> +	return 0;
> +}
> +
> +static enum drm_mode_status
> +cdns_hdmi_bridge_mode_valid(struct drm_bridge *bridge,
> +				const struct drm_display_info *info,
> +				const struct drm_display_mode *mode)
> +{
> +	struct cdns_mhdp_device *mhdp = bridge->driver_private;
> +	enum drm_mode_status mode_status = MODE_OK;
> +	union phy_configure_opts phy_cfg;
> +	int ret;
> +
> +	/* We don't support double-clocked and Interlaced modes */
> +	if (mode->flags & DRM_MODE_FLAG_DBLCLK ||
> +			mode->flags & DRM_MODE_FLAG_INTERLACE)
> +		return MODE_BAD;
> +
> +	/* MAX support pixel clock rate 594MHz */
> +	if (mode->clock > 594000)
> +		return MODE_CLOCK_HIGH;
> +
> +	/* 4096x2160 is not supported */
> +	if (mode->hdisplay > 3840 || mode->vdisplay > 2160)
> +		return MODE_BAD_HVALUE;
> +
> +	/* Check modes supported by PHY */
> +	phy_cfg.hdmi.pixel_clk_rate = mode->clock;
> +	ret = phy_validate(mhdp->phy, PHY_MODE_HDMI, 0, &phy_cfg);
> +	if (ret < 0)
> +		return MODE_CLOCK_RANGE;
> +
> +	return mode_status;
> +}
> +
> +bool cdns_hdmi_bridge_mode_fixup(struct drm_bridge *bridge,
> +				 const struct drm_display_mode *mode,
> +				 struct drm_display_mode 
*adjusted_mode)
> +{
> +	struct cdns_mhdp_device *mhdp = bridge->driver_private;
> +	struct video_info *video = &mhdp->video_info;
> +
> +	video->bpc = 8;
> +	video->color_fmt = DRM_COLOR_FORMAT_RGB444;
> +
> +	return true;
> +}
> +
> +static struct edid *cdns_hdmi_bridge_get_edid(struct drm_bridge *bridge,
> +					      struct drm_connector 
*connector)
> +{
> +	struct cdns_mhdp_device *mhdp = bridge->driver_private;
> +
> +	return drm_do_get_edid(connector, cdns_hdmi_get_edid_block, mhdp);
> +}
> +
> +static void cdns_hdmi_bridge_atomic_disable(struct drm_bridge *bridge,
> +					  struct drm_bridge_state 
*old_state)
> +{
> +	struct cdns_mhdp_device *mhdp = bridge->driver_private;
> +
> +	mhdp->curr_conn = NULL;
> +
> +	mutex_lock(&mhdp->lock);
> +	phy_power_off(mhdp->phy);
> +	mutex_unlock(&mhdp->lock);
> +}
> +
> +static void cdns_hdmi_bridge_atomic_enable(struct drm_bridge *bridge,
> +					 struct drm_bridge_state 
*old_state)
> +{
> +	struct cdns_mhdp_device *mhdp = bridge->driver_private;
> +	struct drm_atomic_state *state = old_state->base.state;
> +	struct drm_connector *connector;
> +	struct video_info *video = &mhdp->video_info;
> +	struct drm_crtc_state *crtc_state;
> +	struct drm_connector_state *conn_state;
> +	const struct drm_display_mode *mode;
> +
> +	connector = drm_atomic_get_new_connector_for_encoder(state,
> +							     
bridge->encoder);
> +	if (WARN_ON(!connector))
> +		return;
> +
> +	mhdp->curr_conn = connector;
> +
> +	conn_state = drm_atomic_get_new_connector_state(state, connector);
> +	if (WARN_ON(!conn_state))
> +		return;
> +
> +	crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc);
> +	if (WARN_ON(!crtc_state))
> +		return;
> +
> +	mode = &crtc_state->adjusted_mode;
> +	DRM_INFO("Mode: %dx%dp%d\n", mode->hdisplay, mode->vdisplay, mode-
>clock);
> +	memcpy(&mhdp->mode, mode, sizeof(struct drm_display_mode));
> +
> +	video->v_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NVSYNC);
> +	video->h_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NHSYNC);
> +
> +	mutex_lock(&mhdp->lock);
> +	cdns_hdmi_mode_set(mhdp);
> +	mutex_unlock(&mhdp->lock);
> +}
> +
> +static const struct drm_bridge_funcs cdns_hdmi_bridge_funcs = {
> +	.attach = cdns_hdmi_bridge_attach,
> +	.detect = cdns_hdmi_bridge_detect,
> +	.get_edid = cdns_hdmi_bridge_get_edid,
> +	.mode_valid = cdns_hdmi_bridge_mode_valid,
> +	.mode_fixup = cdns_hdmi_bridge_mode_fixup,
> +	.atomic_enable = cdns_hdmi_bridge_atomic_enable,
> +	.atomic_disable = cdns_hdmi_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,
> +};
> +
> +static void hotplug_work_func(struct work_struct *work)
> +{
> +	struct cdns_mhdp_device *mhdp = container_of(work,
> +					   struct cdns_mhdp_device, 
hotplug_work.work);
> +	enum drm_connector_status status = cdns_hdmi_detect(mhdp);
> +
> +	drm_bridge_hpd_notify(&mhdp->bridge, status);
> +
> +	if (status == connector_status_connected) {
> +		DRM_INFO("HDMI Cable Plug In\n");
> +		enable_irq(mhdp->irq[IRQ_OUT]);
> +	} else if (status == connector_status_disconnected) {
> +		/* Cable Disconnedted  */
> +		DRM_INFO("HDMI Cable Plug Out\n");
> +		enable_irq(mhdp->irq[IRQ_IN]);
> +	}
> +}
> +
> +static irqreturn_t cdns_hdmi_irq_thread(int irq, void *data)
> +{
> +	struct cdns_mhdp_device *mhdp = data;
> +
> +	disable_irq_nosync(irq);
> +
> +	mod_delayed_work(system_wq, &mhdp->hotplug_work,
> +			msecs_to_jiffies(HOTPLUG_DEBOUNCE_MS));
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int cdns_mhdp_imx_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct cdns_mhdp_device *mhdp;
> +	struct platform_device_info pdevinfo;
> +	struct resource *res;
> +	u32 reg;
> +	int ret;
> +
> +	mhdp = devm_kzalloc(dev, sizeof(*mhdp), GFP_KERNEL);
> +	if (!mhdp)
> +		return -ENOMEM;
> +
> +	mutex_init(&mhdp->lock);
> +	mutex_init(&mhdp->mbox_mutex);
> +
> +	INIT_DELAYED_WORK(&mhdp->hotplug_work, hotplug_work_func);
> +
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	if (!res)
> +		return -ENODEV;
> +	mhdp->regs = devm_ioremap(dev, res->start, resource_size(res));
> +	if (IS_ERR(mhdp->regs))
> +		return PTR_ERR(mhdp->regs);

Please use devm_platform_get_and_ioremap_resource.

> +	mhdp->phy = devm_of_phy_get_by_index(dev, pdev->dev.of_node, 0);
> +	if (IS_ERR(mhdp->phy)) {
> +		dev_err(dev, "no PHY configured\n");
> +		return PTR_ERR(mhdp->phy);
> +	}

Please use dev_err_probe().

> +	mhdp->irq[IRQ_IN] = platform_get_irq_byname(pdev, "plug_in");
> +	if (mhdp->irq[IRQ_IN] < 0) {
> +		dev_info(dev, "No plug_in irq number\n");
> +		return -EPROBE_DEFER;
> +	}

Please use dev_err_probe().

> +	mhdp->irq[IRQ_OUT] = platform_get_irq_byname(pdev, "plug_out");
> +	if (mhdp->irq[IRQ_OUT] < 0) {
> +		dev_info(dev, "No plug_out irq number\n");
> +		return -EPROBE_DEFER;
> +	}

Please use dev_err_probe().

> +	/*
> +	 * Wait for the KEEP_ALIVE "message" on the first 8 bits.
> +	 * Updated each sched "tick" (~2ms)
> +	 */
> +	ret = readl_poll_timeout(mhdp->regs + KEEP_ALIVE, reg,
> +				 reg & CDNS_KEEP_ALIVE_MASK, 500,
> +				 CDNS_KEEP_ALIVE_TIMEOUT);

This freezes my board TQMa8MQ (arch/arm64/boot/dts/freescale/imx8mq-tqma8mq-
mba8mx.dts) completly if this and the PHY driver are compiled in. I have 
"pd_ignore_unused clk_ignore_unused" passed to kernel command line, so I have 
no idea what's wrong here.

Best regards,
Alexander

> +	if (ret) {
> +		dev_err(mhdp->dev,
> +			"device didn't give any life sign: reg %d\n", 
reg);
> +		return ret;
> +	}
> +
> +	ret = phy_init(mhdp->phy);
> +	if (ret) {
> +		dev_err(mhdp->dev, "Failed to initialize PHY: %d\n", ret);
> +		return -ENODEV;
> +	}
> +
> +	/* Enable Hotplug Detect thread */
> +	irq_set_status_flags(mhdp->irq[IRQ_IN], IRQ_NOAUTOEN);
> +	ret = devm_request_threaded_irq(dev, mhdp->irq[IRQ_IN],
> +					NULL, cdns_hdmi_irq_thread,
> +					IRQF_ONESHOT, dev_name(dev),
> +					mhdp);
> +	if (ret < 0) {
> +		dev_err(dev, "can't claim irq %d\n",
> +						mhdp-
>irq[IRQ_IN]);
> +		return -EINVAL;
> +	}
> +
> +	irq_set_status_flags(mhdp->irq[IRQ_OUT], IRQ_NOAUTOEN);
> +	ret = devm_request_threaded_irq(dev, mhdp->irq[IRQ_OUT],
> +					NULL, cdns_hdmi_irq_thread,
> +					IRQF_ONESHOT, dev_name(dev),
> +					mhdp);
> +	if (ret < 0) {
> +		dev_err(dev, "can't claim irq %d\n",
> +						mhdp-
>irq[IRQ_OUT]);
> +		return -EINVAL;
> +	}
> +
> +	mhdp->dev = dev;
> +
> +	if (cdns_mhdp_hdmi_read_hpd(mhdp))
> +		enable_irq(mhdp->irq[IRQ_OUT]);
> +	else
> +		enable_irq(mhdp->irq[IRQ_IN]);
> +
> +	mhdp->bridge.driver_private = mhdp;
> +	mhdp->bridge.funcs = &cdns_hdmi_bridge_funcs;
> +	mhdp->bridge.of_node = dev->of_node;
> +	mhdp->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID |
> +			   DRM_BRIDGE_OP_HPD;
> +	mhdp->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
> +	drm_bridge_add(&mhdp->bridge);
> +
> +	memset(&pdevinfo, 0, sizeof(pdevinfo));
> +	pdevinfo.parent = dev;
> +	pdevinfo.id = PLATFORM_DEVID_AUTO;
> +
> +	dev_set_drvdata(dev, mhdp);
> +	mhdp->plat_data = of_device_get_match_data(dev);
> +
> +	return 0;
> +}
> +
> +static int cdns_mhdp_imx_remove(struct platform_device *pdev)
> +{
> +	struct cdns_mhdp_device *mhdp = platform_get_drvdata(pdev);
> +	int ret = 0;
> +
> +	drm_bridge_remove(&mhdp->bridge);
> +
> +	return ret;
> +}
> +
> +static struct cdns_plat_data imx8mq_hdmi_drv_data = {
> +	.lane_mapping = 0xe4,
> +};
> +
> +static const struct of_device_id cdns_mhdp_imx_dt_ids[] = {
> +	{ .compatible = "cdns,mhdp-imx8mq-hdmi",
> +	  .data = &imx8mq_hdmi_drv_data
> +	},
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, cdns_mhdp_imx_dt_ids);
> +
> +static struct platform_driver cdns_mhdp_imx_platform_driver = {
> +	.probe  = cdns_mhdp_imx_probe,
> +	.remove = cdns_mhdp_imx_remove,
> +	.driver = {
> +		.name = "cdns-mhdp-imx8mq-hdmi",
> +		.of_match_table = cdns_mhdp_imx_dt_ids,
> +	},
> +};
> +
> +module_platform_driver(cdns_mhdp_imx_platform_driver);
> +
> +MODULE_AUTHOR("Sandor Yu <sandor.yu at nxp.com>");
> +MODULE_DESCRIPTION("Cadence HDMI transmitter driver");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:cdns-hdmi");







More information about the linux-arm-kernel mailing list