[v2 06/10] drm: bridge: cadence: Add MHDP HDMI driver for i.MX8MQ
Alexander Stein
alexander.stein at ew.tq-group.com
Tue Nov 8 05:16:50 PST 2022
Hello,
thanks for working on this and the updated version.
Am Freitag, 4. November 2022, 07:44:56 CET schrieb Sandor Yu:
> Add a new DRM HDMI bridge driver for Candence MHDP used in i.MX8MQ
> SOC. MHDP IP could support HDMI or DisplayPort standards according
> embedded Firmware running in the uCPU.
>
> For iMX8MQ SOC, the HDMI FW was loaded and activated by SOC ROM code.
> Bootload binary included HDMI FW was required for the driver.
>
> Signed-off-by: Sandor Yu <Sandor.yu at nxp.com>
> ---
> drivers/gpu/drm/bridge/cadence/Kconfig | 12 +
> .../gpu/drm/bridge/cadence/cdns-hdmi-core.c | 1038 +++++++++++++++++
> 2 files changed, 1050 insertions(+)
> create mode 100644 drivers/gpu/drm/bridge/cadence/cdns-hdmi-core.c
>
> diff --git a/drivers/gpu/drm/bridge/cadence/Kconfig
> b/drivers/gpu/drm/bridge/cadence/Kconfig index e79ae1af3765..377452d09992
> 100644
> --- a/drivers/gpu/drm/bridge/cadence/Kconfig
> +++ b/drivers/gpu/drm/bridge/cadence/Kconfig
> @@ -26,6 +26,18 @@ config DRM_CDNS_MHDP8546_J721E
> clock and data muxes.
> endif
>
> +config DRM_CDNS_HDMI
> + tristate "Cadence HDMI DRM driver"
> + select DRM_KMS_HELPER
> + select DRM_PANEL_BRIDGE
> + select DRM_DISPLAY_HELPER
> + select DRM_CDNS_AUDIO
> + depends on OF
> + help
> + Support Cadence MHDP HDMI driver.
> + Cadence MHDP Controller support one or more protocols,
> + HDMI firmware is required for this driver.
> +
> config DRM_CDNS_DP
> tristate "Cadence DP DRM driver"
> select DRM_KMS_HELPER
> diff --git a/drivers/gpu/drm/bridge/cadence/cdns-hdmi-core.c
> b/drivers/gpu/drm/bridge/cadence/cdns-hdmi-core.c new file mode 100644
> index 000000000000..b392aac015bd
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/cadence/cdns-hdmi-core.c
> @@ -0,0 +1,1038 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Cadence High-Definition Multimedia Interface (HDMI) driver
> + *
> + * Copyright (C) 2019-2022 NXP Semiconductor, Inc.
> + *
> + */
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/hdmi.h>
> +#include <linux/irq.h>
> +#include <linux/module.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/mutex.h>
> +#include <linux/of_device.h>
> +#include <linux/phy/phy.h>
> +#include <linux/phy/phy-hdmi.h>
> +
> +#include <drm/bridge/cdns-mhdp-mailbox.h>
> +#include <drm/display/drm_hdmi_helper.h>
> +#include <drm/display/drm_scdc_helper.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_edid.h>
> +#include <drm/drm_encoder_slave.h>
> +#include <drm/drm_of.h>
> +#include <drm/drm_probe_helper.h>
> +#include <drm/drm_print.h>
> +#include <drm/drm_vblank.h>
> +
> +#include "cdns-mhdp-common.h"
> +
> +void cdns_mhdp_infoframe_set(struct cdns_mhdp_device *mhdp,
> + u8 entry_id, u8 packet_len,
u8 *packet, u8 packet_type)
> +{
> + u32 *packet32, len32;
> + u32 val, i;
> +
> + /* invalidate entry */
> + val = F_ACTIVE_IDLE_TYPE(1) | F_PKT_ALLOC_ADDRESS(entry_id);
> + writel(val, mhdp->regs + SOURCE_PIF_PKT_ALLOC_REG);
> + writel(F_PKT_ALLOC_WR_EN(1), mhdp->regs +
SOURCE_PIF_PKT_ALLOC_WR_EN);
> +
> + /* flush fifo 1 */
> + writel(F_FIFO1_FLUSH(1), mhdp->regs + SOURCE_PIF_FIFO1_FLUSH);
> +
> + /* write packet into memory */
> + packet32 = (u32 *)packet;
This only works on little-endian machines, no?
> + len32 = packet_len / 4;
> + for (i = 0; i < len32; i++)
> + writel(F_DATA_WR(packet32[i]), mhdp->regs +
SOURCE_PIF_DATA_WR);
> +
> + /* write entry id */
> + writel(F_WR_ADDR(entry_id), mhdp->regs + SOURCE_PIF_WR_ADDR);
> +
> + /* write request */
> + writel(F_HOST_WR(1), mhdp->regs + SOURCE_PIF_WR_REQ);
> +
> + /* update entry */
> + val = F_ACTIVE_IDLE_TYPE(1) | F_TYPE_VALID(1) |
> + F_PACKET_TYPE(packet_type) |
F_PKT_ALLOC_ADDRESS(entry_id);
> + writel(val, mhdp->regs + SOURCE_PIF_PKT_ALLOC_REG);
> +
> + writel(F_PKT_ALLOC_WR_EN(1), mhdp->regs +
SOURCE_PIF_PKT_ALLOC_WR_EN);
> +}
> +
> +static int cdns_hdmi_get_edid_block(void *data, u8 *edid,
> + u32 block, size_t length)
> +{
> + struct cdns_mhdp_device *mhdp = data;
> + u8 msg[2], reg[5], i;
> + int ret;
> +
> + mutex_lock(&mhdp->mbox_mutex);
> +
> + for (i = 0; i < 4; i++) {
What is i? It is not used inside the loop.
> + msg[0] = block / 2;
> + msg[1] = block % 2;
> +
> + ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_HDMI_TX,
HDMI_TX_EDID,
> + sizeof(msg), msg);
> + if (ret)
> + continue;
> +
> + ret = cdns_mhdp_mailbox_recv_header(mhdp,
MB_MODULE_ID_HDMI_TX,
> +
HDMI_TX_EDID, sizeof(reg) + length);
> + if (ret)
> + continue;
> +
> + ret = cdns_mhdp_mailbox_recv_data(mhdp, reg, sizeof(reg));
> + if (ret)
> + continue;
> +
> + ret = cdns_mhdp_mailbox_recv_data(mhdp, edid, length);
> + if (ret)
> + continue;
> +
> + if ((reg[3] << 8 | reg[4]) == length)
> + break;
> + }
> +
> + mutex_unlock(&mhdp->mbox_mutex);
> +
> + if (ret)
> + DRM_ERROR("get block[%d] edid failed: %d\n", block, ret);
> + return ret;
> +}
> +
> +int cdns_hdmi_scdc_read(struct cdns_mhdp_device *mhdp, u8 addr, u8 *data)
> +{
> + u8 msg[4], reg[6];
> + int ret;
> +
> + msg[0] = 0x54;
> + msg[1] = addr;
> + msg[2] = 0;
> + msg[3] = 1;
> +
> + mutex_lock(&mhdp->mbox_mutex);
> +
> + ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_HDMI_TX,
HDMI_TX_READ,
> + sizeof(msg), msg);
> + if (ret)
> + goto err_scdc_read;
> +
> + ret = cdns_mhdp_mailbox_recv_header(mhdp, MB_MODULE_ID_HDMI_TX,
> + HDMI_TX_READ,
sizeof(reg));
> + if (ret)
> + goto err_scdc_read;
> +
> + ret = cdns_mhdp_mailbox_recv_data(mhdp, reg, sizeof(reg));
> + if (ret)
> + goto err_scdc_read;
> +
> + *data = reg[5];
> +
> +err_scdc_read:
> +
> + mutex_unlock(&mhdp->mbox_mutex);
> +
> + if (ret)
> + DRM_ERROR("scdc read failed: %d\n", ret);
> + return ret;
> +}
> +
> +int cdns_hdmi_scdc_write(struct cdns_mhdp_device *mhdp, u8 addr, u8 value)
> +{
> + u8 msg[5], reg[5];
> + int ret;
> +
> + msg[0] = 0x54;
> + msg[1] = addr;
> + msg[2] = 0;
> + msg[3] = 1;
> + msg[4] = value;
> +
> + mutex_lock(&mhdp->mbox_mutex);
> +
> + ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_HDMI_TX,
HDMI_TX_WRITE,
> + sizeof(msg), msg);
> + if (ret)
> + goto err_scdc_write;
> +
> + ret = cdns_mhdp_mailbox_recv_header(mhdp, MB_MODULE_ID_HDMI_TX,
> + HDMI_TX_WRITE,
sizeof(reg));
> + if (ret)
> + goto err_scdc_write;
> +
> + ret = cdns_mhdp_mailbox_recv_data(mhdp, reg, sizeof(reg));
> + if (ret)
> + goto err_scdc_write;
> +
> + if (reg[0] != 0)
> + ret = -EINVAL;
> +
> +err_scdc_write:
> +
> + mutex_unlock(&mhdp->mbox_mutex);
> +
> + if (ret)
> + DRM_ERROR("scdc write failed: %d\n", ret);
> + return ret;
> +}
> +
> +int cdns_hdmi_ctrl_init(struct cdns_mhdp_device *mhdp,
> + int protocol, u32 char_rate)
> +{
> + u32 reg0;
> + u32 reg1;
> + u32 val;
> + int ret;
> +
> + /* Set PHY to HDMI data */
> + ret = cdns_mhdp_reg_write(mhdp, PHY_DATA_SEL,
F_SOURCE_PHY_MHDP_SEL(1));
> + if (ret < 0)
> + return ret;
> +
> + ret = cdns_mhdp_reg_write(mhdp, HDTX_HPD,
> + F_HPD_VALID_WIDTH(4) |
F_HPD_GLITCH_WIDTH(0));
> + if (ret < 0)
> + return ret;
> +
> + /* open CARS */
> + ret = cdns_mhdp_reg_write(mhdp, SOURCE_PHY_CAR, 0xF);
> + if (ret < 0)
> + return ret;
> + ret = cdns_mhdp_reg_write(mhdp, SOURCE_HDTX_CAR, 0xFF);
> + if (ret < 0)
> + return ret;
> + ret = cdns_mhdp_reg_write(mhdp, SOURCE_PKT_CAR, 0xF);
> + if (ret < 0)
> + return ret;
> + ret = cdns_mhdp_reg_write(mhdp, SOURCE_AIF_CAR, 0xF);
> + if (ret < 0)
> + return ret;
> + ret = cdns_mhdp_reg_write(mhdp, SOURCE_CIPHER_CAR, 0xF);
> + if (ret < 0)
> + return ret;
> + ret = cdns_mhdp_reg_write(mhdp, SOURCE_CRYPTO_CAR, 0xF);
> + if (ret < 0)
> + return ret;
> + ret = cdns_mhdp_reg_write(mhdp, SOURCE_CEC_CAR, 3);
> + if (ret < 0)
> + return ret;
> +
> + reg0 = reg1 = 0x7c1f;
> + if (protocol == MODE_HDMI_2_0 && char_rate >= 340000) {
> + reg0 = 0;
> + reg1 = 0xFFFFF;
> + }
> + ret = cdns_mhdp_reg_write(mhdp, HDTX_CLOCK_REG_0, reg0);
> + if (ret < 0)
> + return ret;
> + ret = cdns_mhdp_reg_write(mhdp, HDTX_CLOCK_REG_1, reg1);
> + if (ret < 0)
> + return ret;
> +
> + /* set hdmi mode and preemble mode data enable */
> + val = F_HDMI_MODE(protocol) | F_HDMI2_PREAMBLE_EN(1) | F_DATA_EN(1)
|
> + F_HDMI2_CTRL_IL_MODE(1) | F_BCH_EN(1) |
F_PIC_3D(0XF);
> + ret = cdns_mhdp_reg_write(mhdp, HDTX_CONTROLLER, val);
> +
> + return ret;
> +}
> +
> +int cdns_hdmi_mode_config(struct cdns_mhdp_device *mhdp,
> + struct
drm_display_mode *mode,
> + struct
video_info *video_info)
> +{
> + int ret;
> + u32 val;
> + u32 vsync_lines = mode->vsync_end - mode->vsync_start;
> + u32 eof_lines = mode->vsync_start - mode->vdisplay;
> + u32 sof_lines = mode->vtotal - mode->vsync_end;
> + u32 hblank = mode->htotal - mode->hdisplay;
> + u32 hactive = mode->hdisplay;
> + u32 vblank = mode->vtotal - mode->vdisplay;
> + u32 vactive = mode->vdisplay;
> + u32 hfront = mode->hsync_start - mode->hdisplay;
> + u32 hback = mode->htotal - mode->hsync_end;
> + u32 vfront = eof_lines;
> + u32 hsync = hblank - hfront - hback;
> + u32 vsync = vsync_lines;
> + u32 vback = sof_lines;
> + u32 v_h_polarity = ((mode->flags & DRM_MODE_FLAG_NHSYNC) ? 0 : 1) +
> + ((mode->flags &
DRM_MODE_FLAG_NVSYNC) ? 0 : 2);
Please fix the alignment.
> +
> + ret = cdns_mhdp_reg_write(mhdp, SCHEDULER_H_SIZE, (hactive << 16) +
> hblank); + if (ret < 0)
> + return ret;
> +
> + ret = cdns_mhdp_reg_write(mhdp, SCHEDULER_V_SIZE, (vactive << 16) +
> vblank); + if (ret < 0)
> + return ret;
> +
> + ret = cdns_mhdp_reg_write(mhdp, HDTX_SIGNAL_FRONT_WIDTH, (vfront <<
16) +
> hfront); + if (ret < 0)
> + return ret;
> +
> + ret = cdns_mhdp_reg_write(mhdp, HDTX_SIGNAL_SYNC_WIDTH, (vsync <<
16) +
> hsync); + if (ret < 0)
> + return ret;
> +
> + ret = cdns_mhdp_reg_write(mhdp, HDTX_SIGNAL_BACK_WIDTH, (vback <<
16) +
> hback); + if (ret < 0)
> + return ret;
> +
> + ret = cdns_mhdp_reg_write(mhdp, HSYNC2VSYNC_POL_CTRL, v_h_polarity);
> + if (ret < 0)
> + return ret;
> +
> + /* Reset Data Enable */
> + cdns_mhdp_reg_read(mhdp, HDTX_CONTROLLER, &val);
> + val &= ~F_DATA_EN(1);
> + ret = cdns_mhdp_reg_write(mhdp, HDTX_CONTROLLER, val);
> + if (ret < 0)
> + return ret;
> +
> + /* Set bpc */
> + val &= ~F_VIF_DATA_WIDTH(3);
> + switch (video_info->bpc) {
> + case 10:
> + val |= F_VIF_DATA_WIDTH(1);
> + break;
> + case 12:
> + val |= F_VIF_DATA_WIDTH(2);
> + break;
> + case 16:
> + val |= F_VIF_DATA_WIDTH(3);
> + break;
> + case 8:
> + default:
> + val |= F_VIF_DATA_WIDTH(0);
> + break;
> + }
> +
> + /* select color encoding */
> + val &= ~F_HDMI_ENCODING(3);
> + switch (video_info->color_fmt) {
> + case DRM_COLOR_FORMAT_YCBCR444:
> + val |= F_HDMI_ENCODING(2);
> + break;
> + case DRM_COLOR_FORMAT_YCBCR422:
> + val |= F_HDMI_ENCODING(1);
> + break;
> + case DRM_COLOR_FORMAT_YCBCR420:
> + val |= F_HDMI_ENCODING(3);
> + break;
> + case DRM_COLOR_FORMAT_RGB444:
> + default:
> + val |= F_HDMI_ENCODING(0);
> + break;
> + }
> +
> + ret = cdns_mhdp_reg_write(mhdp, HDTX_CONTROLLER, val);
> + if (ret < 0)
> + return ret;
> +
> + /* set data enable */
> + val |= F_DATA_EN(1);
> + ret = cdns_mhdp_reg_write(mhdp, HDTX_CONTROLLER, val);
> +
> + return ret;
> +}
> +
> +int cdns_hdmi_disable_gcp(struct cdns_mhdp_device *mhdp)
> +{
> + u32 val;
> +
> + cdns_mhdp_reg_read(mhdp, HDTX_CONTROLLER, &val);
> + val &= ~F_GCP_EN(1);
> +
> + return cdns_mhdp_reg_write(mhdp, HDTX_CONTROLLER, val);
> +}
> +
> +int cdns_hdmi_enable_gcp(struct cdns_mhdp_device *mhdp)
> +{
> + u32 val;
> +
> + cdns_mhdp_reg_read(mhdp, HDTX_CONTROLLER, &val);
> + val |= F_GCP_EN(1);
> +
> + return cdns_mhdp_reg_write(mhdp, HDTX_CONTROLLER, val);
> +}
> +
> +static void hdmi_sink_config(struct cdns_mhdp_device *mhdp)
> +{
> + struct drm_scdc *scdc = &mhdp->curr_conn->display_info.hdmi.scdc;
> + u8 buff = 0;
> +
> + /* Default work in HDMI1.4 */
> + mhdp->hdmi.hdmi_type = MODE_HDMI_1_4;
> +
> + /* check sink support SCDC or not */
> + if (scdc->supported != true) {
> + DRM_INFO("Sink Not Support SCDC\n");
> + return;
> + }
> +
> + if (mhdp->hdmi.char_rate > 340000) {
> + /*
> + * TMDS Character Rate above 340MHz should working in
HDMI2.0
> + * Enable scrambling and TMDS_Bit_Clock_Ratio
> + */
> + buff = SCDC_TMDS_BIT_CLOCK_RATIO_BY_40 |
SCDC_SCRAMBLING_ENABLE;
> + mhdp->hdmi.hdmi_type = MODE_HDMI_2_0;
> + } else if (scdc->scrambling.low_rates) {
> + /*
> + * Enable scrambling and HDMI2.0 when scrambling
capability of sink
> + * be indicated in the HF-VSDB LTE_340Mcsc_scramble bit
> + */
> + buff = SCDC_SCRAMBLING_ENABLE;
> + mhdp->hdmi.hdmi_type = MODE_HDMI_2_0;
> + }
> +
> + /* TMDS config */
> + cdns_hdmi_scdc_write(mhdp, SCDC_TMDS_CONFIG, buff);
> +}
> +
> +static void hdmi_lanes_config(struct cdns_mhdp_device *mhdp)
> +{
> + u32 lane_mapping = mhdp->plat_data->lane_mapping;
> + /* Line swapping */
> + cdns_mhdp_reg_write(mhdp, LANES_CONFIG, 0x00400000 | lane_mapping);
> +}
> +
> +int cdns_mhdp_hdmi_read_hpd(struct cdns_mhdp_device *mhdp)
> +{
> + u8 status;
> + int ret;
> +
> + mutex_lock(&mhdp->mbox_mutex);
> +
> + ret = cdns_mhdp_mailbox_send(mhdp, MB_MODULE_ID_HDMI_TX,
> HDMI_TX_HPD_STATUS, + 0, NULL);
> + if (ret)
> + goto err_get_hpd;
> +
> + ret = cdns_mhdp_mailbox_recv_header(mhdp, MB_MODULE_ID_HDMI_TX,
> +
HDMI_TX_HPD_STATUS, sizeof(status));
> + if (ret)
> + goto err_get_hpd;
> +
> + ret = cdns_mhdp_mailbox_recv_data(mhdp, &status, sizeof(status));
> + if (ret)
> + goto err_get_hpd;
> +
> + mutex_unlock(&mhdp->mbox_mutex);
> + return status;
> +
> +err_get_hpd:
> + mutex_unlock(&mhdp->mbox_mutex);
> + DRM_ERROR("read hpd failed: %d\n", ret);
> + return ret;
> +}
> +
> +static int hdmi_avi_info_set(struct cdns_mhdp_device *mhdp,
> + struct drm_display_mode *mode)
> +{
> + struct hdmi_avi_infoframe frame;
> + int format = mhdp->video_info.color_fmt;
> + struct drm_connector_state *conn_state = mhdp->curr_conn->state;
> + struct drm_display_mode *adj_mode;
> + enum hdmi_quantization_range qr;
> + u8 buf[32];
> + int ret;
> +
> + /* Initialise info frame from DRM mode */
> + drm_hdmi_avi_infoframe_from_display_mode(&frame,
> + mhdp->curr_conn,
mode);
> +
> + switch (format) {
> + case DRM_COLOR_FORMAT_YCBCR444:
> + frame.colorspace = HDMI_COLORSPACE_YUV444;
> + break;
> + case DRM_COLOR_FORMAT_YCBCR422:
> + frame.colorspace = HDMI_COLORSPACE_YUV422;
> + break;
> + case DRM_COLOR_FORMAT_YCBCR420:
> + frame.colorspace = HDMI_COLORSPACE_YUV420;
> + break;
> + default:
> + frame.colorspace = HDMI_COLORSPACE_RGB;
> + break;
> + }
> +
> + drm_hdmi_avi_infoframe_colorimetry(&frame, conn_state);
> +
> + adj_mode = &mhdp->bridge.encoder->crtc->state->adjusted_mode;
> +
> + qr = drm_default_rgb_quant_range(adj_mode);
> +
> + drm_hdmi_avi_infoframe_quant_range(&frame, mhdp->curr_conn,
> + adj_mode, qr);
> +
> + ret = hdmi_avi_infoframe_check(&frame);
> + if (WARN_ON(ret))
> + return -EINVAL;
> +
> + ret = hdmi_avi_infoframe_pack(&frame, buf + 1, sizeof(buf) - 1);
> + if (ret < 0) {
> + DRM_ERROR("failed to pack AVI infoframe: %d\n", ret);
> + return -1;
> + }
> +
> + buf[0] = 0;
> + cdns_mhdp_infoframe_set(mhdp, 0, sizeof(buf), buf,
> HDMI_INFOFRAME_TYPE_AVI); +
> + return 0;
> +}
> +
> +static void hdmi_vendor_info_set(struct cdns_mhdp_device *mhdp,
> + struct drm_display_mode *mode)
> +{
> + struct hdmi_vendor_infoframe frame;
> + u8 buf[32];
> + int ret;
> +
> + /* Initialise vendor frame from DRM mode */
> + ret = drm_hdmi_vendor_infoframe_from_display_mode(&frame, mhdp-
>curr_conn,
> mode); + if (ret < 0) {
> + DRM_INFO("No vendor infoframe\n");
> + return;
> + }
> +
> + ret = hdmi_vendor_infoframe_pack(&frame, buf + 1, sizeof(buf) - 1);
> + if (ret < 0) {
> + DRM_WARN("Unable to pack vendor infoframe: %d\n", ret);
> + return;
> + }
> +
> + buf[0] = 0;
> + cdns_mhdp_infoframe_set(mhdp, 3, sizeof(buf), buf,
> HDMI_INFOFRAME_TYPE_VENDOR); +}
> +
> +static void hdmi_drm_info_set(struct cdns_mhdp_device *mhdp)
> +{
> + struct drm_connector_state *conn_state;
> + struct hdmi_drm_infoframe frame;
> + u8 buf[32];
> + int ret;
> +
> + conn_state = mhdp->curr_conn->state;
> +
> + if (!conn_state->hdr_output_metadata)
> + return;
> +
> + ret = drm_hdmi_infoframe_set_hdr_metadata(&frame, conn_state);
> + if (ret < 0) {
> + DRM_DEBUG_KMS("couldn't set HDR metadata in infoframe\n");
> + return;
> + }
> +
> + ret = hdmi_drm_infoframe_pack(&frame, buf + 1, sizeof(buf) - 1);
> + if (ret < 0) {
> + DRM_DEBUG_KMS("couldn't pack HDR infoframe\n");
> + return;
> + }
> +
> + buf[0] = 0;
> + cdns_mhdp_infoframe_set(mhdp, 3, sizeof(buf), buf,
> HDMI_INFOFRAME_TYPE_DRM); +}
> +
> +static int hdmi_phy_colorspace(int color_fmt)
> +{
> + int color_space;
> +
> + switch (color_fmt) {
> + case DRM_COLOR_FORMAT_YCBCR444:
> + color_space = HDMI_PHY_COLORSPACE_YUV444;
> + break;
> + case DRM_COLOR_FORMAT_YCBCR422:
> + color_space = HDMI_PHY_COLORSPACE_YUV422;
> + break;
> + case DRM_COLOR_FORMAT_YCBCR420:
> + color_space = HDMI_PHY_COLORSPACE_YUV420;
> + break;
> + case DRM_COLOR_FORMAT_RGB444:
> + default:
> + color_space = HDMI_PHY_COLORSPACE_RGB;
> + break;
> + }
> +
> + return color_space;
> +}
> +
> +void cdns_hdmi_mode_set(struct cdns_mhdp_device *mhdp)
> +{
> + struct drm_display_mode *mode = &mhdp->mode;
> + union phy_configure_opts phy_cfg;
> + int ret;
> +
> + /* video mode check */
> + if (mode->clock == 0 || mode->hdisplay == 0 || mode->vdisplay == 0)
> + return;
> +
> + hdmi_lanes_config(mhdp);
> +
> + phy_cfg.hdmi.pixel_clk_rate = mode->clock;
> + phy_cfg.hdmi.bpc = mhdp->video_info.bpc;
> + phy_cfg.hdmi.color_space =
> hdmi_phy_colorspace(mhdp->video_info.color_fmt); + ret =
> phy_configure(mhdp->phy, &phy_cfg);
> + if (ret) {
> + dev_err(mhdp->dev, "%s: phy_configure() failed: %d\n",
> + __func__, ret);
> + return;
> + }
> +
> + hdmi_sink_config(mhdp);
> +
> + ret = cdns_hdmi_ctrl_init(mhdp, mhdp->hdmi.hdmi_type,
> mhdp->hdmi.char_rate); + if (ret < 0) {
> + DRM_ERROR("%s, ret = %d\n", __func__, ret);
> + return;
> + }
> +
> + /* Config GCP */
> + if (mhdp->video_info.bpc == 8)
> + cdns_hdmi_disable_gcp(mhdp);
> + else
> + cdns_hdmi_enable_gcp(mhdp);
> +
> + ret = hdmi_avi_info_set(mhdp, mode);
> + if (ret < 0) {
> + DRM_ERROR("%s ret = %d\n", __func__, ret);
> + return;
> + }
> +
> + /* vendor info frame is enabled only for HDMI1.4 4K mode */
> + hdmi_vendor_info_set(mhdp, mode);
> +
> + hdmi_drm_info_set(mhdp);
> +
> + ret = cdns_hdmi_mode_config(mhdp, mode, &mhdp->video_info);
> + if (ret < 0) {
> + DRM_ERROR("CDN_API_HDMITX_SetVic_blocking ret = %d\n",
ret);
> + return;
> + }
> +}
> +static enum drm_connector_status
> +cdns_hdmi_detect(struct cdns_mhdp_device *mhdp)
> +{
> + u8 hpd = 0xf;
> +
> + hpd = cdns_mhdp_hdmi_read_hpd(mhdp);
> + if (hpd == 1)
> + return connector_status_connected;
> + else if (hpd == 0)
> + return connector_status_disconnected;
> +
> + DRM_INFO("Unknown cable status, hdp=%u\n", hpd);
> + return connector_status_unknown;
> +}
> +
> +static enum drm_connector_status
> +cdns_hdmi_bridge_detect(struct drm_bridge *bridge)
> +{
> + struct cdns_mhdp_device *mhdp = bridge->driver_private;
> +
> + return cdns_hdmi_detect(mhdp);
> +}
> +
> +static int cdns_hdmi_connector_get_modes(struct drm_connector *connector)
> +{
> + struct cdns_mhdp_device *mhdp =
> + container_of(connector, struct
cdns_mhdp_device, connector);
> + int num_modes = 0;
> + struct edid *edid;
> +
> + edid = drm_do_get_edid(connector,
> + cdns_hdmi_get_edid_block, mhdp);
> + if (edid) {
> + dev_info(mhdp->dev, "%x,%x,%x,%x,%x,%x,%x,%x\n",
> + edid->header[0], edid->header[1],
> + edid->header[2], edid->header[3],
> + edid->header[4], edid->header[5],
> + edid->header[6], edid->header[7]);
> + drm_connector_update_edid_property(connector, edid);
> + num_modes = drm_add_edid_modes(connector, edid);
> + kfree(edid);
> + }
> +
> + if (num_modes == 0)
> + DRM_ERROR("Invalid edid\n");
> + return num_modes;
> +}
> +
> +static bool blob_equal(const struct drm_property_blob *a,
> + const struct drm_property_blob *b)
> +{
> + if (a && b)
> + return a->length == b->length &&
> + !memcmp(a->data, b->data, a->length);
> +
> + return !a == !b;
> +}
> +
> +static int cdns_hdmi_connector_atomic_check(struct drm_connector
> *connector, + struct
drm_atomic_state *state)
> +{
> + struct drm_connector_state *new_con_state =
> + drm_atomic_get_new_connector_state(state, connector);
> + struct drm_connector_state *old_con_state =
> + drm_atomic_get_old_connector_state(state, connector);
> + struct drm_crtc *crtc = new_con_state->crtc;
> + struct drm_crtc_state *new_crtc_state;
> +
> + if (!blob_equal(new_con_state->hdr_output_metadata,
> + old_con_state->hdr_output_metadata) ||
> + new_con_state->colorspace != old_con_state->colorspace) {
> + new_crtc_state = drm_atomic_get_crtc_state(state, crtc);
> + if (IS_ERR(new_crtc_state))
> + return PTR_ERR(new_crtc_state);
> +
> + new_crtc_state->mode_changed =
> + !new_con_state->hdr_output_metadata ||
> + !old_con_state->hdr_output_metadata ||
> + new_con_state->colorspace != old_con_state-
>colorspace;
> + }
> +
> + return 0;
> +}
> +
> +static const struct drm_connector_funcs cdns_hdmi_connector_funcs = {
> + .fill_modes = drm_helper_probe_single_connector_modes,
> + .destroy = drm_connector_cleanup,
> + .reset = drm_atomic_helper_connector_reset,
> + .atomic_duplicate_state =
drm_atomic_helper_connector_duplicate_state,
> + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> +};
> +
> +static const struct drm_connector_helper_funcs
> cdns_hdmi_connector_helper_funcs = { + .get_modes =
> cdns_hdmi_connector_get_modes,
> + .atomic_check = cdns_hdmi_connector_atomic_check,
> +};
> +
> +static int cdns_hdmi_bridge_attach(struct drm_bridge *bridge,
> + enum drm_bridge_attach_flags flags)
> +{
> + struct cdns_mhdp_device *mhdp = bridge->driver_private;
> + struct drm_mode_config *config = &bridge->dev->mode_config;
> + struct drm_encoder *encoder = bridge->encoder;
> + struct drm_connector *connector = &mhdp->connector;
> +
> + if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) {
> + connector->interlace_allowed = 0;
> + connector->polled = DRM_CONNECTOR_POLL_HPD;
> +
> + drm_connector_helper_add(connector,
&cdns_hdmi_connector_helper_funcs);
> +
> + drm_connector_init(bridge->dev, connector,
&cdns_hdmi_connector_funcs,
> + DRM_MODE_CONNECTOR_HDMIA);
> +
> + drm_object_attach_property(&connector->base,
> + config-
>hdr_output_metadata_property, 0);
> +
> + if (!drm_mode_create_hdmi_colorspace_property(connector))
> + drm_object_attach_property(&connector->base,
> + connector-
>colorspace_property, 0);
> +
> + drm_connector_attach_encoder(connector, encoder);
> + }
> +
> + return 0;
> +}
> +
> +static enum drm_mode_status
> +cdns_hdmi_bridge_mode_valid(struct drm_bridge *bridge,
> + const struct drm_display_info *info,
> + const struct drm_display_mode *mode)
> +{
> + struct cdns_mhdp_device *mhdp = bridge->driver_private;
> + enum drm_mode_status mode_status = MODE_OK;
> + union phy_configure_opts phy_cfg;
> + int ret;
> +
> + /* We don't support double-clocked and Interlaced modes */
> + if (mode->flags & DRM_MODE_FLAG_DBLCLK ||
> + mode->flags & DRM_MODE_FLAG_INTERLACE)
> + return MODE_BAD;
> +
> + /* MAX support pixel clock rate 594MHz */
> + if (mode->clock > 594000)
> + return MODE_CLOCK_HIGH;
> +
> + /* 4096x2160 is not supported */
> + if (mode->hdisplay > 3840 || mode->vdisplay > 2160)
> + return MODE_BAD_HVALUE;
> +
> + /* Check modes supported by PHY */
> + phy_cfg.hdmi.pixel_clk_rate = mode->clock;
> + ret = phy_validate(mhdp->phy, PHY_MODE_HDMI, 0, &phy_cfg);
> + if (ret < 0)
> + return MODE_CLOCK_RANGE;
> +
> + return mode_status;
> +}
> +
> +bool cdns_hdmi_bridge_mode_fixup(struct drm_bridge *bridge,
> + const struct drm_display_mode *mode,
> + struct drm_display_mode
*adjusted_mode)
> +{
> + struct cdns_mhdp_device *mhdp = bridge->driver_private;
> + struct video_info *video = &mhdp->video_info;
> +
> + video->bpc = 8;
> + video->color_fmt = DRM_COLOR_FORMAT_RGB444;
> +
> + return true;
> +}
> +
> +static struct edid *cdns_hdmi_bridge_get_edid(struct drm_bridge *bridge,
> + struct drm_connector
*connector)
> +{
> + struct cdns_mhdp_device *mhdp = bridge->driver_private;
> +
> + return drm_do_get_edid(connector, cdns_hdmi_get_edid_block, mhdp);
> +}
> +
> +static void cdns_hdmi_bridge_atomic_disable(struct drm_bridge *bridge,
> + struct drm_bridge_state
*old_state)
> +{
> + struct cdns_mhdp_device *mhdp = bridge->driver_private;
> +
> + mhdp->curr_conn = NULL;
> +
> + mutex_lock(&mhdp->lock);
> + phy_power_off(mhdp->phy);
> + mutex_unlock(&mhdp->lock);
> +}
> +
> +static void cdns_hdmi_bridge_atomic_enable(struct drm_bridge *bridge,
> + struct drm_bridge_state
*old_state)
> +{
> + struct cdns_mhdp_device *mhdp = bridge->driver_private;
> + struct drm_atomic_state *state = old_state->base.state;
> + struct drm_connector *connector;
> + struct video_info *video = &mhdp->video_info;
> + struct drm_crtc_state *crtc_state;
> + struct drm_connector_state *conn_state;
> + const struct drm_display_mode *mode;
> +
> + connector = drm_atomic_get_new_connector_for_encoder(state,
> +
bridge->encoder);
> + if (WARN_ON(!connector))
> + return;
> +
> + mhdp->curr_conn = connector;
> +
> + conn_state = drm_atomic_get_new_connector_state(state, connector);
> + if (WARN_ON(!conn_state))
> + return;
> +
> + crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc);
> + if (WARN_ON(!crtc_state))
> + return;
> +
> + mode = &crtc_state->adjusted_mode;
> + DRM_INFO("Mode: %dx%dp%d\n", mode->hdisplay, mode->vdisplay, mode-
>clock);
> + memcpy(&mhdp->mode, mode, sizeof(struct drm_display_mode));
> +
> + video->v_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NVSYNC);
> + video->h_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NHSYNC);
> +
> + mutex_lock(&mhdp->lock);
> + cdns_hdmi_mode_set(mhdp);
> + mutex_unlock(&mhdp->lock);
> +}
> +
> +static const struct drm_bridge_funcs cdns_hdmi_bridge_funcs = {
> + .attach = cdns_hdmi_bridge_attach,
> + .detect = cdns_hdmi_bridge_detect,
> + .get_edid = cdns_hdmi_bridge_get_edid,
> + .mode_valid = cdns_hdmi_bridge_mode_valid,
> + .mode_fixup = cdns_hdmi_bridge_mode_fixup,
> + .atomic_enable = cdns_hdmi_bridge_atomic_enable,
> + .atomic_disable = cdns_hdmi_bridge_atomic_disable,
> + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
> + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
> + .atomic_reset = drm_atomic_helper_bridge_reset,
> +};
> +
> +static void hotplug_work_func(struct work_struct *work)
> +{
> + struct cdns_mhdp_device *mhdp = container_of(work,
> + struct cdns_mhdp_device,
hotplug_work.work);
> + enum drm_connector_status status = cdns_hdmi_detect(mhdp);
> +
> + drm_bridge_hpd_notify(&mhdp->bridge, status);
> +
> + if (status == connector_status_connected) {
> + DRM_INFO("HDMI Cable Plug In\n");
> + enable_irq(mhdp->irq[IRQ_OUT]);
> + } else if (status == connector_status_disconnected) {
> + /* Cable Disconnedted */
> + DRM_INFO("HDMI Cable Plug Out\n");
> + enable_irq(mhdp->irq[IRQ_IN]);
> + }
> +}
> +
> +static irqreturn_t cdns_hdmi_irq_thread(int irq, void *data)
> +{
> + struct cdns_mhdp_device *mhdp = data;
> +
> + disable_irq_nosync(irq);
> +
> + mod_delayed_work(system_wq, &mhdp->hotplug_work,
> + msecs_to_jiffies(HOTPLUG_DEBOUNCE_MS));
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int cdns_mhdp_imx_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct cdns_mhdp_device *mhdp;
> + struct platform_device_info pdevinfo;
> + struct resource *res;
> + u32 reg;
> + int ret;
> +
> + mhdp = devm_kzalloc(dev, sizeof(*mhdp), GFP_KERNEL);
> + if (!mhdp)
> + return -ENOMEM;
> +
> + mutex_init(&mhdp->lock);
> + mutex_init(&mhdp->mbox_mutex);
> +
> + INIT_DELAYED_WORK(&mhdp->hotplug_work, hotplug_work_func);
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (!res)
> + return -ENODEV;
> + mhdp->regs = devm_ioremap(dev, res->start, resource_size(res));
> + if (IS_ERR(mhdp->regs))
> + return PTR_ERR(mhdp->regs);
Please use devm_platform_get_and_ioremap_resource.
> + mhdp->phy = devm_of_phy_get_by_index(dev, pdev->dev.of_node, 0);
> + if (IS_ERR(mhdp->phy)) {
> + dev_err(dev, "no PHY configured\n");
> + return PTR_ERR(mhdp->phy);
> + }
Please use dev_err_probe().
> + mhdp->irq[IRQ_IN] = platform_get_irq_byname(pdev, "plug_in");
> + if (mhdp->irq[IRQ_IN] < 0) {
> + dev_info(dev, "No plug_in irq number\n");
> + return -EPROBE_DEFER;
> + }
Please use dev_err_probe().
> + mhdp->irq[IRQ_OUT] = platform_get_irq_byname(pdev, "plug_out");
> + if (mhdp->irq[IRQ_OUT] < 0) {
> + dev_info(dev, "No plug_out irq number\n");
> + return -EPROBE_DEFER;
> + }
Please use dev_err_probe().
> + /*
> + * Wait for the KEEP_ALIVE "message" on the first 8 bits.
> + * Updated each sched "tick" (~2ms)
> + */
> + ret = readl_poll_timeout(mhdp->regs + KEEP_ALIVE, reg,
> + reg & CDNS_KEEP_ALIVE_MASK, 500,
> + CDNS_KEEP_ALIVE_TIMEOUT);
This freezes my board TQMa8MQ (arch/arm64/boot/dts/freescale/imx8mq-tqma8mq-
mba8mx.dts) completly if this and the PHY driver are compiled in. I have
"pd_ignore_unused clk_ignore_unused" passed to kernel command line, so I have
no idea what's wrong here.
Best regards,
Alexander
> + if (ret) {
> + dev_err(mhdp->dev,
> + "device didn't give any life sign: reg %d\n",
reg);
> + return ret;
> + }
> +
> + ret = phy_init(mhdp->phy);
> + if (ret) {
> + dev_err(mhdp->dev, "Failed to initialize PHY: %d\n", ret);
> + return -ENODEV;
> + }
> +
> + /* Enable Hotplug Detect thread */
> + irq_set_status_flags(mhdp->irq[IRQ_IN], IRQ_NOAUTOEN);
> + ret = devm_request_threaded_irq(dev, mhdp->irq[IRQ_IN],
> + NULL, cdns_hdmi_irq_thread,
> + IRQF_ONESHOT, dev_name(dev),
> + mhdp);
> + if (ret < 0) {
> + dev_err(dev, "can't claim irq %d\n",
> + mhdp-
>irq[IRQ_IN]);
> + return -EINVAL;
> + }
> +
> + irq_set_status_flags(mhdp->irq[IRQ_OUT], IRQ_NOAUTOEN);
> + ret = devm_request_threaded_irq(dev, mhdp->irq[IRQ_OUT],
> + NULL, cdns_hdmi_irq_thread,
> + IRQF_ONESHOT, dev_name(dev),
> + mhdp);
> + if (ret < 0) {
> + dev_err(dev, "can't claim irq %d\n",
> + mhdp-
>irq[IRQ_OUT]);
> + return -EINVAL;
> + }
> +
> + mhdp->dev = dev;
> +
> + if (cdns_mhdp_hdmi_read_hpd(mhdp))
> + enable_irq(mhdp->irq[IRQ_OUT]);
> + else
> + enable_irq(mhdp->irq[IRQ_IN]);
> +
> + mhdp->bridge.driver_private = mhdp;
> + mhdp->bridge.funcs = &cdns_hdmi_bridge_funcs;
> + mhdp->bridge.of_node = dev->of_node;
> + mhdp->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID |
> + DRM_BRIDGE_OP_HPD;
> + mhdp->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
> + drm_bridge_add(&mhdp->bridge);
> +
> + memset(&pdevinfo, 0, sizeof(pdevinfo));
> + pdevinfo.parent = dev;
> + pdevinfo.id = PLATFORM_DEVID_AUTO;
> +
> + dev_set_drvdata(dev, mhdp);
> + mhdp->plat_data = of_device_get_match_data(dev);
> +
> + return 0;
> +}
> +
> +static int cdns_mhdp_imx_remove(struct platform_device *pdev)
> +{
> + struct cdns_mhdp_device *mhdp = platform_get_drvdata(pdev);
> + int ret = 0;
> +
> + drm_bridge_remove(&mhdp->bridge);
> +
> + return ret;
> +}
> +
> +static struct cdns_plat_data imx8mq_hdmi_drv_data = {
> + .lane_mapping = 0xe4,
> +};
> +
> +static const struct of_device_id cdns_mhdp_imx_dt_ids[] = {
> + { .compatible = "cdns,mhdp-imx8mq-hdmi",
> + .data = &imx8mq_hdmi_drv_data
> + },
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, cdns_mhdp_imx_dt_ids);
> +
> +static struct platform_driver cdns_mhdp_imx_platform_driver = {
> + .probe = cdns_mhdp_imx_probe,
> + .remove = cdns_mhdp_imx_remove,
> + .driver = {
> + .name = "cdns-mhdp-imx8mq-hdmi",
> + .of_match_table = cdns_mhdp_imx_dt_ids,
> + },
> +};
> +
> +module_platform_driver(cdns_mhdp_imx_platform_driver);
> +
> +MODULE_AUTHOR("Sandor Yu <sandor.yu at nxp.com>");
> +MODULE_DESCRIPTION("Cadence HDMI transmitter driver");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:cdns-hdmi");
More information about the linux-phy
mailing list