[v2,06/10] drm: bridge: cadence: Add MHDP HDMI driver for i.MX8MQ
Commit Message
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@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
Comments
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@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@nxp.com>");
> +MODULE_DESCRIPTION("Cadence HDMI transmitter driver");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:cdns-hdmi");
Thanks for your comments.
> -----Original Message-----
> From: Alexander Stein <alexander.stein@ew.tq-group.com>
> Sent: 2022年11月8日 21:17
> To: jonas@kwiboo.se; Sandor Yu <sandor.yu@nxp.com>
> Cc: dri-devel@lists.freedesktop.org; devicetree@vger.kernel.org;
> linux-arm-kernel@lists.infradead.org; linux-kernel@vger.kernel.org;
> linux-phy@lists.infradead.org; andrzej.hajda@intel.com;
> neil.armstrong@linaro.org; robert.foss@linaro.org;
> Laurent.pinchart@ideasonboard.com; jernej.skrabec@gmail.com;
> kishon@ti.com; vkoul@kernel.org; Oliver Brown <oliver.brown@nxp.com>;
> krzysztof.kozlowski+dt@linaro.org; sam@ravnborg.org; jani.nikula@intel.com;
> tzimmermann@suse.de; s.hauer@pengutronix.de; javierm@redhat.com;
> penguin-kernel@i-love.sakura.ne.jp; robh+dt@kernel.org; dl-linux-imx
> <linux-imx@nxp.com>; kernel@pengutronix.de; Sandor Yu
> <sandor.yu@nxp.com>; shawnguo@kernel.org; p.yadav@ti.com;
> maxime@cerno.tech
> Subject: [EXT] Re: [v2 06/10] drm: bridge: cadence: Add MHDP HDMI driver
> for i.MX8MQ
>
> Caution: EXT Email
>
> 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@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?
Register SOURCE_PIF_DATA_WR require little-endian data,
I will use get_unaligned_le32() to replace it in the next version.
>
> > + 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.
EDID data read by HDMI firmware are not guarantee 100% successful.
Base on experiments, try 4 times if EDID read failed.
>
> > + 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.
OK.
>
> > +
> > + 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.
Both HDMI PHY driver and mhdp HDMI driver should access same APB base register offset for mailbox.
devm_ioremap_resource could not support such feature.
>
> > + 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().
OK.
>
> > + 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().
OK.
>
> > + 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().
OK.
>
> > + /*
> > + * 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.
Here is the first time in the driver to access hdmi register when driver probe.
For imx8mq hdmi/dp, mdhp hdmi apb clock and core clock are managed by ROM code, they are always on when device bootup.
Could you dump the clock tree without "pd_ignore_unused clk_ignore_unused" ?
>
> Best regards,
> Alexander
>
B.R
Sandor
> > + 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@nxp.com>");
> > +MODULE_DESCRIPTION("Cadence HDMI transmitter driver");
> > +MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:cdns-hdmi");
>
>
>
Hi Sandor,
Am Mittwoch, 9. November 2022, 14:26:14 CET schrieb Sandor Yu:
> Thanks for your comments.
>
>
> > -----Original Message-----
> > From: Alexander Stein <alexander.stein@ew.tq-group.com>
> > Sent: 2022年11月8日 21:17
> > To: jonas@kwiboo.se; Sandor Yu <sandor.yu@nxp.com>
> > Cc: dri-devel@lists.freedesktop.org; devicetree@vger.kernel.org;
> > linux-arm-kernel@lists.infradead.org; linux-kernel@vger.kernel.org;
> > linux-phy@lists.infradead.org; andrzej.hajda@intel.com;
> > neil.armstrong@linaro.org; robert.foss@linaro.org;
> > Laurent.pinchart@ideasonboard.com; jernej.skrabec@gmail.com;
> > kishon@ti.com; vkoul@kernel.org; Oliver Brown <oliver.brown@nxp.com>;
> > krzysztof.kozlowski+dt@linaro.org; sam@ravnborg.org;
> > jani.nikula@intel.com;
tzimmermann@suse.de; s.hauer@pengutronix.de;
> > javierm@redhat.com;
> > penguin-kernel@i-love.sakura.ne.jp; robh+dt@kernel.org; dl-linux-imx
> > <linux-imx@nxp.com>; kernel@pengutronix.de; Sandor Yu
> > <sandor.yu@nxp.com>; shawnguo@kernel.org; p.yadav@ti.com;
> > maxime@cerno.tech
> > Subject: [EXT] Re: [v2 06/10] drm: bridge: cadence: Add MHDP HDMI driver
> > for i.MX8MQ
> >
> > Caution: EXT Email
> >
> > 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@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
[snip]
> > > +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.
>
> EDID data read by HDMI firmware are not guarantee 100% successful.
> Base on experiments, try 4 times if EDID read failed.
Mh, 4 times sounds a bit too arbitrary to me. How about using a timeout in ms,
like 50ms, for retrying to read the EDID?
[snip]
> > > +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.
>
> Both HDMI PHY driver and mhdp HDMI driver should access same APB base
> register offset for mailbox.
devm_ioremap_resource could not support such
> feature.
Oh I see, both remap the same range. To be honest I do not like this. Is there
a need to map overlapping ranges in both drivers? How can you avoid race
conditions due to simultaneous accesses?
> > > + 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().
>
> OK.
>
> >
> >
> > > + 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().
>
> OK.
>
> >
> >
> > > + 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().
>
> OK.
>
> >
> >
> > > + /*
> > > + * 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.
>
> Here is the first time in the driver to access hdmi register when driver
> probe.
For imx8mq hdmi/dp, mdhp hdmi apb clock and core clock are managed
> by ROM code, they are always on when device bootup. Could you dump the
> clock tree without "pd_ignore_unused clk_ignore_unused" ?
I noticed too this is the 1st access, so I have no idea what's wrong here.
Here is my /sys/kernel/debug/clk/clk_summary from using the regular DT without
enabling 'dcss', 'hdmi_phy' and 'mhdp_hdmi':
enable prepare protect
duty hardware
clock count count count rate
accuracy phase cycle enable
-------------------------------------------------------------------------------------------------------
sys2_pll_out 7 7 0 1000000000
0 0 50000 Y
sys_pll2_out_monitor 0 0 0 1000000000
0 0 50000 Y
sys2_pll_1000m 0 0 0 1000000000
0 0 50000 Y
sys2_pll_500m 1 2 0 500000000
0 0 50000 Y
audio_ahb 0 1 0 500000000
0 0 50000 N
ipg_audio_root 0 1 0 250000000
0 0 50000 Y
sdma2_clk 0 2 0 250000000
0 0 50000 N
sai6_ipg_clk 0 0 0 250000000
0 0 50000 N
sai5_ipg_clk 0 0 0 250000000
0 0 50000 N
sai4_ipg_clk 0 0 0 250000000
0 0 50000 N
sai1_ipg_clk 0 0 0 250000000
0 0 50000 N
nand 0 0 0 500000000
0 0 50000 N
nand_root_clk 0 0 0 500000000
0 0 50000 N
usb_bus 2 2 0 500000000
0 0 50000 Y
usb2_ctrl_root_clk 1 1 0 500000000
0 0 50000 Y
usb1_ctrl_root_clk 1 1 0 500000000
0 0 50000 Y
sys2_pll_333m 1 1 0 333333333
0 0 50000 Y
main_axi 1 1 0 333333333
0 0 50000 Y
sys2_pll_250m 2 2 0 250000000
0 0 50000 Y
pcie1_ctrl 1 1 0 250000000
0 0 50000 Y
pcie1_root_clk 1 1 0 250000000
0 0 50000 Y
pcie2_ctrl 1 1 0 250000000
0 0 50000 Y
pcie2_root_clk 1 1 0 250000000
0 0 50000 Y
sys2_pll_200m 3 3 0 200000000
0 0 50000 Y
ecspi3 0 0 0 200000000
0 0 50000 N
ecspi3_root_clk 0 0 0 200000000
0 0 50000 N
ecspi2 1 1 0 200000000
0 0 50000 Y
ecspi2_root_clk 2 2 0 200000000
0 0 50000 Y
ecspi1 1 1 0 200000000
0 0 50000 Y
ecspi1_root_clk 2 2 0 200000000
0 0 50000 Y
gic 1 1 0 200000000
0 0 50000 Y
arm_m4_core 0 0 0 200000000
0 0 50000 N
sys2_pll_166m 0 0 0 166666666
0 0 50000 Y
sys2_pll_125m 1 1 0 125000000
0 0 50000 Y
enet_ref 1 1 0 125000000
0 0 50000 Y
sys2_pll_100m 3 3 0 100000000
0 0 50000 Y
pcie1_phy 1 1 0 100000000
0 0 50000 Y
pcie2_phy 1 1 0 100000000
0 0 50000 Y
enet_timer 1 1 0 100000000
0 0 50000 Y
sys2_pll_50m 1 1 0 50000000
0 0 50000 Y
enet_phy 1 1 0 50000000
0 0 50000 Y
sys1_pll_out 5 5 0 800000000
0 0 50000 Y
sys_pll1_out_monitor 0 0 0 800000000
0 0 50000 Y
sys1_pll_800m 2 2 0 800000000
0 0 50000 Y
vpu_bus 0 0 0 800000000
0 0 50000 N
vpu_dec_root_clk 0 0 0 800000000
0 0 50000 N
arm_a53_div 0 0 0 800000000
0 0 50000 N
dram_apb 1 1 0 160000000
0 0 50000 Y
noc 1 1 0 800000000
0 0 50000 Y
disp_rtrm 0 0 0 400000000
0 0 50000 N
disp_rtrm_root_clk 0 0 0 400000000
0 0 50000 N
disp_apb 0 0 0 133333334
0 0 50000 N
disp_apb_root_clk 0 0 0 133333334
0 0 50000 N
disp_axi 0 0 0 800000000
0 0 50000 N
disp_axi_root_clk 0 0 0 800000000
0 0 50000 N
sys1_pll_400m 0 0 0 400000000
0 0 50000 Y
usdhc2 0 0 0 400000000
0 0 50000 N
usdhc2_root_clk 0 0 0 400000000
0 0 50000 N
usdhc1 0 0 0 400000000
0 0 50000 N
usdhc1_root_clk 0 0 0 400000000
0 0 50000 N
sys1_pll_266m 1 1 0 266666666
0 0 50000 Y
nand_usdhc_bus 0 0 0 266666666
0 0 50000 N
nand_usdhc_rawnand_clk 0 0 0 266666666
0 0 50000 N
enet_axi 1 1 0 266666666
0 0 50000 Y
enet1_root_clk 2 2 0 266666666
0 0 50000 Y
sys1_pll_200m 0 0 0 200000000
0 0 50000 Y
sys1_pll_160m 0 0 0 160000000
0 0 50000 Y
sys1_pll_133m 1 1 0 133333333
0 0 50000 Y
ahb 9 4 0 133333333
0 0 50000 Y
ipg_root 8 8 0 66666667
0 0 50000 Y
sdma1_clk 6 1 0 66666667
0 0 50000 Y
tmu_root_clk 1 1 0 66666667
0 0 50000 Y
sai3_ipg_clk 0 0 0 66666667
0 0 50000 N
sai2_ipg_clk 0 0 0 66666667
0 0 50000 N
ocotp_root_clk 0 0 0 66666667
0 0 50000 N
mu_root_clk 0 0 0 66666667
0 0 50000 N
gpio5_root_clk 1 1 0 66666667
0 0 50000 Y
gpio4_root_clk 1 1 0 66666667
0 0 50000 Y
gpio3_root_clk 1 1 0 66666667
0 0 50000 Y
gpio2_root_clk 1 1 0 66666667
0 0 50000 Y
gpio1_root_clk 1 1 0 66666667
0 0 50000 Y
sys1_pll_100m 2 2 0 100000000
0 0 50000 Y
usb_phy_ref 2 2 0 100000000
0 0 50000 Y
usb2_phy_root_clk 1 1 0 100000000
0 0 50000 Y
usb1_phy_root_clk 1 1 0 100000000
0 0 50000 Y
usb_core_ref 2 2 0 100000000
0 0 50000 Y
qspi 0 0 0 100000000
0 0 50000 N
qspi_root_clk 0 0 0 100000000
0 0 50000 N
dram_alt 0 0 0 100000000
0 0 50000 N
dram_alt_root 0 0 0 25000000
0 0 50000 Y
sys1_pll_80m 2 2 0 80000000
0 0 50000 Y
pcie1_aux 1 1 0 10000000
0 0 50000 Y
pcie2_aux 1 1 0 10000000
0 0 50000 Y
uart2 0 0 0 80000000
0 0 50000 N
uart2_root_clk 0 0 0 80000000
0 0 50000 N
uart1 0 0 0 80000000
0 0 50000 N
uart1_root_clk 0 0 0 80000000
0 0 50000 N
sys1_pll_40m 0 0 0 40000000
0 0 50000 Y
wrclk 0 0 0 40000000
0 0 50000 N
dummy 0 0 0 0
0 0 50000 Y
clk-xtal25 2 2 0 25000000
0 0 50000 Y
DIF3 0 0 0 100000000
0 0 50000 Y
DIF2 1 1 0 100000000
0 0 50000 Y
DIF1 0 0 0 100000000
0 0 50000 Y
DIF0 1 1 0 100000000
0 0 50000 Y
clock 0 0 0 32768
0 0 50000 Y
clk_ext4 0 0 0 133000000
0 0 50000 Y
clk_ext3 0 0 0 133000000
0 0 50000 Y
clk_ext2 0 0 0 133000000
0 0 50000 Y
clk_ext1 0 0 0 133000000
0 0 50000 Y
hdmi_phy_27m 0 0 0 27000000
0 0 50000 Y
osc_27m 0 0 0 27000000
0 0 50000 Y
osc_25m 7 11 0 25000000
0 0 50000 Y
gpt_3m 0 0 0 3125000
0 0 50000 Y
csi2_esc 0 0 0 25000000
0 0 50000 N
csi2_phy_ref 0 0 0 25000000
0 0 50000 N
csi2_core 0 0 0 25000000
0 0 50000 N
csi2_root_clk 0 0 0 25000000
0 0 50000 N
csi1_esc 0 0 0 25000000
0 0 50000 N
csi1_phy_ref 0 0 0 25000000
0 0 50000 N
csi1_core 0 0 0 25000000
0 0 50000 N
csi1_root_clk 0 0 0 25000000
0 0 50000 N
dsi_ahb 0 0 0 25000000
0 0 50000 N
dsi_ipg_div 0 0 0 12500000
0 0 50000 Y
dsi_esc 0 0 0 25000000
0 0 50000 N
dsi_dbi 0 0 0 25000000
0 0 50000 N
dsi_phy_ref 0 0 0 25000000
0 0 50000 N
dsi_core 0 0 0 25000000
0 0 50000 N
clko2 0 0 0 25000000
0 0 50000 N
clko1 0 0 0 25000000
0 0 50000 N
wdog 1 1 0 25000000
0 0 50000 Y
wdog3_root_clk 0 0 0 25000000
0 0 50000 N
wdog2_root_clk 0 0 0 25000000
0 0 50000 N
wdog1_root_clk 1 1 0 25000000
0 0 50000 Y
gpt1 0 0 0 25000000
0 0 50000 N
gpt1_root_clk 0 0 0 25000000
0 0 50000 N
pwm4 0 0 0 25000000
0 0 50000 N
pwm4_root_clk 0 0 0 25000000
0 0 50000 N
pwm3 0 0 0 25000000
0 0 50000 N
pwm3_root_clk 0 0 0 25000000
0 0 50000 N
pwm2 0 0 0 25000000
0 0 50000 N
pwm2_root_clk 0 0 0 25000000
0 0 50000 N
pwm1 0 0 0 25000000
0 0 50000 N
pwm1_root_clk 0 0 0 25000000
0 0 50000 N
uart4 0 0 0 25000000
0 0 50000 N
uart4_root_clk 0 0 0 25000000
0 0 50000 N
uart3 1 1 0 25000000
0 0 50000 Y
uart3_root_clk 4 4 0 25000000
0 0 50000 Y
i2c4 0 0 0 25000000
0 0 50000 N
i2c4_root_clk 0 0 0 25000000
0 0 50000 N
i2c3 0 1 0 25000000
0 0 50000 N
i2c3_root_clk 0 1 0 25000000
0 0 50000 N
i2c2 0 1 0 25000000
0 0 50000 N
i2c2_root_clk 0 1 0 25000000
0 0 50000 N
i2c1 0 1 0 25000000
0 0 50000 N
i2c1_root_clk 0 1 0 25000000
0 0 50000 N
spdif2 0 0 0 25000000
0 0 50000 N
spdif1 0 0 0 25000000
0 0 50000 N
sai6 0 0 0 25000000
0 0 50000 N
sai6_root_clk 0 0 0 25000000
0 0 50000 N
sai5 0 0 0 25000000
0 0 50000 N
sai5_root_clk 0 0 0 25000000
0 0 50000 N
sai4 0 0 0 25000000
0 0 50000 N
sai4_root_clk 0 0 0 25000000
0 0 50000 N
sai2 0 0 0 25000000
0 0 50000 N
sai2_root_clk 0 0 0 25000000
0 0 50000 N
sai1 0 0 0 25000000
0 0 50000 N
sai1_root_clk 0 0 0 25000000
0 0 50000 N
lcdif_pixel 0 0 0 25000000
0 0 50000 N
disp_dc8000 0 0 0 25000000
0 0 50000 N
disp_root_clk 0 0 0 25000000
0 0 50000 N
disp_dtrc 0 0 0 25000000
0 0 50000 N
noc_apb 1 1 0 25000000
0 0 50000 Y
video2_pll1_ref_sel 0 0 0 25000000
0 0 50000 Y
video2_pll_out 0 0 0 25000000
0 0 50000 Y
video_pll2_out_monitor 0 0 0 25000000
0 0 50000 Y
dram_pll1_ref_sel 1 1 0 25000000
0 0 50000 Y
dram_pll_out 2 2 0 800000000
0 0 50000 Y
dram_core_clk 1 1 0 800000000
0 0 50000 Y
dram_pll_out_monitor 0 0 0 800000000
0 0 50000 Y
sys3_pll1_ref_sel 1 1 0 25000000
0 0 50000 Y
sys3_pll_out 1 1 0 25000000
0 0 50000 Y
sys_pll3_out_monitor 0 0 0 25000000
0 0 50000 Y
video_pll1_ref_sel 0 0 0 25000000
0 0 50000 Y
video_pll1_bypass 0 0 0 25000000
0 0 50000 Y
video_pll1_out_monitor 0 0 0 25000000
0 0 50000 Y
video_pll1_out 0 0 0 25000000
0 0 50000 N
dc_pixel 0 0 0 5000000
0 0 50000 N
video_pll1_ref_div 0 0 0 5000000
0 0 50000 Y
video_pll1 0 0 0 650000000
0 0 50000 Y
audio_pll2_ref_sel 0 0 0 25000000
0 0 50000 Y
audio_pll2_ref_div 0 0 0 5000000
0 0 50000 Y
audio_pll2 0 0 0 722534397
0 0 50000 Y
audio_pll2_bypass 0 0 0 722534397
0 0 50000 Y
audio_pll2_out_monitor 0 0 0 722534397
0 0 50000 Y
audio_pll2_out 0 0 0 722534397
0 0 50000 N
audio_pll1_ref_sel 0 0 0 25000000
0 0 50000 Y
audio_pll1_ref_div 0 0 0 5000000
0 0 50000 Y
audio_pll1 0 0 0 786431998
0 0 50000 Y
audio_pll1_bypass 0 0 0 786431998
0 0 50000 Y
audio_pll1_out_monitor 0 0 0 786431998
0 0 50000 Y
audio_pll1_out 0 0 0 786431998
0 0 50000 N
sai3 0 0 0 49152000
0 0 50000 N
sai3_root_clk 0 0 0 49152000
0 0 50000 N
pll 0 0 0 196608000
0 0 50000 Y
codec_clkin 0 0 0 196608000
0 0 50000 Y
nadc 0 0 0 196608000
0 0 50000 Y
madc 0 0 0 196608000
0 0 50000 Y
ndac 0 0 0 196608000
0 0 50000 Y
mdac 0 0 0 196608000
0 0 50000 Y
bdiv 0 0 0
196608000 0 0 50000 Y
vpu_pll_ref_sel 0 1 0 25000000
0 0 50000 Y
vpu_pll_ref_div 0 1 0 5000000
0 0 50000 Y
vpu_pll 0 1 0 600000000
0 0 50000 Y
vpu_pll_bypass 0 1 0 600000000
0 0 50000 Y
vpu_pll_out_monitor 0 0 0 600000000
0 0 50000 Y
vpu_pll_out 0 2 0 600000000
0 0 50000 N
vpu_g2 0 1 0 600000000
0 0 50000 N
vpu_g2_root_clk 0 1 0 600000000
0 0 50000 N
vpu_g1 0 1 0 600000000
0 0 50000 N
vpu_g1_root_clk 0 1 0 600000000
0 0 50000 N
gpu_pll_ref_sel 0 0 0 25000000
0 0 50000 Y
gpu_pll_ref_div 0 0 0 5000000
0 0 50000 Y
gpu_pll 0 0 0 800000000
0 0 50000 Y
gpu_pll_bypass 0 0 0 800000000
0 0 50000 Y
gpu_pll_out_monitor 0 0 0 800000000
0 0 50000 Y
pllout_monitor_sel 0 0 0 800000000
0 0 50000 Y
pllout_monitor_clk2 0 0 0
800000000 0 0 50000 N
gpu_pll_out 0 0 0 800000000
0 0 50000 N
gpu_ahb 0 0 0 800000000
0 0 50000 N
gpu_axi 0 0 0 800000000
0 0 50000 N
gpu_shader 0 0 0 800000000
0 0 50000 N
gpu_core 0 0 0 800000000
0 0 50000 N
gpu_root_clk 0 0 0 800000000
0 0 50000 N
arm_pll_ref_sel 1 1 0 25000000
0 0 50000 Y
arm_pll_ref_div 1 1 0 5000000
0 0 50000 Y
arm_pll 1 1 0 800000000
0 0 50000 Y
arm_pll_bypass 1 1 0 800000000
0 0 50000 Y
arm_pll_out_monitor 0 0 0 800000000
0 0 50000 Y
arm_pll_out 1 1 0 800000000
0 0 50000 Y
arm_a53_core 1 1 0 800000000
0 0 50000 Y
arm 1 1 0 800000000
0 0 50000 Y
vpu_core 0 0 0 800000000
0 0 50000 N
ckil 2 2 0 32768
0 0 50000 Y
Thanks and best regards
Alexander
Hi Alexander,
> -----Original Message-----
> From: Alexander Stein <alexander.stein@ew.tq-group.com>
> Sent: 2022年11月10日 23:44
> To: Sandor Yu <sandor.yu@nxp.com>
> Cc: jonas@kwiboo.se; dri-devel@lists.freedesktop.org;
> devicetree@vger.kernel.org; linux-arm-kernel@lists.infradead.org;
> linux-kernel@vger.kernel.org; linux-phy@lists.infradead.org;
> andrzej.hajda@intel.com; neil.armstrong@linaro.org; robert.foss@linaro.org;
> Laurent.pinchart@ideasonboard.com; jernej.skrabec@gmail.com;
> kishon@ti.com; vkoul@kernel.org; Oliver Brown <oliver.brown@nxp.com>;
> krzysztof.kozlowski+dt@linaro.org; sam@ravnborg.org;
> tzimmermann@suse.de; s.hauer@pengutronix.de; javierm@redhat. com
> <javierm@redhat.com>; penguin-kernel@i-love.sakura.ne.jp;
> robh+dt@kernel.org; dl-linux-imx <linux-imx@nxp.com>;
> kernel@pengutronix.de; shawnguo@kernel.org; p.yadav@ti.com;
> maxime@cerno.tech
> Subject: RE: [EXT] Re: [v2 06/10] drm: bridge: cadence: Add MHDP HDMI
> driver for i.MX8MQ
>
> Caution: EXT Email
>
> Hi Sandor,
>
> Am Mittwoch, 9. November 2022, 14:26:14 CET schrieb Sandor Yu:
> > Thanks for your comments.
> >
> >
> > > -----Original Message-----
> > > From: Alexander Stein <alexander.stein@ew.tq-group.com>
> > > Sent: 2022年11月8日 21:17
> > > To: jonas@kwiboo.se; Sandor Yu <sandor.yu@nxp.com>
> > > Cc: dri-devel@lists.freedesktop.org; devicetree@vger.kernel.org;
> > > linux-arm-kernel@lists.infradead.org; linux-kernel@vger.kernel.org;
> > > linux-phy@lists.infradead.org; andrzej.hajda@intel.com;
> > > neil.armstrong@linaro.org; robert.foss@linaro.org;
> > > Laurent.pinchart@ideasonboard.com; jernej.skrabec@gmail.com;
> > > kishon@ti.com; vkoul@kernel.org; Oliver Brown
> > > <oliver.brown@nxp.com>; krzysztof.kozlowski+dt@linaro.org;
> > > sam@ravnborg.org; jani.nikula@intel.com;
> tzimmermann@suse.de; s.hauer@pengutronix.de;
> > > javierm@redhat.com;
> > > penguin-kernel@i-love.sakura.ne.jp; robh+dt@kernel.org; dl-linux-imx
> > > <linux-imx@nxp.com>; kernel@pengutronix.de; Sandor Yu
> > > <sandor.yu@nxp.com>; shawnguo@kernel.org; p.yadav@ti.com;
> > > maxime@cerno.tech
> > > Subject: [EXT] Re: [v2 06/10] drm: bridge: cadence: Add MHDP HDMI
> > > driver for i.MX8MQ
> > >
> > > Caution: EXT Email
> > >
> > > 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@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
>
> [snip]
>
> > > > +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.
> >
> > EDID data read by HDMI firmware are not guarantee 100% successful.
> > Base on experiments, try 4 times if EDID read failed.
>
> Mh, 4 times sounds a bit too arbitrary to me. How about using a timeout in ms,
> like 50ms, for retrying to read the EDID?
>
For EDID read failed case, the mailbox read will timeout, additional timeout in the loop is not necessary.
> [snip]
>
> > > > +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.
> >
> > Both HDMI PHY driver and mhdp HDMI driver should access same APB base
> > register offset for mailbox.
> devm_ioremap_resource could not support such
> > feature.
>
> Oh I see, both remap the same range. To be honest I do not like this. Is there
> a need to map overlapping ranges in both drivers? How can you avoid race
> conditions due to simultaneous accesses?
>
To separate HDMI controller driver and PHY driver, I have to remap those registers in different drivers.
Race conditions could be avoid in HDMI driver when PHY function is called.
> > > > + 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().
> >
> > OK.
> >
> > >
> > >
> > > > + 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().
> >
> > OK.
> >
> > >
> > >
> > > > + 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().
> >
> > OK.
> >
> > >
> > >
> > > > + /*
> > > > + * 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.
> >
> > Here is the first time in the driver to access hdmi register when
> > driver probe.
> For imx8mq hdmi/dp, mdhp hdmi apb clock and core clock are managed
> > by ROM code, they are always on when device bootup. Could you dump the
> > clock tree without "pd_ignore_unused clk_ignore_unused" ?
>
> I noticed too this is the 1st access, so I have no idea what's wrong here.
> Here is my /sys/kernel/debug/clk/clk_summary from using the regular DT
> without enabling 'dcss', 'hdmi_phy' and 'mhdp_hdmi':
>
> enable prepare protect duty
> hardware
> clock count count count
> rate
> accuracy phase cycle enable
> ---------------------------------------------------------------------------------------------
> ----------
> sys2_pll_out 7 7 0
> 1000000000
> 0 0 50000 Y
> sys_pll2_out_monitor 0 0 0
> 1000000000
> 0 0 50000 Y
> sys2_pll_1000m 0 0 0
> 1000000000
> 0 0 50000 Y
> sys2_pll_500m 1 2 0
> 500000000
> 0 0 50000 Y
> audio_ahb 0 1 0
> 500000000
> 0 0 50000 N
> ipg_audio_root 0 1 0
> 250000000
> 0 0 50000 Y
> sdma2_clk 0 2 0
> 250000000
> 0 0 50000 N
> sai6_ipg_clk 0 0 0
> 250000000
> 0 0 50000 N
> sai5_ipg_clk 0 0 0
> 250000000
> 0 0 50000 N
> sai4_ipg_clk 0 0 0
> 250000000
> 0 0 50000 N
> sai1_ipg_clk 0 0 0
> 250000000
> 0 0 50000 N
> nand 0 0 0
> 500000000
> 0 0 50000 N
> nand_root_clk 0 0 0
> 500000000
> 0 0 50000 N
> usb_bus 2 2 0
> 500000000
> 0 0 50000 Y
> usb2_ctrl_root_clk 1 1 0
> 500000000
> 0 0 50000 Y
> usb1_ctrl_root_clk 1 1 0
> 500000000
> 0 0 50000 Y
> sys2_pll_333m 1 1 0
> 333333333
> 0 0 50000 Y
> main_axi 1 1 0
> 333333333
> 0 0 50000 Y
> sys2_pll_250m 2 2 0
> 250000000
> 0 0 50000 Y
> pcie1_ctrl 1 1 0
> 250000000
> 0 0 50000 Y
> pcie1_root_clk 1 1 0
> 250000000
> 0 0 50000 Y
> pcie2_ctrl 1 1 0
> 250000000
> 0 0 50000 Y
> pcie2_root_clk 1 1 0
> 250000000
> 0 0 50000 Y
> sys2_pll_200m 3 3 0
> 200000000
> 0 0 50000 Y
> ecspi3 0 0 0
> 200000000
> 0 0 50000 N
> ecspi3_root_clk 0 0 0
> 200000000
> 0 0 50000 N
> ecspi2 1 1 0
> 200000000
> 0 0 50000 Y
> ecspi2_root_clk 2 2 0
> 200000000
> 0 0 50000 Y
> ecspi1 1 1 0
> 200000000
> 0 0 50000 Y
> ecspi1_root_clk 2 2 0
> 200000000
> 0 0 50000 Y
> gic 1 1 0
> 200000000
> 0 0 50000 Y
> arm_m4_core 0 0 0
> 200000000
> 0 0 50000 N
> sys2_pll_166m 0 0 0
> 166666666
> 0 0 50000 Y
> sys2_pll_125m 1 1 0
> 125000000
> 0 0 50000 Y
> enet_ref 1 1 0
> 125000000
> 0 0 50000 Y
> sys2_pll_100m 3 3 0
> 100000000
> 0 0 50000 Y
> pcie1_phy 1 1 0
> 100000000
> 0 0 50000 Y
> pcie2_phy 1 1 0
> 100000000
> 0 0 50000 Y
> enet_timer 1 1 0
> 100000000
> 0 0 50000 Y
> sys2_pll_50m 1 1 0
> 50000000
> 0 0 50000 Y
> enet_phy 1 1 0
> 50000000
> 0 0 50000 Y
> sys1_pll_out 5 5 0
> 800000000
> 0 0 50000 Y
> sys_pll1_out_monitor 0 0 0
> 800000000
> 0 0 50000 Y
> sys1_pll_800m 2 2 0
> 800000000
> 0 0 50000 Y
> vpu_bus 0 0 0
> 800000000
> 0 0 50000 N
> vpu_dec_root_clk 0 0 0
> 800000000
> 0 0 50000 N
> arm_a53_div 0 0 0
> 800000000
> 0 0 50000 N
> dram_apb 1 1 0
> 160000000
> 0 0 50000 Y
> noc 1 1 0
> 800000000
> 0 0 50000 Y
> disp_rtrm 0 0 0
> 400000000
> 0 0 50000 N
> disp_rtrm_root_clk 0 0 0
> 400000000
> 0 0 50000 N
> disp_apb 0 0 0
> 133333334
> 0 0 50000 N
> disp_apb_root_clk 0 0 0
> 133333334
> 0 0 50000 N
> disp_axi 0 0 0
> 800000000
> 0 0 50000 N
> disp_axi_root_clk 0 0 0
> 800000000
> 0 0 50000 N
> sys1_pll_400m 0 0 0
> 400000000
> 0 0 50000 Y
> usdhc2 0 0 0
> 400000000
> 0 0 50000 N
> usdhc2_root_clk 0 0 0
> 400000000
> 0 0 50000 N
> usdhc1 0 0 0
> 400000000
> 0 0 50000 N
> usdhc1_root_clk 0 0 0
> 400000000
> 0 0 50000 N
> sys1_pll_266m 1 1 0
> 266666666
> 0 0 50000 Y
> nand_usdhc_bus 0 0 0
> 266666666
> 0 0 50000 N
> nand_usdhc_rawnand_clk 0 0 0
> 266666666
> 0 0 50000 N
> enet_axi 1 1 0
> 266666666
> 0 0 50000 Y
> enet1_root_clk 2 2 0
> 266666666
> 0 0 50000 Y
> sys1_pll_200m 0 0 0
> 200000000
> 0 0 50000 Y
> sys1_pll_160m 0 0 0
> 160000000
> 0 0 50000 Y
> sys1_pll_133m 1 1 0
> 133333333
> 0 0 50000 Y
> ahb 9 4 0
> 133333333
> 0 0 50000 Y
> ipg_root 8 8 0
> 66666667
> 0 0 50000 Y
> sdma1_clk 6 1 0
> 66666667
> 0 0 50000 Y
> tmu_root_clk 1 1 0
> 66666667
> 0 0 50000 Y
> sai3_ipg_clk 0 0 0
> 66666667
> 0 0 50000 N
> sai2_ipg_clk 0 0 0
> 66666667
> 0 0 50000 N
> ocotp_root_clk 0 0 0
> 66666667
> 0 0 50000 N
> mu_root_clk 0 0 0
> 66666667
> 0 0 50000 N
> gpio5_root_clk 1 1 0
> 66666667
> 0 0 50000 Y
> gpio4_root_clk 1 1 0
> 66666667
> 0 0 50000 Y
> gpio3_root_clk 1 1 0
> 66666667
> 0 0 50000 Y
> gpio2_root_clk 1 1 0
> 66666667
> 0 0 50000 Y
> gpio1_root_clk 1 1 0
> 66666667
> 0 0 50000 Y
> sys1_pll_100m 2 2 0
> 100000000
> 0 0 50000 Y
> usb_phy_ref 2 2 0
> 100000000
> 0 0 50000 Y
> usb2_phy_root_clk 1 1 0
> 100000000
> 0 0 50000 Y
> usb1_phy_root_clk 1 1 0
> 100000000
> 0 0 50000 Y
> usb_core_ref 2 2 0
> 100000000
> 0 0 50000 Y
> qspi 0 0 0
> 100000000
> 0 0 50000 N
> qspi_root_clk 0 0 0
> 100000000
> 0 0 50000 N
> dram_alt 0 0 0
> 100000000
> 0 0 50000 N
> dram_alt_root 0 0 0
> 25000000
> 0 0 50000 Y
> sys1_pll_80m 2 2 0
> 80000000
> 0 0 50000 Y
> pcie1_aux 1 1 0
> 10000000
> 0 0 50000 Y
> pcie2_aux 1 1 0
> 10000000
> 0 0 50000 Y
> uart2 0 0 0
> 80000000
> 0 0 50000 N
> uart2_root_clk 0 0 0
> 80000000
> 0 0 50000 N
> uart1 0 0 0
> 80000000
> 0 0 50000 N
> uart1_root_clk 0 0 0
> 80000000
> 0 0 50000 N
> sys1_pll_40m 0 0 0
> 40000000
> 0 0 50000 Y
> wrclk 0 0 0
> 40000000
> 0 0 50000 N
> dummy 0 0 0
> 0
> 0 0 50000 Y
> clk-xtal25 2 2 0
> 25000000
> 0 0 50000 Y
> DIF3 0 0 0
> 100000000
> 0 0 50000 Y
> DIF2 1 1 0
> 100000000
> 0 0 50000 Y
> DIF1 0 0 0
> 100000000
> 0 0 50000 Y
> DIF0 1 1 0
> 100000000
> 0 0 50000 Y
> clock 0 0 0
> 32768
> 0 0 50000 Y
> clk_ext4 0 0 0
> 133000000
> 0 0 50000 Y
> clk_ext3 0 0 0
> 133000000
> 0 0 50000 Y
> clk_ext2 0 0 0
> 133000000
> 0 0 50000 Y
> clk_ext1 0 0 0
> 133000000
> 0 0 50000 Y
> hdmi_phy_27m 0 0 0
> 27000000
> 0 0 50000 Y
> osc_27m 0 0 0
> 27000000
> 0 0 50000 Y
> osc_25m 7 11 0
> 25000000
> 0 0 50000 Y
> gpt_3m 0 0 0
> 3125000
> 0 0 50000 Y
> csi2_esc 0 0 0
> 25000000
> 0 0 50000 N
> csi2_phy_ref 0 0 0
> 25000000
> 0 0 50000 N
> csi2_core 0 0 0
> 25000000
> 0 0 50000 N
> csi2_root_clk 0 0 0
> 25000000
> 0 0 50000 N
> csi1_esc 0 0 0
> 25000000
> 0 0 50000 N
> csi1_phy_ref 0 0 0
> 25000000
> 0 0 50000 N
> csi1_core 0 0 0
> 25000000
> 0 0 50000 N
> csi1_root_clk 0 0 0
> 25000000
> 0 0 50000 N
> dsi_ahb 0 0 0
> 25000000
> 0 0 50000 N
> dsi_ipg_div 0 0 0
> 12500000
> 0 0 50000 Y
> dsi_esc 0 0 0
> 25000000
> 0 0 50000 N
> dsi_dbi 0 0 0
> 25000000
> 0 0 50000 N
> dsi_phy_ref 0 0 0
> 25000000
> 0 0 50000 N
> dsi_core 0 0 0
> 25000000
> 0 0 50000 N
> clko2 0 0 0
> 25000000
> 0 0 50000 N
> clko1 0 0 0
> 25000000
> 0 0 50000 N
> wdog 1 1 0
> 25000000
> 0 0 50000 Y
> wdog3_root_clk 0 0 0
> 25000000
> 0 0 50000 N
> wdog2_root_clk 0 0 0
> 25000000
> 0 0 50000 N
> wdog1_root_clk 1 1 0
> 25000000
> 0 0 50000 Y
> gpt1 0 0 0
> 25000000
> 0 0 50000 N
> gpt1_root_clk 0 0 0
> 25000000
> 0 0 50000 N
> pwm4 0 0 0
> 25000000
> 0 0 50000 N
> pwm4_root_clk 0 0 0
> 25000000
> 0 0 50000 N
> pwm3 0 0 0
> 25000000
> 0 0 50000 N
> pwm3_root_clk 0 0 0
> 25000000
> 0 0 50000 N
> pwm2 0 0 0
> 25000000
> 0 0 50000 N
> pwm2_root_clk 0 0 0
> 25000000
> 0 0 50000 N
> pwm1 0 0 0
> 25000000
> 0 0 50000 N
> pwm1_root_clk 0 0 0
> 25000000
> 0 0 50000 N
> uart4 0 0 0
> 25000000
> 0 0 50000 N
> uart4_root_clk 0 0 0
> 25000000
> 0 0 50000 N
> uart3 1 1 0
> 25000000
> 0 0 50000 Y
> uart3_root_clk 4 4 0
> 25000000
> 0 0 50000 Y
> i2c4 0 0 0
> 25000000
> 0 0 50000 N
> i2c4_root_clk 0 0 0
> 25000000
> 0 0 50000 N
> i2c3 0 1 0
> 25000000
> 0 0 50000 N
> i2c3_root_clk 0 1 0
> 25000000
> 0 0 50000 N
> i2c2 0 1 0
> 25000000
> 0 0 50000 N
> i2c2_root_clk 0 1 0
> 25000000
> 0 0 50000 N
> i2c1 0 1 0
> 25000000
> 0 0 50000 N
> i2c1_root_clk 0 1 0
> 25000000
> 0 0 50000 N
> spdif2 0 0 0
> 25000000
> 0 0 50000 N
> spdif1 0 0 0
> 25000000
> 0 0 50000 N
> sai6 0 0 0
> 25000000
> 0 0 50000 N
> sai6_root_clk 0 0 0
> 25000000
> 0 0 50000 N
> sai5 0 0 0
> 25000000
> 0 0 50000 N
> sai5_root_clk 0 0 0
> 25000000
> 0 0 50000 N
> sai4 0 0 0
> 25000000
> 0 0 50000 N
> sai4_root_clk 0 0 0
> 25000000
> 0 0 50000 N
> sai2 0 0 0
> 25000000
> 0 0 50000 N
> sai2_root_clk 0 0 0
> 25000000
> 0 0 50000 N
> sai1 0 0 0
> 25000000
> 0 0 50000 N
> sai1_root_clk 0 0 0
> 25000000
> 0 0 50000 N
> lcdif_pixel 0 0 0
> 25000000
> 0 0 50000 N
> disp_dc8000 0 0 0
> 25000000
> 0 0 50000 N
> disp_root_clk 0 0 0
> 25000000
> 0 0 50000 N
> disp_dtrc 0 0 0
> 25000000
> 0 0 50000 N
> noc_apb 1 1 0
> 25000000
> 0 0 50000 Y
> video2_pll1_ref_sel 0 0 0
> 25000000
> 0 0 50000 Y
> video2_pll_out 0 0 0
> 25000000
> 0 0 50000 Y
> video_pll2_out_monitor 0 0 0
> 25000000
> 0 0 50000 Y
> dram_pll1_ref_sel 1 1 0
> 25000000
> 0 0 50000 Y
> dram_pll_out 2 2 0
> 800000000
> 0 0 50000 Y
> dram_core_clk 1 1 0
> 800000000
> 0 0 50000 Y
> dram_pll_out_monitor 0 0 0
> 800000000
> 0 0 50000 Y
> sys3_pll1_ref_sel 1 1 0
> 25000000
> 0 0 50000 Y
> sys3_pll_out 1 1 0
> 25000000
> 0 0 50000 Y
> sys_pll3_out_monitor 0 0 0
> 25000000
> 0 0 50000 Y
> video_pll1_ref_sel 0 0 0
> 25000000
> 0 0 50000 Y
> video_pll1_bypass 0 0 0
> 25000000
> 0 0 50000 Y
> video_pll1_out_monitor 0 0 0
> 25000000
> 0 0 50000 Y
> video_pll1_out 0 0 0
> 25000000
> 0 0 50000 N
> dc_pixel 0 0 0
> 5000000
> 0 0 50000 N
> video_pll1_ref_div 0 0 0
> 5000000
> 0 0 50000 Y
> video_pll1 0 0 0
> 650000000
> 0 0 50000 Y
> audio_pll2_ref_sel 0 0 0
> 25000000
> 0 0 50000 Y
> audio_pll2_ref_div 0 0 0
> 5000000
> 0 0 50000 Y
> audio_pll2 0 0 0
> 722534397
> 0 0 50000 Y
> audio_pll2_bypass 0 0 0
> 722534397
> 0 0 50000 Y
> audio_pll2_out_monitor 0 0 0
> 722534397
> 0 0 50000 Y
> audio_pll2_out 0 0 0
> 722534397
> 0 0 50000 N
> audio_pll1_ref_sel 0 0 0
> 25000000
> 0 0 50000 Y
> audio_pll1_ref_div 0 0 0
> 5000000
> 0 0 50000 Y
> audio_pll1 0 0 0
> 786431998
> 0 0 50000 Y
> audio_pll1_bypass 0 0 0
> 786431998
> 0 0 50000 Y
> audio_pll1_out_monitor 0 0 0
> 786431998
> 0 0 50000 Y
> audio_pll1_out 0 0 0
> 786431998
> 0 0 50000 N
> sai3 0 0 0
> 49152000
> 0 0 50000 N
> sai3_root_clk 0 0 0
> 49152000
> 0 0 50000 N
> pll 0 0 0
> 196608000
> 0 0 50000 Y
> codec_clkin 0 0
> 0 196608000
> 0 0 50000 Y
> nadc 0 0 0
> 196608000
> 0 0 50000 Y
> madc 0 0
> 0 196608000
> 0 0 50000 Y
> ndac 0 0 0
> 196608000
> 0 0 50000 Y
> mdac 0 0
> 0 196608000
> 0 0 50000 Y
> bdiv 0 0
> 0
> 196608000 0 0 50000 Y
> vpu_pll_ref_sel 0 1 0
> 25000000
> 0 0 50000 Y
> vpu_pll_ref_div 0 1 0
> 5000000
> 0 0 50000 Y
> vpu_pll 0 1 0
> 600000000
> 0 0 50000 Y
> vpu_pll_bypass 0 1 0
> 600000000
> 0 0 50000 Y
> vpu_pll_out_monitor 0 0 0
> 600000000
> 0 0 50000 Y
> vpu_pll_out 0 2 0
> 600000000
> 0 0 50000 N
> vpu_g2 0 1 0
> 600000000
> 0 0 50000 N
> vpu_g2_root_clk 0 1 0
> 600000000
> 0 0 50000 N
> vpu_g1 0 1 0
> 600000000
> 0 0 50000 N
> vpu_g1_root_clk 0 1 0
> 600000000
> 0 0 50000 N
> gpu_pll_ref_sel 0 0 0
> 25000000
> 0 0 50000 Y
> gpu_pll_ref_div 0 0 0
> 5000000
> 0 0 50000 Y
> gpu_pll 0 0 0
> 800000000
> 0 0 50000 Y
> gpu_pll_bypass 0 0 0
> 800000000
> 0 0 50000 Y
> gpu_pll_out_monitor 0 0 0
> 800000000
> 0 0 50000 Y
> pllout_monitor_sel 0 0 0
> 800000000
> 0 0 50000 Y
> pllout_monitor_clk2 0 0
> 0
> 800000000 0 0 50000 N
> gpu_pll_out 0 0 0
> 800000000
> 0 0 50000 N
> gpu_ahb 0 0 0
> 800000000
> 0 0 50000 N
> gpu_axi 0 0 0
> 800000000
> 0 0 50000 N
> gpu_shader 0 0 0
> 800000000
> 0 0 50000 N
> gpu_core 0 0 0
> 800000000
> 0 0 50000 N
> gpu_root_clk 0 0 0
> 800000000
> 0 0 50000 N
> arm_pll_ref_sel 1 1 0
> 25000000
> 0 0 50000 Y
> arm_pll_ref_div 1 1 0
> 5000000
> 0 0 50000 Y
> arm_pll 1 1 0
> 800000000
> 0 0 50000 Y
> arm_pll_bypass 1 1 0
> 800000000
> 0 0 50000 Y
> arm_pll_out_monitor 0 0 0
> 800000000
> 0 0 50000 Y
> arm_pll_out 1 1 0
> 800000000
> 0 0 50000 Y
> arm_a53_core 1 1 0
> 800000000
> 0 0 50000 Y
> arm 1 1 0
> 800000000
> 0 0 50000 Y
> vpu_core 0 0 0
> 800000000
> 0 0 50000 N
> ckil 2 2 0
> 32768
> 0 0 50000 Y
>
I will try to duplicate the same clocks status as your on imx8mq evk. Any result will let you know in different mail.
Thanks
Sandor
> Thanks and best regards
> Alexander
>
>
@@ -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
new file mode 100644
@@ -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;
+ 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++) {
+ 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);
+
+ 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);
+
+ 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);
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ /*
+ * 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);
+ 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@nxp.com>");
+MODULE_DESCRIPTION("Cadence HDMI transmitter driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:cdns-hdmi");