| From 9d6e1670f14a77c092ce32b559de52d7ddea3748 Mon Sep 17 00:00:00 2001 |
| From: Sandor Yu <Sandor.yu@nxp.com> |
| 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 <Sandor.yu@nxp.com> |
| --- |
| 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 <drm/bridge/cdns-mhdp-imx.h> |
| +#include <drm/drm_atomic_helper.h> |
| +#include <drm/drm_crtc_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/drmP.h> |
| +#include <linux/delay.h> |
| +#include <linux/err.h> |
| +#include <linux/irq.h> |
| +#include <linux/module.h> |
| +#include <linux/mutex.h> |
| +#include <linux/of_device.h> |
| + |
| +#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 <sandor.yu@nxp.com>"); |
| +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 <drm/bridge/cdns-mhdp-imx.h> |
| +#include <drm/drm_atomic_helper.h> |
| +#include <drm/drm_crtc_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/drmP.h> |
| +#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/regmap.h> |
| +#include <linux/of_device.h> |
| + |
| +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 <sandor.yu@nxp.com>"); |
| +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 <drm/bridge/cdns-mhdp-common.h> |
| + |
| +#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_ */ |