From 9d6e1670f14a77c092ce32b559de52d7ddea3748 Mon Sep 17 00:00:00 2001 From: Sandor Yu Date: Fri, 23 Aug 2019 13:57:49 +0800 Subject: [PATCH] drm: bridge: Add Cadence DP/HDMI core driver Add HDMI and DP core driver. Signed-off-by: Sandor Yu --- drivers/gpu/drm/bridge/cadence/Kconfig | 6 + drivers/gpu/drm/bridge/cadence/Makefile | 2 + drivers/gpu/drm/bridge/cadence/cdns-dp-core.c | 605 ++++++++++++++++++++++ drivers/gpu/drm/bridge/cadence/cdns-hdmi-core.c | 643 ++++++++++++++++++++++++ include/drm/bridge/cdns-mhdp-imx.h | 121 +++++ 5 files changed, 1377 insertions(+) create mode 100644 drivers/gpu/drm/bridge/cadence/cdns-dp-core.c create mode 100644 drivers/gpu/drm/bridge/cadence/cdns-hdmi-core.c create mode 100644 include/drm/bridge/cdns-mhdp-imx.h --- a/drivers/gpu/drm/bridge/cadence/Kconfig +++ b/drivers/gpu/drm/bridge/cadence/Kconfig @@ -5,3 +5,9 @@ config DRM_CDNS_MHDP depends on OF help Support Cadence MHDP API library. + +config DRM_CDNS_HDMI + tristate "Cadence HDMI DRM driver" + +config DRM_CDNS_DP + tristate "Cadence DP DRM driver" --- a/drivers/gpu/drm/bridge/cadence/Makefile +++ b/drivers/gpu/drm/bridge/cadence/Makefile @@ -1,3 +1,5 @@ #ccflags-y := -Iinclude/drm obj-$(CONFIG_DRM_CDNS_MHDP) += cdns-mhdp-common.o cdns-mhdp-hdmi.o +obj-$(CONFIG_DRM_CDNS_HDMI) += cdns-hdmi-core.o +obj-$(CONFIG_DRM_CDNS_DP) += cdns-dp-core.o --- /dev/null +++ b/drivers/gpu/drm/bridge/cadence/cdns-dp-core.c @@ -0,0 +1,605 @@ +/* + * Cadence Display Port Interface (DP) driver + * + * Copyright (C) 2019 NXP Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define aux_to_hdp(x) container_of(x, struct imx_mhdp_device, aux) + +/* + * This function only implements native DPDC reads and writes + */ +static ssize_t dp_aux_transfer(struct drm_dp_aux *aux, + struct drm_dp_aux_msg *msg) +{ + struct cdns_mhdp_device *mhdp = dev_get_drvdata(aux->dev); + bool native = msg->request & (DP_AUX_NATIVE_WRITE & DP_AUX_NATIVE_READ); + int ret; + + /* Ignore address only message */ + if ((msg->size == 0) || (msg->buffer == NULL)) { + msg->reply = native ? + DP_AUX_NATIVE_REPLY_ACK : DP_AUX_I2C_REPLY_ACK; + return msg->size; + } + + if (!native) { + dev_err(mhdp->dev, "%s: only native messages supported\n", __func__); + return -EINVAL; + } + + /* msg sanity check */ + if (msg->size > DP_AUX_MAX_PAYLOAD_BYTES) { + dev_err(mhdp->dev, "%s: invalid msg: size(%zu), request(%x)\n", + __func__, msg->size, (unsigned int)msg->request); + return -EINVAL; + } + + if (msg->request == DP_AUX_NATIVE_WRITE) { + const u8 *buf = msg->buffer; + int i; + for (i = 0; i < msg->size; ++i) { + ret = cdns_mhdp_dpcd_write(mhdp, + msg->address + i, buf[i]); + if (!ret) + continue; + + DRM_DEV_ERROR(mhdp->dev, "Failed to write DPCD\n"); + + return ret; + } + } + + if (msg->request == DP_AUX_NATIVE_READ) { + ret = cdns_mhdp_dpcd_read(mhdp, msg->address, msg->buffer, msg->size); + if (ret < 0) + return -EIO; + msg->reply = DP_AUX_NATIVE_REPLY_ACK; + return msg->size; + } + return 0; +} + +static int dp_aux_init(struct cdns_mhdp_device *mhdp, + struct device *dev) +{ + int ret; + + mhdp->dp.aux.name = "imx_dp_aux"; + mhdp->dp.aux.dev = dev; + mhdp->dp.aux.transfer = dp_aux_transfer; + + ret = drm_dp_aux_register(&mhdp->dp.aux); + + return ret; +} + +static int dp_aux_destroy(struct cdns_mhdp_device *mhdp) +{ + drm_dp_aux_unregister(&mhdp->dp.aux); + return 0; +} + +static void dp_pixel_clk_reset(struct cdns_mhdp_device *mhdp) +{ + u32 val; + + /* reset pixel clk */ + val = cdns_mhdp_reg_read(mhdp, SOURCE_HDTX_CAR); + cdns_mhdp_reg_write(mhdp, SOURCE_HDTX_CAR, val & 0xFD); + cdns_mhdp_reg_write(mhdp, SOURCE_HDTX_CAR, val); +} + +static void cdns_dp_mode_set(struct imx_mhdp_device *dp, + const struct drm_display_mode *mode) +{ + struct drm_dp_link link; + struct cdns_mhdp_device *mhdp = &dp->mhdp; + u32 lane_mapping = mhdp->dp.lane_mapping; + int ret; + char linkid[6]; + + memcpy(&mhdp->mode, mode, sizeof(struct drm_display_mode)); + + dp->dual_mode = video_is_dual_mode(mode); + + dp_pixel_clk_reset(mhdp); + + hdp_plat_call(dp, pclock_change); + + hdp_plat_call(dp, phy_init); + + ret = drm_dp_downstream_id(&mhdp->dp.aux, linkid); + if (ret < 0) { + DRM_INFO("Failed to Get DP link ID: %d\n", ret); + return; + } + DRM_INFO("DP link id: %s, 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x\n", + linkid, linkid[0], linkid[1], linkid[2], linkid[3], linkid[4], + linkid[5]); + + /* Check dp link */ + ret = drm_dp_link_probe(&mhdp->dp.aux, &link); + if (ret < 0) { + DRM_INFO("Failed to probe DP link: %d\n", ret); + return; + } + DRM_INFO("DP revision: 0x%x\n", link.revision); + DRM_INFO("DP rate: %d Mbps\n", link.rate); + DRM_INFO("DP number of lanes: %d\n", link.num_lanes); + DRM_INFO("DP capabilities: 0x%lx\n", link.capabilities); + + drm_dp_link_power_up(&mhdp->dp.aux, &mhdp->dp.link); + if (ret < 0) { + DRM_INFO("Failed to power DP link: %d\n", ret); + return; + } + + /* always use the number of lanes from the display*/ + mhdp->dp.link.num_lanes = link.num_lanes; + + /* Use the lower link rate */ + if (mhdp->dp.link_rate != 0) { + mhdp->dp.link.rate = min(mhdp->dp.link_rate, (u32)link.rate); + DRM_DEBUG("DP actual link rate: 0x%x\n", link.rate); + } + + /* initialize phy if lanes or link rate differnt */ + if (mhdp->dp.link.num_lanes != mhdp->dp.num_lanes || + mhdp->dp.link.rate != mhdp->dp.link_rate) + hdp_plat_call(dp, phy_init); + + /* Video off */ + ret = cdns_mhdp_set_video_status(mhdp, CONTROL_VIDEO_IDLE); + if (ret) { + DRM_DEV_ERROR(mhdp->dev, "Failed to valid video %d\n", ret); + return; + } + + /* Line swaping */ + cdns_mhdp_reg_write(mhdp, LANES_CONFIG, 0x00400000 | lane_mapping); + + /* Set DP host capability */ + ret = cdns_mhdp_set_host_cap(mhdp, mhdp->dp.link.num_lanes, false); + if (ret) { + DRM_DEV_ERROR(mhdp->dev, "Failed to set host cap %d\n", ret); + return; + } + + ret = cdns_mhdp_config_video(mhdp); + if (ret) { + DRM_DEV_ERROR(mhdp->dev, "Failed to config video %d\n", ret); + return; + } + + /* Link trainning */ + ret = cdns_mhdp_train_link(mhdp); + if (ret) { + DRM_DEV_ERROR(mhdp->dev, "Failed link train %d\n", ret); + return; + } + + ret = cdns_mhdp_set_video_status(mhdp, CONTROL_VIDEO_VALID); + if (ret) { + DRM_DEV_ERROR(mhdp->dev, "Failed to valid video %d\n", ret); + return; + } + + return; +} + +/* ----------------------------------------------------------------------------- + * dp TX Setup + */ +static enum drm_connector_status +cdns_dp_connector_detect(struct drm_connector *connector, bool force) +{ + struct imx_mhdp_device *dp = container_of(connector, + struct imx_mhdp_device, mhdp.connector.base); + u8 hpd = 0xf; + + hpd = cdns_mhdp_read_hpd(&dp->mhdp); + if (hpd == 1) + /* Cable Connected */ + return connector_status_connected; + else if (hpd == 0) + /* Cable Disconnedted */ + return connector_status_disconnected; + else { + /* Cable status unknown */ + DRM_INFO("Unknow cable status, hdp=%u\n", hpd); + return connector_status_unknown; + } +} + +static int cdns_dp_connector_get_modes(struct drm_connector *connector) +{ + struct imx_mhdp_device *dp = container_of(connector, + struct imx_mhdp_device, mhdp.connector.base); + int num_modes = 0; + struct edid *edid; + + edid = drm_do_get_edid(&dp->mhdp.connector.base, + cdns_mhdp_get_edid_block, &dp->mhdp); + if (edid) { + dev_info(dp->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 const struct drm_connector_funcs cdns_dp_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = cdns_dp_connector_detect, + .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_dp_connector_helper_funcs = { + .get_modes = cdns_dp_connector_get_modes, +}; + +static int cdns_dp_bridge_attach(struct drm_bridge *bridge) +{ + struct imx_mhdp_device *dp = bridge->driver_private; + struct drm_encoder *encoder = bridge->encoder; + struct drm_connector *connector = &dp->mhdp.connector.base; + + connector->interlace_allowed = 1; + connector->polled = DRM_CONNECTOR_POLL_HPD; + + drm_connector_helper_add(connector, &cdns_dp_connector_helper_funcs); + + drm_connector_init(bridge->dev, connector, &cdns_dp_connector_funcs, + DRM_MODE_CONNECTOR_DisplayPort); + + drm_connector_attach_encoder(connector, encoder); + + return 0; +} + +static enum drm_mode_status +cdns_dp_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_mode *mode) +{ + enum drm_mode_status mode_status = MODE_OK; + + /* We don't support double-clocked 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 now */ + if (mode->hdisplay > 3840) + return MODE_BAD_HVALUE; + + if (mode->vdisplay > 2160) + return MODE_BAD_VVALUE; + + return mode_status; +} + +static void cdns_dp_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *orig_mode, + const struct drm_display_mode *mode) +{ + struct imx_mhdp_device *dp = bridge->driver_private; + struct drm_display_info *display_info = &dp->mhdp.connector.base.display_info; + struct video_info *video = &dp->mhdp.video_info; + + switch (display_info->bpc) { + case 10: + video->color_depth = 10; + break; + case 6: + video->color_depth = 6; + break; + default: + video->color_depth = 8; + break; + } + + video->color_fmt = PXL_RGB; + video->v_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NVSYNC); + video->h_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NHSYNC); + + DRM_INFO("Mode: %dx%dp%d\n", mode->hdisplay, mode->vdisplay, mode->clock); + + mutex_lock(&dp->lock); + + cdns_dp_mode_set(dp, mode); + + mutex_unlock(&dp->lock); +} + +static void cdn_hdp_bridge_enable(struct drm_bridge *bridge) +{ +} + +static void cdn_hdp_bridge_disable(struct drm_bridge *bridge) +{ + struct imx_mhdp_device *dp = bridge->driver_private; + struct cdns_mhdp_device *mhdp = &dp->mhdp; + + cdns_mhdp_set_video_status(mhdp, CONTROL_VIDEO_IDLE); + drm_dp_link_power_down(&mhdp->dp.aux, &mhdp->dp.link); +} + +static const struct drm_bridge_funcs cdns_dp_bridge_funcs = { + .attach = cdns_dp_bridge_attach, + .enable = cdn_hdp_bridge_enable, + .disable = cdn_hdp_bridge_disable, + .mode_set = cdns_dp_bridge_mode_set, + .mode_valid = cdns_dp_bridge_mode_valid, +}; + +static void hotplug_work_func(struct work_struct *work) +{ + struct imx_mhdp_device *dp = container_of(work, + struct imx_mhdp_device, hotplug_work.work); + struct drm_connector *connector = &dp->mhdp.connector.base; + + drm_helper_hpd_irq_event(connector->dev); + + if (connector->status == connector_status_connected) { + DRM_INFO("HDMI/DP Cable Plug In\n"); + enable_irq(dp->irq[IRQ_OUT]); + } else if (connector->status == connector_status_disconnected) { + /* Cable Disconnedted */ + DRM_INFO("HDMI/DP Cable Plug Out\n"); + enable_irq(dp->irq[IRQ_IN]); + } +} + +static irqreturn_t cdns_dp_irq_thread(int irq, void *data) +{ + struct imx_mhdp_device *dp = data; + + disable_irq_nosync(irq); + + mod_delayed_work(system_wq, &dp->hotplug_work, + msecs_to_jiffies(HOTPLUG_DEBOUNCE_MS)); + + return IRQ_HANDLED; +} + +static void cdns_dp_parse_dt(struct cdns_mhdp_device *mhdp) +{ + struct device_node *of_node = mhdp->dev->of_node; + int ret; + + ret = of_property_read_u32(of_node, "lane-mapping", + &mhdp->dp.lane_mapping); + if (ret) { + mhdp->dp.lane_mapping = 0xc6; + dev_warn(mhdp->dev, "Failed to get lane_mapping - using default 0xc6\n"); + } + dev_info(mhdp->dev, "lane-mapping 0x%02x\n", mhdp->dp.lane_mapping); + + ret = of_property_read_u32(of_node, "link-rate", &mhdp->dp.link_rate); + if (ret) { + mhdp->dp.link_rate = 162000 ; + dev_warn(mhdp->dev, "Failed to get link-rate, use default 1620MHz\n"); + } + dev_info(mhdp->dev, "link-rate %d\n", mhdp->dp.link_rate); + + ret = of_property_read_u32(of_node, "num-lanes", &mhdp->dp.num_lanes); + if (ret) { + mhdp->dp.num_lanes = 4; + dev_warn(mhdp->dev, "Failed to get num_lanes - using default\n"); + } + dev_info(mhdp->dev, "dp_num_lanes 0x%02x\n", mhdp->dp.num_lanes); + + mhdp->dp.link.num_lanes = mhdp->dp.num_lanes; + mhdp->dp.link.rate= mhdp->dp.link_rate; +} + +static struct imx_mhdp_device * +__cdns_dp_probe(struct platform_device *pdev, + const struct cdn_plat_data *plat_data) +{ + struct device *dev = &pdev->dev; + struct imx_mhdp_device *dp; + struct resource *iores = NULL; + int ret; + + dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL); + if (!dp) + return ERR_PTR(-ENOMEM); + + dp->plat_data = plat_data; + dp->mhdp.dev = dev; + + mutex_init(&dp->lock); + mutex_init(&dp->audio_mutex); + spin_lock_init(&dp->audio_lock); + + INIT_DELAYED_WORK(&dp->hotplug_work, hotplug_work_func); + + iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); + dp->mhdp.regs = devm_ioremap(dev, iores->start, resource_size(iores)); + if (IS_ERR(dp->mhdp.regs)) { + ret = PTR_ERR(dp->mhdp.regs); + goto err_out; + } + +#if 0 + iores = platform_get_resource(pdev, IORESOURCE_MEM, 1); + dp->regs_ss = devm_ioremap(dev, iores->start, resource_size(iores)); + if (IS_ERR(dp->regs_ss)) { + ret = PTR_ERR(dp->regs_ss); + goto err_out; + } +#endif + + dp->irq[IRQ_IN] = platform_get_irq_byname(pdev, "plug_in"); + if (dp->irq[IRQ_IN] < 0) + dev_info(&pdev->dev, "No plug_in irq number\n"); + + dp->irq[IRQ_OUT] = platform_get_irq_byname(pdev, "plug_out"); + if (dp->irq[IRQ_OUT] < 0) + dev_info(&pdev->dev, "No plug_out irq number\n"); + + cdns_dp_parse_dt(&dp->mhdp); + + dp->dual_mode = false; + hdp_plat_call(dp, fw_init); + + /* DP FW alive check */ + ret = cdns_mhdp_check_alive(&dp->mhdp); + if (ret == false) { + DRM_ERROR("NO dp FW running\n"); + return ERR_PTR(-ENXIO); + } + + /* DP PHY init before AUX init */ + hdp_plat_call(dp, phy_init); + + /* Enable Hotplug Detect IRQ thread */ + irq_set_status_flags(dp->irq[IRQ_IN], IRQ_NOAUTOEN); + ret = devm_request_threaded_irq(dev, dp->irq[IRQ_IN], + NULL, cdns_dp_irq_thread, + IRQF_ONESHOT, dev_name(dev), + dp); + if (ret) { + dev_err(&pdev->dev, "can't claim irq %d\n", + dp->irq[IRQ_IN]); + goto err_out; + } + + irq_set_status_flags(dp->irq[IRQ_OUT], IRQ_NOAUTOEN); + ret = devm_request_threaded_irq(dev, dp->irq[IRQ_OUT], + NULL, cdns_dp_irq_thread, + IRQF_ONESHOT, dev_name(dev), + dp); + if (ret) { + dev_err(&pdev->dev, "can't claim irq %d\n", + dp->irq[IRQ_OUT]); + goto err_out; + } + if (cdns_mhdp_read_hpd(&dp->mhdp)) + enable_irq(dp->irq[IRQ_OUT]); + else + enable_irq(dp->irq[IRQ_IN]); + + dp->mhdp.bridge.base.driver_private = dp; + dp->mhdp.bridge.base.funcs = &cdns_dp_bridge_funcs; +#ifdef CONFIG_OF + dp->mhdp.bridge.base.of_node = pdev->dev.of_node; +#endif + + platform_set_drvdata(pdev, dp); + + dp_aux_init(&dp->mhdp, dev); + + return dp; + +err_out: + return ERR_PTR(ret); +} + +static void __cdns_dp_remove(struct imx_mhdp_device *dp) +{ + dp_aux_destroy(&dp->mhdp); +} + +/* ----------------------------------------------------------------------------- + * Probe/remove API, used from platforms based on the DRM bridge API. + */ +int cdns_dp_probe(struct platform_device *pdev, + const struct cdn_plat_data *plat_data) +{ + struct imx_mhdp_device *dp; + + dp = __cdns_dp_probe(pdev, plat_data); + if (IS_ERR(dp)) + return PTR_ERR(dp); + + drm_bridge_add(&dp->mhdp.bridge.base); + + return 0; +} +EXPORT_SYMBOL_GPL(cdns_dp_probe); + +void cdns_dp_remove(struct platform_device *pdev) +{ + struct imx_mhdp_device *dp = platform_get_drvdata(pdev); + + drm_bridge_remove(&dp->mhdp.bridge.base); + + __cdns_dp_remove(dp); +} +EXPORT_SYMBOL_GPL(cdns_dp_remove); + +/* ----------------------------------------------------------------------------- + * Bind/unbind API, used from platforms based on the component framework. + */ +int cdns_dp_bind(struct platform_device *pdev, struct drm_encoder *encoder, + const struct cdn_plat_data *plat_data) +{ + struct imx_mhdp_device *dp; + int ret; + + dp = __cdns_dp_probe(pdev, plat_data); + if (IS_ERR(dp)) + return PTR_ERR(dp); + + ret = drm_bridge_attach(encoder, &dp->mhdp.bridge.base, NULL); + if (ret) { + cdns_dp_remove(pdev); + DRM_ERROR("Failed to initialize bridge with drm\n"); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(cdns_dp_bind); + +void cdns_dp_unbind(struct device *dev) +{ + struct imx_mhdp_device *dp = dev_get_drvdata(dev); + + __cdns_dp_remove(dp); +} +EXPORT_SYMBOL_GPL(cdns_dp_unbind); + +MODULE_AUTHOR("Sandor Yu "); +MODULE_DESCRIPTION("Cadence Display Port transmitter driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:cdn-dp"); --- /dev/null +++ b/drivers/gpu/drm/bridge/cadence/cdns-hdmi-core.c @@ -0,0 +1,643 @@ +/* + * Cadence High-Definition Multimedia Interface (HDMI) driver + * + * Copyright (C) 2019 NXP Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void hdmi_writel(struct cdns_mhdp_device *mhdp, u32 val, u32 offset) +{ + struct imx_mhdp_device *hdmi = container_of(mhdp, struct imx_mhdp_device, mhdp); + + /* TODO */ + if (offset >= 0x1000 && hdmi->regmap_csr) { + /* Remap address to low 4K memory */ + regmap_write(hdmi->regmap_csr, hdmi->csr_ctrl0_reg, offset >> 12); + writel(val, (offset & 0xfff) + mhdp->regs); + /* Restore address mapping */ + regmap_write(hdmi->regmap_csr, hdmi->csr_ctrl0_reg, 0); + + } else + writel(val, mhdp->regs + offset); +} + +static int hdmi_sink_config(struct cdns_mhdp_device *mhdp) +{ + struct drm_scdc *scdc = &mhdp->connector.base.display_info.hdmi.scdc; + u8 buff; + int ret; + + if (mhdp->hdmi.char_rate > 340000) { + /* + * TMDS Character Rate above 340MHz should working in HDMI2.0 + * Enable scrambling and TMDS_Bit_Clock_Ratio + */ + buff = 3; + 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 = 1; + mhdp->hdmi.hdmi_type = MODE_HDMI_2_0; + } else { + /* Default work in HDMI1.4 */ + buff = 0; + mhdp->hdmi.hdmi_type = MODE_HDMI_1_4; + } + + /* TMDS config */ + ret = cdns_hdmi_scdc_write(mhdp, 0x20, buff); + return ret; +} + +static int hdmi_lanes_config(struct cdns_mhdp_device *mhdp) +{ + int ret; + + /* TODO */ + /* Set the lane swapping */ +// if (cpu_is_imx8qm()) + ret = cdns_mhdp_reg_write(mhdp, LANES_CONFIG, + F_SOURCE_PHY_LANE0_SWAP(3) | + F_SOURCE_PHY_LANE1_SWAP(0) | + F_SOURCE_PHY_LANE2_SWAP(1) | + F_SOURCE_PHY_LANE3_SWAP(2) | + F_SOURCE_PHY_COMB_BYPASS(0) | + F_SOURCE_PHY_20_10(1)); +#if 0 + else + ret = cdns_mhdp_reg_write(mhdp, LANES_CONFIG, + F_SOURCE_PHY_LANE0_SWAP(0) | + F_SOURCE_PHY_LANE1_SWAP(1) | + F_SOURCE_PHY_LANE2_SWAP(2) | + F_SOURCE_PHY_LANE3_SWAP(3) | + F_SOURCE_PHY_COMB_BYPASS(0) | + F_SOURCE_PHY_20_10(1)); +#endif + return ret; +} + +static void hdmi_info_frame_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); + hdmi_writel(mhdp, val, SOURCE_PIF_PKT_ALLOC_REG); + hdmi_writel(mhdp, F_PKT_ALLOC_WR_EN(1), SOURCE_PIF_PKT_ALLOC_WR_EN); + + /* flush fifo 1 */ + hdmi_writel(mhdp, F_FIFO1_FLUSH(1), SOURCE_PIF_FIFO1_FLUSH); + + /* write packet into memory */ + packet32 = (u32 *)packet; + len32 = packet_len / 4; + for (i = 0; i < len32; i++) + hdmi_writel(mhdp, F_DATA_WR(packet32[i]), SOURCE_PIF_DATA_WR); + + /* write entry id */ + hdmi_writel(mhdp, F_WR_ADDR(entry_id), SOURCE_PIF_WR_ADDR); + + /* write request */ + hdmi_writel(mhdp, F_HOST_WR(1), 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); + hdmi_writel(mhdp, val, SOURCE_PIF_PKT_ALLOC_REG); + + hdmi_writel(mhdp, F_PKT_ALLOC_WR_EN(1), SOURCE_PIF_PKT_ALLOC_WR_EN); +} + +#define RGB_ALLOWED_COLORIMETRY (BIT(HDMI_EXTENDED_COLORIMETRY_BT2020) |\ + BIT(HDMI_EXTENDED_COLORIMETRY_OPRGB)) +#define YCC_ALLOWED_COLORIMETRY (BIT(HDMI_EXTENDED_COLORIMETRY_BT2020) |\ + BIT(HDMI_EXTENDED_COLORIMETRY_BT2020_CONST_LUM) |\ + BIT(HDMI_EXTENDED_COLORIMETRY_OPYCC_601) |\ + BIT(HDMI_EXTENDED_COLORIMETRY_S_YCC_601) |\ + BIT(HDMI_EXTENDED_COLORIMETRY_XV_YCC_709) |\ + BIT(HDMI_EXTENDED_COLORIMETRY_XV_YCC_601)) +static int hdmi_avi_info_set(struct cdns_mhdp_device *mhdp, + struct drm_display_mode *mode) +{ + struct hdmi_avi_infoframe frame; +// struct drm_display_info *di = &mhdp->connector.base.display_info; +// enum hdmi_extended_colorimetry ext_col; +// u32 sink_col, allowed_col; + int format = mhdp->video_info.color_fmt; + u8 buf[32]; + int ret; + + /* Initialise info frame from DRM mode */ + drm_hdmi_avi_infoframe_from_display_mode(&frame, &mhdp->connector.base, mode); + +#if 0 //TODO to DCSS + /* Set up colorimetry */ + allowed_col = format == PXL_RGB ? RGB_ALLOWED_COLORIMETRY : + YCC_ALLOWED_COLORIMETRY; + + sink_col = di->hdmi.colorimetry & allowed_col; + + if (sink_col & BIT(HDMI_EXTENDED_COLORIMETRY_BT2020)) + ext_col = HDMI_EXTENDED_COLORIMETRY_BT2020; + else if (sink_col & BIT(HDMI_EXTENDED_COLORIMETRY_BT2020_CONST_LUM)) + ext_col = HDMI_EXTENDED_COLORIMETRY_BT2020_CONST_LUM; + else if (sink_col & BIT(HDMI_EXTENDED_COLORIMETRY_OPRGB)) + ext_col = HDMI_EXTENDED_COLORIMETRY_OPRGB; + else if (sink_col & BIT(HDMI_EXTENDED_COLORIMETRY_XV_YCC_709)) + ext_col = HDMI_EXTENDED_COLORIMETRY_XV_YCC_709; + else if (sink_col & BIT(HDMI_EXTENDED_COLORIMETRY_OPYCC_601)) + ext_col = HDMI_EXTENDED_COLORIMETRY_OPYCC_601; + else if (sink_col & BIT(HDMI_EXTENDED_COLORIMETRY_S_YCC_601)) + ext_col = HDMI_EXTENDED_COLORIMETRY_S_YCC_601; + else if (sink_col & BIT(HDMI_EXTENDED_COLORIMETRY_XV_YCC_601)) + ext_col = HDMI_EXTENDED_COLORIMETRY_XV_YCC_601; + else + ext_col = 0; + + frame.colorimetry = sink_col ? HDMI_COLORIMETRY_EXTENDED : + HDMI_COLORIMETRY_NONE; + frame.extended_colorimetry = ext_col; +#endif + + switch (format) { + case YCBCR_4_4_4: + frame.colorspace = HDMI_COLORSPACE_YUV444; + break; + case YCBCR_4_2_2: + frame.colorspace = HDMI_COLORSPACE_YUV422; + break; + case YCBCR_4_2_0: + frame.colorspace = HDMI_COLORSPACE_YUV420; + break; + default: + frame.colorspace = HDMI_COLORSPACE_RGB; + break; + } + + 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; + hdmi_info_frame_set(mhdp, 0, sizeof(buf), buf, HDMI_INFOFRAME_TYPE_AVI); + return 0; +} + +static int 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->connector.base, mode); + if (ret < 0) { + DRM_WARN("Unable to init vendor infoframe: %d\n", ret); + return -1; + } + + 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 -1; + } + + buf[0] = 0; + hdmi_info_frame_set(mhdp, 3, sizeof(buf), buf, HDMI_INFOFRAME_TYPE_VENDOR); + return 0; +} + +void cdns_hdmi_mode_set(struct cdns_mhdp_device *mhdp) +{ + struct drm_display_mode *mode = &mhdp->mode; + int ret; + + ret = hdmi_sink_config(mhdp); + if (ret < 0) { + DRM_ERROR("%s failed\n", __func__); + return; + } + + 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.color_depth == 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 enable only when HDMI1.4 4K mode */ + ret = hdmi_vendor_info_set(mhdp, mode); + if (ret < 0) + DRM_WARN("Unable to configure Vendor infoframe\n"); + + 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; + } + + /* wait HDMI PHY pixel clock stable */ + msleep(50); +} + +static enum drm_connector_status +cdns_hdmi_connector_detect(struct drm_connector *connector, bool force) +{ + struct imx_mhdp_device *hdmi = + container_of(connector, struct imx_mhdp_device, mhdp.connector.base); + + u8 hpd = 0xf; + + hpd = cdns_mhdp_read_hpd(&hdmi->mhdp); + + if (hpd == 1) + /* Cable Connected */ + return connector_status_connected; + else if (hpd == 0) + /* Cable Disconnedted */ + return connector_status_disconnected; + else { + /* Cable status unknown */ + DRM_INFO("Unknow cable status, hdp=%u\n", hpd); + return connector_status_unknown; + } +} + +static int cdns_hdmi_connector_get_modes(struct drm_connector *connector) +{ + struct imx_mhdp_device *hdmi = container_of(connector, struct imx_mhdp_device, + mhdp.connector.base); + int num_modes = 0; + struct edid *edid; + + edid = drm_do_get_edid(&hdmi->mhdp.connector.base, + cdns_hdmi_get_edid_block, &hdmi->mhdp); + if (edid) { + dev_info(hdmi->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 const struct drm_connector_funcs cdns_hdmi_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = cdns_hdmi_connector_detect, + .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, +}; + +static int cdns_hdmi_bridge_attach(struct drm_bridge *bridge) +{ + struct imx_mhdp_device *hdmi = bridge->driver_private; + struct drm_encoder *encoder = bridge->encoder; + struct drm_connector *connector = &hdmi->mhdp.connector.base; + + connector->interlace_allowed = 1; + 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_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_mode *mode) +{ + enum drm_mode_status mode_status = MODE_OK; + + /* 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 148.5MHz */ + if (mode->clock > 148500) + return MODE_CLOCK_HIGH; + + /* 4096x2160 is not supported */ + if (mode->hdisplay > 3840 || mode->vdisplay > 2160) + return MODE_BAD_HVALUE; + + return mode_status; +} + +static void cdns_hdmi_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *orig_mode, + const struct drm_display_mode *mode) +{ + struct imx_mhdp_device *hdmi = bridge->driver_private; + struct drm_display_info *display_info = &hdmi->mhdp.connector.base.display_info; + struct video_info *video = &hdmi->mhdp.video_info; + + switch (display_info->bpc) { + case 10: + video->color_depth = 10; + break; + case 6: + video->color_depth = 6; + break; + default: + video->color_depth = 8; + break; + } + + video->color_fmt = PXL_RGB; + video->v_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NVSYNC); + video->h_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NHSYNC); + + mutex_lock(&hdmi->lock); + + DRM_INFO("Mode: %dx%dp%d\n", mode->hdisplay, mode->vdisplay, mode->clock); + + memcpy(&hdmi->mhdp.mode, mode, sizeof(struct drm_display_mode)); + + hdmi->dual_mode = video_is_dual_mode(mode); + + hdmi_lanes_config(&hdmi->mhdp); + + hdp_plat_call(hdmi, pclock_change); + + hdp_plat_call(hdmi, phy_init); + + cdns_hdmi_mode_set(&hdmi->mhdp); + + mutex_unlock(&hdmi->lock); +} + +static const struct drm_bridge_funcs cdns_hdmi_bridge_funcs = { + .attach = cdns_hdmi_bridge_attach, + .mode_set = cdns_hdmi_bridge_mode_set, + .mode_valid = cdns_hdmi_bridge_mode_valid, +}; + +static void hotplug_work_func(struct work_struct *work) +{ + struct imx_mhdp_device *hdmi = container_of(work, + struct imx_mhdp_device, hotplug_work.work); + struct drm_connector *connector = &hdmi->mhdp.connector.base; + + drm_helper_hpd_irq_event(connector->dev); + + if (connector->status == connector_status_connected) { + /* Cable Connected */ + DRM_INFO("HDMI Cable Plug In\n"); + enable_irq(hdmi->irq[IRQ_OUT]); + } else if (connector->status == connector_status_disconnected) { + /* Cable Disconnedted */ + DRM_INFO("HDMI Cable Plug Out\n"); + enable_irq(hdmi->irq[IRQ_IN]); + } +} + +static irqreturn_t cdns_hdmi_irq_thread(int irq, void *data) +{ + struct imx_mhdp_device *hdmi = data; + + disable_irq_nosync(irq); + + mod_delayed_work(system_wq, &hdmi->hotplug_work, + msecs_to_jiffies(HOTPLUG_DEBOUNCE_MS)); + + return IRQ_HANDLED; +} + +static struct imx_mhdp_device * +__cdns_hdmi_probe(struct platform_device *pdev, + const struct cdn_plat_data *plat_data) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct platform_device_info pdevinfo; + struct imx_mhdp_device *hdmi; + struct resource *iores = NULL; + int ret; + + hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL); + if (!hdmi) + return ERR_PTR(-ENOMEM); + + hdmi->plat_data = plat_data; + hdmi->mhdp.dev = dev; + + mutex_init(&hdmi->lock); + mutex_init(&hdmi->audio_mutex); + spin_lock_init(&hdmi->audio_lock); + + INIT_DELAYED_WORK(&hdmi->hotplug_work, hotplug_work_func); + + iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); + hdmi->mhdp.regs = devm_ioremap(dev, iores->start, resource_size(iores)); + if (IS_ERR(hdmi->mhdp.regs)) { + ret = PTR_ERR(hdmi->mhdp.regs); + goto err_out; + } + + /* csr register base */ + hdmi->regmap_csr = syscon_regmap_lookup_by_phandle(np, "csr"); + if (IS_ERR(hdmi->regmap_csr)) { + dev_info(dev, "No csr regmap\n"); + } + + hdmi->irq[IRQ_IN] = platform_get_irq_byname(pdev, "plug_in"); + if (hdmi->irq[IRQ_IN] < 0) { + dev_info(&pdev->dev, "No plug_in irq number\n"); + return ERR_PTR(-EPROBE_DEFER); + } + + hdmi->irq[IRQ_OUT] = platform_get_irq_byname(pdev, "plug_out"); + if (hdmi->irq[IRQ_OUT] < 0) { + dev_info(&pdev->dev, "No plug_out irq number\n"); + return ERR_PTR(-EPROBE_DEFER); + } + + /* Initialize dual_mode to false */ + hdmi->dual_mode = false; + + /* Initialize FW */ + hdp_plat_call(hdmi, fw_init); + + /* HDMI FW alive check */ + ret = cdns_mhdp_check_alive(&hdmi->mhdp); + if (ret == false) { + DRM_ERROR("NO HDMI FW running\n"); + return ERR_PTR(-ENXIO); + } + + /* Enable Hotplug Detect thread */ + irq_set_status_flags(hdmi->irq[IRQ_IN], IRQ_NOAUTOEN); + ret = devm_request_threaded_irq(dev, hdmi->irq[IRQ_IN], + NULL, cdns_hdmi_irq_thread, + IRQF_ONESHOT, dev_name(dev), + hdmi); + if (ret) { + dev_err(&pdev->dev, "can't claim irq %d\n", + hdmi->irq[IRQ_IN]); + goto err_out; + } + + irq_set_status_flags(hdmi->irq[IRQ_OUT], IRQ_NOAUTOEN); + ret = devm_request_threaded_irq(dev, hdmi->irq[IRQ_OUT], + NULL, cdns_hdmi_irq_thread, + IRQF_ONESHOT, dev_name(dev), + hdmi); + if (ret) { + dev_err(&pdev->dev, "can't claim irq %d\n", + hdmi->irq[IRQ_OUT]); + goto err_out; + } + + if (cdns_mhdp_read_hpd(&hdmi->mhdp)) + enable_irq(hdmi->irq[IRQ_OUT]); + else + enable_irq(hdmi->irq[IRQ_IN]); + + hdmi->mhdp.bridge.base.driver_private = hdmi; + hdmi->mhdp.bridge.base.funcs = &cdns_hdmi_bridge_funcs; +#ifdef CONFIG_OF + hdmi->mhdp.bridge.base.of_node = pdev->dev.of_node; +#endif + + memset(&pdevinfo, 0, sizeof(pdevinfo)); + pdevinfo.parent = dev; + pdevinfo.id = PLATFORM_DEVID_AUTO; + + platform_set_drvdata(pdev, hdmi); + + return hdmi; + +err_out: + + return ERR_PTR(ret); +} + +static void __cdns_hdmi_remove(struct imx_mhdp_device *hdmi) +{ +} + +/* ----------------------------------------------------------------------------- + * Probe/remove API, used from platforms based on the DRM bridge API. + */ +int cdns_hdmi_probe(struct platform_device *pdev, + const struct cdn_plat_data *plat_data) +{ + struct imx_mhdp_device *hdmi; + + hdmi = __cdns_hdmi_probe(pdev, plat_data); + if (IS_ERR(hdmi)) + return PTR_ERR(hdmi); + + drm_bridge_add(&hdmi->mhdp.bridge.base); + + return 0; +} +EXPORT_SYMBOL_GPL(cdns_hdmi_probe); + +void cdns_hdmi_remove(struct platform_device *pdev) +{ + struct imx_mhdp_device *hdmi = platform_get_drvdata(pdev); + + drm_bridge_remove(&hdmi->mhdp.bridge.base); + + __cdns_hdmi_remove(hdmi); +} +EXPORT_SYMBOL_GPL(cdns_hdmi_remove); + +/* ----------------------------------------------------------------------------- + * Bind/unbind API, used from platforms based on the component framework. + */ +int cdns_hdmi_bind(struct platform_device *pdev, struct drm_encoder *encoder, + const struct cdn_plat_data *plat_data) +{ + struct imx_mhdp_device *hdmi; + int ret; + + hdmi = __cdns_hdmi_probe(pdev, plat_data); + if (IS_ERR(hdmi)) + return PTR_ERR(hdmi); + + ret = drm_bridge_attach(encoder, &hdmi->mhdp.bridge.base, NULL); + if (ret) { + cdns_hdmi_remove(pdev); + DRM_ERROR("Failed to initialize bridge with drm\n"); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(cdns_hdmi_bind); + +void cdns_hdmi_unbind(struct device *dev) +{ + struct imx_mhdp_device *hdmi = dev_get_drvdata(dev); + + __cdns_hdmi_remove(hdmi); +} +EXPORT_SYMBOL_GPL(cdns_hdmi_unbind); + +MODULE_AUTHOR("Sandor Yu "); +MODULE_DESCRIPTION("Cadence HDMI transmitter driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:cdn-hdmi"); --- /dev/null +++ b/include/drm/bridge/cdns-mhdp-imx.h @@ -0,0 +1,121 @@ +/* + * Cadence High-Definition Multimedia Interface (HDMI) driver + * + * Copyright (C) 2019 NXP Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ +#ifndef CDNS_MHDP_IMX_H_ +#define CDNS_MHDP_IMX_H_ + +#include + +#define IRQ_IN 0 +#define IRQ_OUT 1 +#define IRQ_NUM 2 + +#define hdp_plat_call(hdp, operation) \ + (!(hdp) ? -ENODEV : (((hdp)->plat_data && (hdp)->plat_data->operation) ? \ + (hdp)->plat_data->operation(hdp) : ENOIOCTLCMD)) + +#define HDP_DUAL_MODE_MIN_PCLK_RATE 300000 /* KHz */ +#define HDP_SINGLE_MODE_MAX_WIDTH 1920 + +static inline bool video_is_dual_mode(const struct drm_display_mode *mode) +{ + return (mode->clock > HDP_DUAL_MODE_MIN_PCLK_RATE || + mode->hdisplay > HDP_SINGLE_MODE_MAX_WIDTH) ? true : false; +} + +struct imx_mhdp_device; + +struct imx_hdp_clks { + struct clk *av_pll; + struct clk *dig_pll; + struct clk *clk_ipg; + struct clk *clk_core; + struct clk *clk_pxl; + struct clk *clk_pxl_mux; + struct clk *clk_pxl_link; + + struct clk *lpcg_hdp; + struct clk *lpcg_msi; + struct clk *lpcg_pxl; + struct clk *lpcg_vif; + struct clk *lpcg_lis; + struct clk *lpcg_apb; + struct clk *lpcg_apb_csr; + struct clk *lpcg_apb_ctrl; + + struct clk *lpcg_i2s; + struct clk *clk_i2s_bypass; +}; + +struct cdn_plat_data { + /* Vendor PHY support */ + int (*phy_init)(struct imx_mhdp_device *hdmi); + int (*bind)(struct platform_device *pdev, + struct drm_encoder *encoder, + const struct cdn_plat_data *plat_data); + void (*unbind)(struct device *dev); + int (*fw_init)(struct imx_mhdp_device *hdp); + void (*pclock_change)(struct imx_mhdp_device *hdp); + char is_dp; +}; + +struct imx_mhdp_device { + struct cdns_mhdp_device mhdp; + + struct mutex lock; + struct mutex audio_mutex; + spinlock_t audio_lock; + bool connected; + bool active; + bool suspended; + struct imx_hdp_clks clks; + + const struct cdn_plat_data *plat_data; + + int irq[IRQ_NUM]; + struct delayed_work hotplug_work; + //void __iomem *regmap_csr; + struct regmap *regmap_csr; + u32 csr_pxl_mux_reg; + u32 csr_ctrl0_reg; + u32 csr_ctrl0_sec; + + struct audio_info audio_info; + bool sink_has_audio; + u32 dual_mode; + + struct device *pd_mhdp_dev; + struct device *pd_pll0_dev; + struct device *pd_pll1_dev; + struct device_link *pd_mhdp_link; + struct device_link *pd_pll0_link; + struct device_link *pd_pll1_link; + + u32 phy_init; +}; + +int cdns_hdmi_probe(struct platform_device *pdev, + const struct cdn_plat_data *plat_data); +void cdns_hdmi_remove(struct platform_device *pdev); +void cdns_hdmi_unbind(struct device *dev); +int cdns_hdmi_bind(struct platform_device *pdev, struct drm_encoder *encoder, + const struct cdn_plat_data *plat_data); +void cdns_hdmi_set_sample_rate(struct imx_mhdp_device *hdmi, unsigned int rate); +void cdns_hdmi_audio_enable(struct imx_mhdp_device *hdmi); +void cdns_hdmi_audio_disable(struct imx_mhdp_device *hdmi); +int cdns_dp_probe(struct platform_device *pdev, + const struct cdn_plat_data *plat_data); +void cdns_dp_remove(struct platform_device *pdev); +void cdns_dp_unbind(struct device *dev); +int cdns_dp_bind(struct platform_device *pdev, struct drm_encoder *encoder, + const struct cdn_plat_data *plat_data); + +#endif /* CDNS_MHDP_IMX_H_ */