diff options
Diffstat (limited to 'drivers/gpu/drm/imx/hdp/imx-hdp.c')
-rw-r--r-- | drivers/gpu/drm/imx/hdp/imx-hdp.c | 1455 |
1 files changed, 1455 insertions, 0 deletions
diff --git a/drivers/gpu/drm/imx/hdp/imx-hdp.c b/drivers/gpu/drm/imx/hdp/imx-hdp.c new file mode 100644 index 000000000000..b985297bab1d --- /dev/null +++ b/drivers/gpu/drm/imx/hdp/imx-hdp.c @@ -0,0 +1,1455 @@ +/* + * Copyright 2017-2018 NXP + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#include <linux/clk.h> +#include <linux/kthread.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/component.h> +#include <linux/mfd/syscon.h> +#include <linux/of.h> +#include <linux/irq.h> +#include <linux/of_device.h> + +#include "imx-hdp.h" +#include "imx-hdmi.h" +#include "imx-dp.h" +#include "../imx-drm.h" + +struct drm_display_mode *g_mode; + +static struct drm_display_mode edid_cea_modes[] = { + /* 3 - 720x480@60Hz */ + { DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 27000, 720, 736, + 798, 858, 0, 480, 489, 495, 525, 0, + DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC), + .vrefresh = 60, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, }, + /* 4 - 1280x720@60Hz */ + { DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 1390, + 1430, 1650, 0, 720, 725, 730, 750, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .vrefresh = 60, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, }, + /* 16 - 1920x1080@60Hz */ + { DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 148500, 1920, 2008, + 2052, 2200, 0, 1080, 1084, 1089, 1125, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .vrefresh = 60, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, }, + /* 97 - 3840x2160@60Hz */ + { DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 594000, + 3840, 4016, 4104, 4400, 0, + 2160, 2168, 2178, 2250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .vrefresh = 60, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, }, + /* 96 - 3840x2160@30Hz */ + { DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 297000, + 3840, 4016, 4104, 4400, 0, + 2160, 2168, 2178, 2250, 0, + DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC), + .vrefresh = 30, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, }, +}; + +static inline struct imx_hdp *enc_to_imx_hdp(struct drm_encoder *e) +{ + return container_of(e, struct imx_hdp, encoder); +} + +static void imx_hdp_state_init(struct imx_hdp *hdp) +{ + state_struct *state = &hdp->state; + + memset(state, 0, sizeof(state_struct)); + mutex_init(&state->mutex); + + state->mem = &hdp->mem; + state->rw = hdp->rw; + state->edp = hdp->is_edp; +} + +#ifdef CONFIG_IMX_HDP_CEC +static void imx_hdp_cec_init(struct imx_hdp *hdp) +{ + state_struct *state = &hdp->state; + struct imx_cec_dev *cec = &hdp->cec; + u32 clk_MHz; + + memset(cec, 0, sizeof(struct imx_cec_dev)); + + CDN_API_GetClock(state, &clk_MHz); + cec->clk_div = clk_MHz * 10; + cec->dev = hdp->dev; + cec->mem = &hdp->mem; + cec->rw = hdp->rw; +} +#endif + +static void imx8qm_pixel_link_mux(state_struct *state, struct drm_display_mode *mode) +{ + struct imx_hdp *hdp = state_to_imx_hdp(state); + u32 val; + + val = 4; /* RGB */ + if (mode->flags & DRM_MODE_FLAG_PVSYNC) + val |= 1 << PL_MUX_CTL_VCP_OFFSET; + if (mode->flags & DRM_MODE_FLAG_PHSYNC) + val |= 1 << PL_MUX_CTL_HCP_OFFSET; + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + val |= 0x2; + + writel(val, hdp->mem.ss_base + CSR_PIXEL_LINK_MUX_CTL); +} + +static int imx8qm_pixel_link_validate(state_struct *state) +{ + struct imx_hdp *hdp = state_to_imx_hdp(state); + sc_err_t sciErr; + + sciErr = sc_ipc_getMuID(&hdp->mu_id); + if (sciErr != SC_ERR_NONE) { + DRM_ERROR("Cannot obtain MU ID\n"); + return -EINVAL; + } + + sciErr = sc_ipc_open(&hdp->ipcHndl, hdp->mu_id); + if (sciErr != SC_ERR_NONE) { + DRM_ERROR("sc_ipc_open failed! (sciError = %d)\n", sciErr); + return -EINVAL; + } + + sciErr = sc_misc_set_control(hdp->ipcHndl, SC_R_DC_0, + SC_C_PXL_LINK_MST1_VLD, 1); + if (sciErr != SC_ERR_NONE) { + DRM_ERROR("SC_R_DC_0:SC_C_PXL_LINK_MST1_VLD sc_misc_set_control failed! (sciError = %d)\n", sciErr); + return -EINVAL; + } + + sc_ipc_close(hdp->mu_id); + + return 0; +} + +static int imx8qm_pixel_link_invalidate(state_struct *state) +{ + struct imx_hdp *hdp = state_to_imx_hdp(state); + sc_err_t sciErr; + + sciErr = sc_ipc_getMuID(&hdp->mu_id); + if (sciErr != SC_ERR_NONE) { + DRM_ERROR("Cannot obtain MU ID\n"); + return -EINVAL; + } + + sciErr = sc_ipc_open(&hdp->ipcHndl, hdp->mu_id); + if (sciErr != SC_ERR_NONE) { + DRM_ERROR("sc_ipc_open failed! (sciError = %d)\n", sciErr); + return -EINVAL; + } + + sciErr = sc_misc_set_control(hdp->ipcHndl, SC_R_DC_0, SC_C_PXL_LINK_MST1_VLD, 0); + if (sciErr != SC_ERR_NONE) { + DRM_ERROR("SC_R_DC_0:SC_C_PXL_LINK_MST1_VLD sc_misc_set_control failed! (sciError = %d)\n", sciErr); + return -EINVAL; + } + + sc_ipc_close(hdp->mu_id); + + return 0; +} + +static int imx8qm_pixel_link_sync_ctrl_enable(state_struct *state) +{ + struct imx_hdp *hdp = state_to_imx_hdp(state); + sc_err_t sciErr; + + sciErr = sc_ipc_getMuID(&hdp->mu_id); + if (sciErr != SC_ERR_NONE) { + DRM_ERROR("Cannot obtain MU ID\n"); + return -EINVAL; + } + + sciErr = sc_ipc_open(&hdp->ipcHndl, hdp->mu_id); + if (sciErr != SC_ERR_NONE) { + DRM_ERROR("sc_ipc_open failed! (sciError = %d)\n", sciErr); + return -EINVAL; + } + + sciErr = sc_misc_set_control(hdp->ipcHndl, SC_R_DC_0, SC_C_SYNC_CTRL0, 1); + if (sciErr != SC_ERR_NONE) { + DRM_ERROR("SC_R_DC_0:SC_C_SYNC_CTRL0 sc_misc_set_control failed! (sciError = %d)\n", sciErr); + return -EINVAL; + } + + sc_ipc_close(hdp->mu_id); + + return 0; +} + +static int imx8qm_pixel_link_sync_ctrl_disable(state_struct *state) +{ + struct imx_hdp *hdp = state_to_imx_hdp(state); + sc_err_t sciErr; + + sciErr = sc_ipc_getMuID(&hdp->mu_id); + if (sciErr != SC_ERR_NONE) { + DRM_ERROR("Cannot obtain MU ID\n"); + return -EINVAL; + } + + sciErr = sc_ipc_open(&hdp->ipcHndl, hdp->mu_id); + if (sciErr != SC_ERR_NONE) { + DRM_ERROR("sc_ipc_open failed! (sciError = %d)\n", sciErr); + return -EINVAL; + } + + sciErr = sc_misc_set_control(hdp->ipcHndl, SC_R_DC_0, SC_C_SYNC_CTRL0, 0); + if (sciErr != SC_ERR_NONE) { + DRM_ERROR("SC_R_DC_0:SC_C_SYNC_CTRL0 sc_misc_set_control failed! (sciError = %d)\n", sciErr); + return -EINVAL; + } + + sc_ipc_close(hdp->mu_id); + + return 0; +} + +void imx8qm_phy_reset(sc_ipc_t ipcHndl, struct hdp_mem *mem, u8 reset) +{ + sc_err_t sciErr; + /* set the pixel link mode and pixel type */ + sciErr = sc_misc_set_control(ipcHndl, SC_R_HDMI, SC_C_PHY_RESET, reset); + if (sciErr != SC_ERR_NONE) + DRM_ERROR("SC_R_HDMI PHY reset failed %d!\n", sciErr); +} + +void imx8mq_phy_reset(sc_ipc_t ipcHndl, struct hdp_mem *mem, u8 reset) +{ + void *tmp_addr = mem->rst_base; + + if (reset) + __raw_writel(0x8, + (volatile unsigned int *)(tmp_addr+0x4)); /*set*/ + else + __raw_writel(0x8, + (volatile unsigned int *)(tmp_addr+0x8)); /*clear*/ + + + return; +} + +int imx8qm_clock_init(struct hdp_clks *clks) +{ + struct imx_hdp *hdp = clks_to_imx_hdp(clks); + struct device *dev = hdp->dev; + + clks->av_pll = devm_clk_get(dev, "av_pll"); + if (IS_ERR(clks->av_pll)) { + dev_warn(dev, "failed to get av pll clk\n"); + return PTR_ERR(clks->av_pll); + } + + clks->dig_pll = devm_clk_get(dev, "dig_pll"); + if (IS_ERR(clks->dig_pll)) { + dev_warn(dev, "failed to get dig pll clk\n"); + return PTR_ERR(clks->dig_pll); + } + + clks->clk_ipg = devm_clk_get(dev, "clk_ipg"); + if (IS_ERR(clks->clk_ipg)) { + dev_warn(dev, "failed to get dp ipg clk\n"); + return PTR_ERR(clks->clk_ipg); + } + + clks->clk_core = devm_clk_get(dev, "clk_core"); + if (IS_ERR(clks->clk_core)) { + dev_warn(dev, "failed to get hdp core clk\n"); + return PTR_ERR(clks->clk_core); + } + + clks->clk_pxl = devm_clk_get(dev, "clk_pxl"); + if (IS_ERR(clks->clk_pxl)) { + dev_warn(dev, "failed to get pxl clk\n"); + return PTR_ERR(clks->clk_pxl); + } + + clks->clk_pxl_mux = devm_clk_get(dev, "clk_pxl_mux"); + if (IS_ERR(clks->clk_pxl_mux)) { + dev_warn(dev, "failed to get pxl mux clk\n"); + return PTR_ERR(clks->clk_pxl_mux); + } + + clks->clk_pxl_link = devm_clk_get(dev, "clk_pxl_link"); + if (IS_ERR(clks->clk_pxl_mux)) { + dev_warn(dev, "failed to get pxl link clk\n"); + return PTR_ERR(clks->clk_pxl_link); + } + + clks->clk_hdp = devm_clk_get(dev, "clk_hdp"); + if (IS_ERR(clks->clk_hdp)) { + dev_warn(dev, "failed to get hdp clk\n"); + return PTR_ERR(clks->clk_hdp); + } + + clks->clk_phy = devm_clk_get(dev, "clk_phy"); + if (IS_ERR(clks->clk_phy)) { + dev_warn(dev, "failed to get phy clk\n"); + return PTR_ERR(clks->clk_phy); + } + clks->clk_apb = devm_clk_get(dev, "clk_apb"); + if (IS_ERR(clks->clk_apb)) { + dev_warn(dev, "failed to get apb clk\n"); + return PTR_ERR(clks->clk_apb); + } + clks->clk_lis = devm_clk_get(dev, "clk_lis"); + if (IS_ERR(clks->clk_lis)) { + dev_warn(dev, "failed to get lis clk\n"); + return PTR_ERR(clks->clk_lis); + } + clks->clk_msi = devm_clk_get(dev, "clk_msi"); + if (IS_ERR(clks->clk_msi)) { + dev_warn(dev, "failed to get msi clk\n"); + return PTR_ERR(clks->clk_msi); + } + clks->clk_lpcg = devm_clk_get(dev, "clk_lpcg"); + if (IS_ERR(clks->clk_lpcg)) { + dev_warn(dev, "failed to get lpcg clk\n"); + return PTR_ERR(clks->clk_lpcg); + } + clks->clk_even = devm_clk_get(dev, "clk_even"); + if (IS_ERR(clks->clk_even)) { + dev_warn(dev, "failed to get even clk\n"); + return PTR_ERR(clks->clk_even); + } + clks->clk_dbl = devm_clk_get(dev, "clk_dbl"); + if (IS_ERR(clks->clk_dbl)) { + dev_warn(dev, "failed to get dbl clk\n"); + return PTR_ERR(clks->clk_dbl); + } + clks->clk_vif = devm_clk_get(dev, "clk_vif"); + if (IS_ERR(clks->clk_vif)) { + dev_warn(dev, "failed to get vif clk\n"); + return PTR_ERR(clks->clk_vif); + } + clks->clk_apb_csr = devm_clk_get(dev, "clk_apb_csr"); + if (IS_ERR(clks->clk_apb_csr)) { + dev_warn(dev, "failed to get apb csr clk\n"); + return PTR_ERR(clks->clk_apb_csr); + } + clks->clk_apb_ctrl = devm_clk_get(dev, "clk_apb_ctrl"); + if (IS_ERR(clks->clk_apb_ctrl)) { + dev_warn(dev, "failed to get apb ctrl clk\n"); + return PTR_ERR(clks->clk_apb_ctrl); + } + clks->clk_i2s = devm_clk_get(dev, "clk_i2s"); + if (IS_ERR(clks->clk_i2s)) { + dev_warn(dev, "failed to get i2s clk\n"); + return PTR_ERR(clks->clk_i2s); + } + clks->clk_i2s_bypass = devm_clk_get(dev, "clk_i2s_bypass"); + if (IS_ERR(clks->clk_i2s_bypass)) { + dev_err(dev, "failed to get i2s bypass clk\n"); + return PTR_ERR(clks->clk_i2s_bypass); + } + return true; +} + +int imx8qm_pixel_clock_enable(struct hdp_clks *clks) +{ + struct imx_hdp *hdp = clks_to_imx_hdp(clks); + struct device *dev = hdp->dev; + int ret; + + ret = clk_prepare_enable(clks->av_pll); + if (ret < 0) { + dev_err(dev, "%s, pre av pll error\n", __func__); + return ret; + } + + ret = clk_prepare_enable(clks->clk_pxl); + if (ret < 0) { + dev_err(dev, "%s, pre clk pxl error\n", __func__); + return ret; + } + ret = clk_prepare_enable(clks->clk_pxl_mux); + if (ret < 0) { + dev_err(dev, "%s, pre clk pxl mux error\n", __func__); + return ret; + } + + ret = clk_prepare_enable(clks->clk_pxl_link); + if (ret < 0) { + dev_err(dev, "%s, pre clk pxl link error\n", __func__); + return ret; + } + ret = clk_prepare_enable(clks->clk_vif); + if (ret < 0) { + dev_err(dev, "%s, pre clk vif error\n", __func__); + return ret; + } + return ret; + +} + +void imx8qm_pixel_clock_disable(struct hdp_clks *clks) +{ + clk_disable_unprepare(clks->clk_vif); + clk_disable_unprepare(clks->clk_pxl); + clk_disable_unprepare(clks->clk_pxl_link); + clk_disable_unprepare(clks->clk_pxl_mux); + clk_disable_unprepare(clks->av_pll); +} + +void imx8qm_dp_pixel_clock_set_rate(struct hdp_clks *clks) +{ + struct imx_hdp *hdp = clks_to_imx_hdp(clks); + unsigned int pclock = hdp->video.cur_mode.clock * 1000; + + if (!hdp->is_digpll_dp_pclock) { + sc_err_t sci_err = 0; + sc_ipc_t ipc_handle = 0; + u32 mu_id; + + sci_err = sc_ipc_getMuID(&mu_id); + + if (sci_err != SC_ERR_NONE) + pr_err("Failed to get MU ID (%d)\n", sci_err); + sci_err = sc_ipc_open(&ipc_handle, mu_id); + + if (sci_err != SC_ERR_NONE) + pr_err("Failed to open IPC (%d)\n", sci_err); + + clk_set_rate(clks->av_pll, pclock); + + /* Enable the 24MHz for HDP PHY */ + sc_misc_set_control(ipc_handle, SC_R_HDMI, SC_C_MODE, 1); + + sc_ipc_close(ipc_handle); + } else + clk_set_rate(clks->av_pll, 24000000); + + if (hdp->dual_mode == true) { + clk_set_rate(clks->clk_pxl, pclock/2); + clk_set_rate(clks->clk_pxl_link, pclock/2); + } else { + clk_set_rate(clks->clk_pxl, pclock); + clk_set_rate(clks->clk_pxl_link, pclock); + } + clk_set_rate(clks->clk_pxl_mux, pclock); +} + +void imx8qm_hdmi_pixel_clock_set_rate(struct hdp_clks *clks) +{ + struct imx_hdp *hdp = clks_to_imx_hdp(clks); + unsigned int pclock = hdp->video.cur_mode.clock * 1000; + + /* pixel clock for HDMI */ + clk_set_rate(clks->av_pll, pclock); + + if (hdp->dual_mode == true) { + clk_set_rate(clks->clk_pxl, pclock/2); + clk_set_rate(clks->clk_pxl_link, pclock/2); + } else { + clk_set_rate(clks->clk_pxl_link, pclock); + clk_set_rate(clks->clk_pxl, pclock); + } + clk_set_rate(clks->clk_pxl_mux, pclock); +} + +int imx8qm_ipg_clock_enable(struct hdp_clks *clks) +{ + int ret; + struct imx_hdp *hdp = clks_to_imx_hdp(clks); + struct device *dev = hdp->dev; + + ret = clk_prepare_enable(clks->dig_pll); + if (ret < 0) { + dev_err(dev, "%s, pre dig pll error\n", __func__); + return ret; + } + + ret = clk_prepare_enable(clks->clk_ipg); + if (ret < 0) { + dev_err(dev, "%s, pre clk_ipg error\n", __func__); + return ret; + } + + ret = clk_prepare_enable(clks->clk_core); + if (ret < 0) { + dev_err(dev, "%s, pre clk core error\n", __func__); + return ret; + } + + ret = clk_prepare_enable(clks->clk_hdp); + if (ret < 0) { + dev_err(dev, "%s, pre clk hdp error\n", __func__); + return ret; + } + + ret = clk_prepare_enable(clks->clk_phy); + if (ret < 0) { + dev_err(dev, "%s, pre clk phy\n", __func__); + return ret; + } + + ret = clk_prepare_enable(clks->clk_apb); + if (ret < 0) { + dev_err(dev, "%s, pre clk apb error\n", __func__); + return ret; + } + ret = clk_prepare_enable(clks->clk_lis); + if (ret < 0) { + dev_err(dev, "%s, pre clk lis error\n", __func__); + return ret; + } + ret = clk_prepare_enable(clks->clk_lpcg); + if (ret < 0) { + dev_err(dev, "%s, pre clk lpcg error\n", __func__); + return ret; + } + ret = clk_prepare_enable(clks->clk_msi); + if (ret < 0) { + dev_err(dev, "%s, pre clk msierror\n", __func__); + return ret; + } + ret = clk_prepare_enable(clks->clk_even); + if (ret < 0) { + dev_err(dev, "%s, pre clk even error\n", __func__); + return ret; + } + ret = clk_prepare_enable(clks->clk_dbl); + if (ret < 0) { + dev_err(dev, "%s, pre clk dbl error\n", __func__); + return ret; + } + ret = clk_prepare_enable(clks->clk_apb_csr); + if (ret < 0) { + dev_err(dev, "%s, pre clk apb csr error\n", __func__); + return ret; + } + ret = clk_prepare_enable(clks->clk_apb_ctrl); + if (ret < 0) { + dev_err(dev, "%s, pre clk apb ctrl error\n", __func__); + return ret; + } + ret = clk_prepare_enable(clks->clk_i2s); + if (ret < 0) { + dev_err(dev, "%s, pre clk i2s error\n", __func__); + return ret; + } + ret = clk_prepare_enable(clks->clk_i2s_bypass); + if (ret < 0) { + dev_err(dev, "%s, pre clk i2s bypass error\n", __func__); + return ret; + } + return ret; +} + +void imx8qm_ipg_clock_disable(struct hdp_clks *clks) +{ +} + +void imx8qm_ipg_clock_set_rate(struct hdp_clks *clks) +{ + struct imx_hdp *hdp = clks_to_imx_hdp(clks); + u32 clk_rate, desired_rate; + + if (hdp->is_digpll_dp_pclock) + desired_rate = PLL_1188MHZ; + else + desired_rate = PLL_675MHZ; + + /* hdmi/dp ipg/core clock */ + clk_rate = clk_get_rate(clks->dig_pll); + + if (clk_rate != desired_rate) { + pr_warn("%s, dig_pll was %u MHz, changing to %u MHz\n", + __func__, clk_rate/1000000, + desired_rate/1000000); + } + + if (hdp->is_digpll_dp_pclock) { + clk_set_rate(clks->dig_pll, desired_rate); + clk_set_rate(clks->clk_core, desired_rate/10); + clk_set_rate(clks->clk_ipg, desired_rate/12); + clk_set_rate(clks->av_pll, 24000000); + } else { + clk_set_rate(clks->dig_pll, desired_rate); + clk_set_rate(clks->clk_core, desired_rate/5); + clk_set_rate(clks->clk_ipg, desired_rate/8); + } +} + +static u8 imx_hdp_link_rate(struct drm_display_mode *mode) +{ + if (mode->clock < 297000) + return AFE_LINK_RATE_1_6; + else if (mode->clock > 297000) + return AFE_LINK_RATE_5_4; + else + return AFE_LINK_RATE_2_7; +} + +static void imx_hdp_mode_setup(struct imx_hdp *hdp, struct drm_display_mode *mode) +{ + int ret; + + /* set pixel clock before video mode setup */ + imx_hdp_call(hdp, pixel_clock_disable, &hdp->clks); + + imx_hdp_call(hdp, pixel_clock_set_rate, &hdp->clks); + + imx_hdp_call(hdp, pixel_clock_enable, &hdp->clks); + + /* Config pixel link mux */ + imx_hdp_call(hdp, pixel_link_mux, &hdp->state, mode); + + hdp->link_rate = imx_hdp_link_rate(mode); + + /* mode set */ + ret = imx_hdp_call(hdp, phy_init, &hdp->state, mode, hdp->format, hdp->bpc); + if (ret < 0) { + DRM_ERROR("Failed to initialise HDP PHY\n"); + return; + } + imx_hdp_call(hdp, mode_set, &hdp->state, mode, + hdp->format, hdp->bpc, hdp->link_rate); + + /* Get vic of CEA-861 */ + hdp->vic = drm_match_cea_mode(mode); +} + +bool imx_hdp_bridge_mode_fixup(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct imx_hdp *hdp = bridge->driver_private; + struct drm_display_info *di = &hdp->connector.display_info; + int vic = drm_match_cea_mode(mode); + + if (vic < 0) + return false; + + if (vic == VIC_MODE_97_60Hz && + (di->color_formats & DRM_COLOR_FORMAT_YCRCB420) && + (di->hdmi.y420_dc_modes & DRM_EDID_YCBCR420_DC_30)) { + hdp->bpc = 10; + hdp->format = YCBCR_4_2_0; + return true; + } + + hdp->bpc = 8; + hdp->format = PXL_RGB; + + return true; +} + +static void imx_hdp_bridge_mode_set(struct drm_bridge *bridge, + struct drm_display_mode *orig_mode, + struct drm_display_mode *mode) +{ + struct imx_hdp *hdp = bridge->driver_private; + + mutex_lock(&hdp->mutex); + + memcpy(&hdp->video.cur_mode, mode, sizeof(hdp->video.cur_mode)); + imx_hdp_mode_setup(hdp, mode); + /* Store the display mode for plugin/DKMS poweron events */ + memcpy(&hdp->video.pre_mode, mode, sizeof(hdp->video.pre_mode)); + + mutex_unlock(&hdp->mutex); +} + +static void imx_hdp_bridge_disable(struct drm_bridge *bridge) +{ +} + +static void imx_hdp_bridge_enable(struct drm_bridge *bridge) +{ +} + +static enum drm_connector_status +imx_hdp_connector_detect(struct drm_connector *connector, bool force) +{ + struct imx_hdp *hdp = container_of(connector, + struct imx_hdp, connector); + int ret; + u8 hpd = 0xf; + + ret = imx_hdp_call(hdp, get_hpd_state, &hdp->state, &hpd); + if (ret > 0) + return connector_status_unknown; + + 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 imx_hdp_default_video_modes(struct drm_connector *connector) +{ + struct drm_display_mode *mode; + int i; + + for (i = 0; i < ARRAY_SIZE(edid_cea_modes); i++) { + mode = drm_mode_create(connector->dev); + if (!mode) + return -EINVAL; + drm_mode_copy(mode, &edid_cea_modes[i]); + mode->type |= DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + drm_mode_probed_add(connector, mode); + } + return i; +} + +static int imx_hdp_connector_get_modes(struct drm_connector *connector) +{ + struct imx_hdp *hdp = container_of(connector, struct imx_hdp, connector); + struct edid *edid; + int num_modes = 0; + + if (hdp->is_edid == true) { + edid = drm_do_get_edid(connector, hdp->ops->get_edid_block, &hdp->state); + if (edid) { + dev_dbg(hdp->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_mode_connector_update_edid_property(connector, edid); + num_modes = drm_add_edid_modes(connector, edid); + if (num_modes == 0) { + dev_dbg(hdp->dev, "Invalid edid, use default video modes\n"); + num_modes = imx_hdp_default_video_modes(connector); + } else + /* Store the ELD */ + drm_edid_to_eld(connector, edid); + kfree(edid); + } else { + dev_dbg(hdp->dev, "failed to get edid, use default video modes\n"); + num_modes = imx_hdp_default_video_modes(connector); + } + } else { + dev_dbg(hdp->dev, "No EDID function, use default video mode\n"); + num_modes = imx_hdp_default_video_modes(connector); + } + + return num_modes; +} + +static enum drm_mode_status +imx_hdp_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct imx_hdp *hdp = container_of(connector, struct imx_hdp, + connector); + enum drm_mode_status mode_status = MODE_OK; + struct drm_cmdline_mode *cmdline_mode; + int ret; + + cmdline_mode = &connector->cmdline_mode; + + /* cmdline mode is the max support video mode when edid disabled */ + if (!hdp->is_edid) { + if (cmdline_mode->xres != 0 && + cmdline_mode->xres < mode->hdisplay) + return MODE_BAD_HVALUE; + } + + if (hdp->is_4kp60 && mode->clock > 594000) + return MODE_CLOCK_HIGH; + else if (!hdp->is_4kp60 && mode->clock > 297000) + return MODE_CLOCK_HIGH; + + ret = imx_hdp_call(hdp, pixel_clock_range, mode); + if (ret == 0) { + DRM_DEBUG("pixel clock %d out of range\n", mode->clock); + return MODE_CLOCK_RANGE; + } + + /* 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 imx_hdp_connector_force(struct drm_connector *connector) +{ + struct imx_hdp *hdp = container_of(connector, struct imx_hdp, + connector); + + mutex_lock(&hdp->mutex); + hdp->force = connector->force; + mutex_unlock(&hdp->mutex); +} + +static int imx_hdp_set_property(struct drm_connector *connector, + struct drm_connector_state *state, + struct drm_property *property, uint64_t val) +{ + struct imx_hdp *hdp = container_of(connector, struct imx_hdp, + connector); + int ret; + union hdmi_infoframe frame; + struct hdr_static_metadata *hdr_metadata; + + if (state->hdr_source_metadata_blob_ptr && + state->hdr_source_metadata_blob_ptr->length && + hdp->ops->write_hdr_metadata) { + hdr_metadata = (struct hdr_static_metadata *) + state->hdr_source_metadata_blob_ptr->data; + + ret = drm_hdmi_infoframe_set_hdr_metadata(&frame.drm, + hdr_metadata); + + if (ret < 0) { + DRM_ERROR("could not set HDR metadata in infoframe\n"); + return ret; + } + + hdp->ops->write_hdr_metadata(&hdp->state, &frame); + } + + return 0; +} + +static const struct drm_connector_funcs imx_hdp_connector_funcs = { + .dpms = drm_atomic_helper_connector_dpms, + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = imx_hdp_connector_detect, + .destroy = drm_connector_cleanup, + .force = imx_hdp_connector_force, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .atomic_set_property = imx_hdp_set_property, + .set_property = drm_atomic_helper_connector_set_property, +}; + +static const struct drm_connector_helper_funcs imx_hdp_connector_helper_funcs = { + .get_modes = imx_hdp_connector_get_modes, + .mode_valid = imx_hdp_connector_mode_valid, +}; + +static const struct drm_bridge_funcs imx_hdp_bridge_funcs = { + .enable = imx_hdp_bridge_enable, + .disable = imx_hdp_bridge_disable, + .mode_set = imx_hdp_bridge_mode_set, + .mode_fixup = imx_hdp_bridge_mode_fixup, +}; + +static void imx_hdp_imx_encoder_disable(struct drm_encoder *encoder) +{ + struct imx_hdp *hdp = container_of(encoder, struct imx_hdp, encoder); + + imx_hdp_call(hdp, pixel_link_sync_ctrl_disable, &hdp->state); + imx_hdp_call(hdp, pixel_link_invalidate, &hdp->state); +} + +static void imx_hdp_imx_encoder_enable(struct drm_encoder *encoder) +{ + struct imx_hdp *hdp = container_of(encoder, struct imx_hdp, encoder); + union hdmi_infoframe frame; + struct hdr_static_metadata *hdr_metadata; + struct drm_connector_state *conn_state = hdp->connector.state; + int ret = 0; + + if (!hdp->ops->write_hdr_metadata) + goto out; + + if (hdp->hdr_metadata_present) { + hdr_metadata = (struct hdr_static_metadata *) + conn_state->hdr_source_metadata_blob_ptr->data; + + ret = drm_hdmi_infoframe_set_hdr_metadata(&frame.drm, + hdr_metadata); + } else { + hdr_metadata = devm_kzalloc(hdp->dev, + sizeof(struct hdr_static_metadata), + GFP_KERNEL); + hdr_metadata->eotf = 0; + + ret = drm_hdmi_infoframe_set_hdr_metadata(&frame.drm, + hdr_metadata); + + devm_kfree(hdp->dev, hdr_metadata); + } + + if (ret < 0) { + DRM_ERROR("could not set HDR metadata in infoframe\n"); + return; + } + + hdp->ops->write_hdr_metadata(&hdp->state, &frame); + +out: + imx_hdp_call(hdp, pixel_link_validate, &hdp->state); + imx_hdp_call(hdp, pixel_link_sync_ctrl_enable , &hdp->state); +} + +static int imx_hdp_imx_encoder_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc_state); + struct imx_hdp *hdp = container_of(encoder, struct imx_hdp, encoder); + + imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB101010_1X30; + + if (conn_state->hdr_metadata_changed && + conn_state->hdr_source_metadata_blob_ptr && + conn_state->hdr_source_metadata_blob_ptr->length) + hdp->hdr_metadata_present = true; + + return 0; +} + +static const struct drm_encoder_helper_funcs imx_hdp_imx_encoder_helper_funcs = { + .enable = imx_hdp_imx_encoder_enable, + .disable = imx_hdp_imx_encoder_disable, + .atomic_check = imx_hdp_imx_encoder_atomic_check, +}; + +static const struct drm_encoder_funcs imx_hdp_imx_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + +static int imx8mq_hdp_read(struct hdp_mem *mem, unsigned int addr, unsigned int *value) +{ + unsigned int temp; + void *tmp_addr = mem->regs_base + addr; + temp = __raw_readl((volatile unsigned int *)tmp_addr); + *value = temp; + return 0; +} + +static int imx8mq_hdp_write(struct hdp_mem *mem, unsigned int addr, unsigned int value) +{ + void *tmp_addr = mem->regs_base + addr; + + __raw_writel(value, (volatile unsigned int *)tmp_addr); + return 0; +} + +static int imx8mq_hdp_sread(struct hdp_mem *mem, unsigned int addr, unsigned int *value) +{ + unsigned int temp; + void *tmp_addr = mem->ss_base + addr; + temp = __raw_readl((volatile unsigned int *)tmp_addr); + *value = temp; + return 0; +} + +static int imx8mq_hdp_swrite(struct hdp_mem *mem, unsigned int addr, unsigned int value) +{ + void *tmp_addr = mem->ss_base + addr; + __raw_writel(value, (volatile unsigned int *)tmp_addr); + return 0; +} + +static int imx8qm_hdp_read(struct hdp_mem *mem, unsigned int addr, unsigned int *value) +{ + unsigned int temp; + void *tmp_addr = (addr & 0xfff) + mem->regs_base; + void *off_addr = 0x8 + mem->ss_base;; + + __raw_writel(addr >> 12, off_addr); + temp = __raw_readl((volatile unsigned int *)tmp_addr); + + *value = temp; + return 0; +} + +static int imx8qm_hdp_write(struct hdp_mem *mem, unsigned int addr, unsigned int value) +{ + void *tmp_addr = (addr & 0xfff) + mem->regs_base; + void *off_addr = 0x8 + mem->ss_base;; + + __raw_writel(addr >> 12, off_addr); + + __raw_writel(value, (volatile unsigned int *) tmp_addr); + + return 0; +} + +static int imx8qm_hdp_sread(struct hdp_mem *mem, unsigned int addr, unsigned int *value) +{ + unsigned int temp; + void *tmp_addr = (addr & 0xfff) + mem->regs_base; + void *off_addr = 0xc + mem->ss_base;; + + __raw_writel(addr >> 12, off_addr); + + temp = __raw_readl((volatile unsigned int *)tmp_addr); + *value = temp; + return 0; +} + +static int imx8qm_hdp_swrite(struct hdp_mem *mem, unsigned int addr, unsigned int value) +{ + void *tmp_addr = (addr & 0xfff) + mem->regs_base; + void *off_addr = 0xc + mem->ss_base; + + __raw_writel(addr >> 12, off_addr); + __raw_writel(value, (volatile unsigned int *)tmp_addr); + + return 0; +} + +static struct hdp_rw_func imx8qm_rw = { + .read_reg = imx8qm_hdp_read, + .write_reg = imx8qm_hdp_write, + .sread_reg = imx8qm_hdp_sread, + .swrite_reg = imx8qm_hdp_swrite, +}; + +static struct hdp_ops imx8qm_dp_ops = { +#ifdef DEBUG_FW_LOAD + .fw_load = dp_fw_load, +#endif + .fw_init = dp_fw_init, + .phy_init = dp_phy_init, + .mode_set = dp_mode_set, + .get_edid_block = dp_get_edid_block, + .get_hpd_state = dp_get_hpd_state, + + .phy_reset = imx8qm_phy_reset, + .pixel_link_validate = imx8qm_pixel_link_validate, + .pixel_link_invalidate = imx8qm_pixel_link_invalidate, + .pixel_link_sync_ctrl_enable = imx8qm_pixel_link_sync_ctrl_enable, + .pixel_link_sync_ctrl_disable = imx8qm_pixel_link_sync_ctrl_disable, + .pixel_link_mux = imx8qm_pixel_link_mux, + + .clock_init = imx8qm_clock_init, + .ipg_clock_set_rate = imx8qm_ipg_clock_set_rate, + .ipg_clock_enable = imx8qm_ipg_clock_enable, + .ipg_clock_disable = imx8qm_ipg_clock_disable, + .pixel_clock_set_rate = imx8qm_dp_pixel_clock_set_rate, + .pixel_clock_enable = imx8qm_pixel_clock_enable, + .pixel_clock_disable = imx8qm_pixel_clock_disable, +}; + +static struct hdp_ops imx8qm_hdmi_ops = { +#ifdef DEBUG_FW_LOAD + .fw_load = hdmi_fw_load, +#endif + .fw_init = hdmi_fw_init, + .phy_init = hdmi_phy_init_ss28fdsoi, + .mode_set = hdmi_mode_set_ss28fdsoi, + .get_edid_block = hdmi_get_edid_block, + .get_hpd_state = hdmi_get_hpd_state, + + .phy_reset = imx8qm_phy_reset, + .pixel_link_validate = imx8qm_pixel_link_validate, + .pixel_link_invalidate = imx8qm_pixel_link_invalidate, + .pixel_link_sync_ctrl_enable = imx8qm_pixel_link_sync_ctrl_enable, + .pixel_link_sync_ctrl_disable = imx8qm_pixel_link_sync_ctrl_disable, + .pixel_link_mux = imx8qm_pixel_link_mux, + + .clock_init = imx8qm_clock_init, + .ipg_clock_set_rate = imx8qm_ipg_clock_set_rate, + .ipg_clock_enable = imx8qm_ipg_clock_enable, + .ipg_clock_disable = imx8qm_ipg_clock_disable, + .pixel_clock_set_rate = imx8qm_hdmi_pixel_clock_set_rate, + .pixel_clock_enable = imx8qm_pixel_clock_enable, + .pixel_clock_disable = imx8qm_pixel_clock_disable, +}; + +static struct hdp_devtype imx8qm_dp_devtype = { + .is_edid = false, + .is_4kp60 = false, + .audio_type = CDN_DPTX, + .ops = &imx8qm_dp_ops, + .rw = &imx8qm_rw, +}; + +static struct hdp_devtype imx8qm_hdmi_devtype = { + .is_edid = false, + .is_4kp60 = false, + .audio_type = CDN_HDMITX_TYPHOON, + .ops = &imx8qm_hdmi_ops, + .rw = &imx8qm_rw, +}; + +static struct hdp_rw_func imx8mq_rw = { + .read_reg = imx8mq_hdp_read, + .write_reg = imx8mq_hdp_write, + .sread_reg = imx8mq_hdp_sread, + .swrite_reg = imx8mq_hdp_swrite, +}; + +static struct hdp_ops imx8mq_ops = { + .phy_init = hdmi_phy_init_t28hpc, + .mode_set = hdmi_mode_set_t28hpc, + .get_edid_block = hdmi_get_edid_block, + .get_hpd_state = hdmi_get_hpd_state, + .write_hdr_metadata = hdmi_write_hdr_metadata, + .pixel_clock_range = pixel_clock_range_t28hpc, +}; + +static struct hdp_devtype imx8mq_hdmi_devtype = { + .is_edid = true, + .is_4kp60 = true, + .audio_type = CDN_HDMITX_KIRAN, + .ops = &imx8mq_ops, + .rw = &imx8mq_rw, +}; + +static struct hdp_ops imx8mq_dp_ops = { + .phy_init = dp_phy_init_t28hpc, + .mode_set = dp_mode_set, + .get_edid_block = dp_get_edid_block, + .get_hpd_state = dp_get_hpd_state, + .phy_reset = imx8mq_phy_reset, +}; + +static struct hdp_devtype imx8mq_dp_devtype = { + .is_edid = true, + .is_4kp60 = true, + .audio_type = CDN_DPTX, + .ops = &imx8mq_dp_ops, + .rw = &imx8mq_rw, +}; + +static const struct of_device_id imx_hdp_dt_ids[] = { + { .compatible = "fsl,imx8qm-hdmi", .data = &imx8qm_hdmi_devtype}, + { .compatible = "fsl,imx8qm-dp", .data = &imx8qm_dp_devtype}, + { .compatible = "fsl,imx8mq-hdmi", .data = &imx8mq_hdmi_devtype}, + { .compatible = "fsl,imx8mq-dp", .data = &imx8mq_dp_devtype}, + { } +}; +MODULE_DEVICE_TABLE(of, imx_hdp_dt_ids); + +static void hotplug_work_func(struct work_struct *work) +{ + struct imx_hdp *hdp = container_of(work, struct imx_hdp, + hotplug_work.work); + struct drm_connector *connector = &hdp->connector; + + drm_helper_hpd_irq_event(connector->dev); + + if (connector->status == connector_status_connected) { + /* Cable Connected */ + /* For HDMI2.0 SCDC should setup again. + * So recovery pre video mode if it is 4Kp60 */ + if (drm_mode_equal(&hdp->video.pre_mode, &edid_cea_modes[3])) + imx_hdp_mode_setup(hdp, &hdp->video.pre_mode); + DRM_INFO("HDMI/DP Cable Plug In\n"); + enable_irq(hdp->irq[HPD_IRQ_OUT]); + } else if (connector->status == connector_status_disconnected) { + /* Cable Disconnedted */ + DRM_INFO("HDMI/DP Cable Plug Out\n"); + enable_irq(hdp->irq[HPD_IRQ_IN]); + } +} + +static irqreturn_t imx_hdp_irq_thread(int irq, void *data) +{ + struct imx_hdp *hdp = data; + + disable_irq_nosync(irq); + + mod_delayed_work(system_wq, &hdp->hotplug_work, + msecs_to_jiffies(HOTPLUG_DEBOUNCE_MS)); + + return IRQ_HANDLED; +} + +static int imx_hdp_imx_bind(struct device *dev, struct device *master, + void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct drm_device *drm = data; + struct imx_hdp *hdp; + const struct of_device_id *of_id = + of_match_device(imx_hdp_dt_ids, dev); + const struct hdp_devtype *devtype = of_id->data; + struct drm_encoder *encoder; + struct drm_bridge *bridge; + struct drm_connector *connector; + struct resource *res; + u8 hpd; + int ret; + + if (!pdev->dev.of_node) + return -ENODEV; + + hdp = devm_kzalloc(&pdev->dev, sizeof(*hdp), GFP_KERNEL); + if (!hdp) + return -ENOMEM; + + hdp->dev = &pdev->dev; + encoder = &hdp->encoder; + bridge = &hdp->bridge; + connector = &hdp->connector; + + mutex_init(&hdp->mutex); + + hdp->irq[HPD_IRQ_IN] = platform_get_irq_byname(pdev, "plug_in"); + if (hdp->irq[HPD_IRQ_IN] < 0) + dev_info(&pdev->dev, "No plug_in irq number\n"); + + hdp->irq[HPD_IRQ_OUT] = platform_get_irq_byname(pdev, "plug_out"); + if (hdp->irq[HPD_IRQ_OUT] < 0) + dev_info(&pdev->dev, "No plug_out irq number\n"); + + /* register map */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + hdp->mem.regs_base = devm_ioremap_resource(dev, res); + if (IS_ERR(hdp->mem.regs_base)) { + dev_err(dev, "Failed to get HDP CTRL base register\n"); + return -EINVAL; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + hdp->mem.ss_base = devm_ioremap_resource(dev, res); + if (IS_ERR(hdp->mem.ss_base)) { + dev_err(dev, "Failed to get HDP CRS base register\n"); + return -EINVAL; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 2); + hdp->mem.rst_base = devm_ioremap_resource(dev, res); + if (IS_ERR(hdp->mem.rst_base)) { + dev_warn(dev, "Failed to get HDP RESET base register\n"); + } + + hdp->is_cec = of_property_read_bool(pdev->dev.of_node, "fsl,cec"); + + hdp->is_digpll_dp_pclock = of_property_read_bool(pdev->dev.of_node, "fsl,use_digpll_pclock"); + + hdp->is_edp = of_property_read_bool(pdev->dev.of_node, "fsl,edp"); + + ret = of_property_read_u32(pdev->dev.of_node, + "lane_mapping", + &hdp->lane_mapping); + if (ret) { + hdp->lane_mapping = 0x1b; + dev_warn(dev, "Failed to get lane_mapping - using default\n"); + } + dev_info(dev, "lane_mapping 0x%02x\n", hdp->lane_mapping); + + ret = of_property_read_u32(pdev->dev.of_node, + "edp_link_rate", + &hdp->edp_link_rate); + if (ret) { + hdp->edp_link_rate = 0; + dev_warn(dev, "Failed to get dp_link_rate - using default\n"); + } + dev_info(dev, "edp_link_rate 0x%02x\n", hdp->edp_link_rate); + + ret = of_property_read_u32(pdev->dev.of_node, + "edp_num_lanes", + &hdp->edp_num_lanes); + if (ret) { + hdp->edp_num_lanes = 4; + dev_warn(dev, "Failed to get dp_num_lanes - using default\n"); + } + dev_info(dev, "dp_num_lanes 0x%02x\n", hdp->edp_num_lanes); + + hdp->is_edid = devtype->is_edid; + hdp->is_4kp60 = devtype->is_4kp60; + hdp->audio_type = devtype->audio_type; + hdp->ops = devtype->ops; + hdp->rw = devtype->rw; + hdp->bpc = 8; + hdp->format = PXL_RGB; + + /* HDP controller init */ + imx_hdp_state_init(hdp); + + hdp->link_rate = AFE_LINK_RATE_1_6; + + hdp->dual_mode = false; + + ret = imx_hdp_call(hdp, clock_init, &hdp->clks); + if (ret < 0) { + DRM_ERROR("Failed to initialize clock\n"); + return ret; + } + + imx_hdp_call(hdp, ipg_clock_set_rate, &hdp->clks); + + ret = imx_hdp_call(hdp, ipg_clock_enable, &hdp->clks); + if (ret < 0) { + DRM_ERROR("Failed to initialize IPG clock\n"); + return ret; + } + imx_hdp_call(hdp, pixel_clock_enable, &hdp->clks); + + imx_hdp_call(hdp, phy_reset, hdp->ipcHndl, &hdp->mem, 0); + + imx_hdp_call(hdp, fw_load, &hdp->state); + + ret = imx_hdp_call(hdp, fw_init, &hdp->state); + if (ret < 0) { + DRM_ERROR("Failed to initialise HDP firmware\n"); + return ret; + } + + /* Pixel Format - 1 RGB, 2 YCbCr 444, 3 YCbCr 420 */ + /* bpp (bits per subpixel) - 8 24bpp, 10 30bpp, 12 36bpp, 16 48bpp */ + /* default set hdmi to 1080p60 mode */ + ret = imx_hdp_call(hdp, phy_init, &hdp->state, &edid_cea_modes[2], + hdp->format, hdp->bpc); + if (ret < 0) { + DRM_ERROR("Failed to initialise HDP PHY\n"); + return ret; + } + + /* encoder */ + encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node); + /* + * If we failed to find the CRTC(s) which this encoder is + * supposed to be connected to, it's because the CRTC has + * not been registered yet. Defer probing, and hope that + * the required CRTC is added later. + */ + if (encoder->possible_crtcs == 0) + return -EPROBE_DEFER; + + /* encoder */ + drm_encoder_helper_add(encoder, &imx_hdp_imx_encoder_helper_funcs); + drm_encoder_init(drm, encoder, &imx_hdp_imx_encoder_funcs, + DRM_MODE_ENCODER_TMDS, NULL); + + /* bridge */ + bridge->encoder = encoder; + bridge->driver_private = hdp; + bridge->funcs = &imx_hdp_bridge_funcs; + ret = drm_bridge_attach(drm, bridge); + if (ret) { + DRM_ERROR("Failed to initialize bridge with drm\n"); + return -EINVAL; + } + + encoder->bridge = bridge; + hdp->connector.polled = DRM_CONNECTOR_POLL_HPD; + hdp->connector.ycbcr_420_allowed = true; + + /* connector */ + drm_connector_helper_add(connector, + &imx_hdp_connector_helper_funcs); + + drm_connector_init(drm, connector, + &imx_hdp_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA); + + drm_object_attach_property(&connector->base, + connector->dev->mode_config.hdr_source_metadata_property, 0); + + drm_mode_connector_attach_encoder(connector, encoder); + + dev_set_drvdata(dev, hdp); + + INIT_DELAYED_WORK(&hdp->hotplug_work, hotplug_work_func); + + /* Check cable states before enable irq */ + imx_hdp_call(hdp, get_hpd_state, &hdp->state, &hpd); + + /* Enable Hotplug Detect IRQ thread */ + if (hdp->irq[HPD_IRQ_IN] > 0) { + irq_set_status_flags(hdp->irq[HPD_IRQ_IN], IRQ_NOAUTOEN); + ret = devm_request_threaded_irq(dev, hdp->irq[HPD_IRQ_IN], + NULL, imx_hdp_irq_thread, + IRQF_ONESHOT, dev_name(dev), hdp); + if (ret) { + dev_err(&pdev->dev, "can't claim irq %d\n", + hdp->irq[HPD_IRQ_IN]); + goto err_irq; + } + /* Cable Disconnedted, enable Plug in IRQ */ + if (hpd == 0) + enable_irq(hdp->irq[HPD_IRQ_IN]); + } + if (hdp->irq[HPD_IRQ_OUT] > 0) { + irq_set_status_flags(hdp->irq[HPD_IRQ_OUT], IRQ_NOAUTOEN); + ret = devm_request_threaded_irq(dev, hdp->irq[HPD_IRQ_OUT], + NULL, imx_hdp_irq_thread, + IRQF_ONESHOT, dev_name(dev), hdp); + if (ret) { + dev_err(&pdev->dev, "can't claim irq %d\n", + hdp->irq[HPD_IRQ_OUT]); + goto err_irq; + } + /* Cable Connected, enable Plug out IRQ */ + if (hpd == 1) + enable_irq(hdp->irq[HPD_IRQ_OUT]); + } +#ifdef CONFIG_IMX_HDP_CEC + if (hdp->is_cec) { + imx_hdp_cec_init(hdp); + imx_cec_register(&hdp->cec); + } +#endif + + imx_hdp_register_audio_driver(dev); + + return 0; +err_irq: + drm_encoder_cleanup(encoder); + return ret; +} + +static void imx_hdp_imx_unbind(struct device *dev, struct device *master, + void *data) +{ + struct imx_hdp *hdp = dev_get_drvdata(dev); + +#ifdef CONFIG_IMX_HDP_CEC + if (hdp->is_cec) + imx_cec_unregister(&hdp->cec); +#endif + imx_hdp_call(hdp, pixel_clock_disable, &hdp->clks); + drm_bridge_detach(&hdp->bridge); +} + +static const struct component_ops imx_hdp_imx_ops = { + .bind = imx_hdp_imx_bind, + .unbind = imx_hdp_imx_unbind, +}; + +static int imx_hdp_imx_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &imx_hdp_imx_ops); +} + +static int imx_hdp_imx_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &imx_hdp_imx_ops); + + return 0; +} + +static struct platform_driver imx_hdp_imx_platform_driver = { + .probe = imx_hdp_imx_probe, + .remove = imx_hdp_imx_remove, + .driver = { + .name = "i.mx8-hdp", + .of_match_table = imx_hdp_dt_ids, + }, +}; + +module_platform_driver(imx_hdp_imx_platform_driver); + +MODULE_AUTHOR("Sandor Yu <Sandor.yu@nxp.com>"); +MODULE_DESCRIPTION("IMX8QM DP Display Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:dp-hdmi-imx"); |