| /* |
| * 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/mutex.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-hdcp.h" |
| #include "imx-hdcp-private.h" |
| #include "imx-dp.h" |
| #include "../imx-drm.h" |
| |
| #define B0_SILICON_ID 0x11 |
| |
| struct drm_display_mode *g_mode; |
| uint8_t g_default_mode = 3; |
| 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 inline bool imx_hdp_is_dual_mode(struct drm_display_mode *mode) |
| { |
| return (mode->clock > HDP_DUAL_MODE_MIN_PCLK_RATE || |
| mode->hdisplay > HDP_SINGLE_MODE_MAX_WIDTH) ? true : false; |
| } |
| |
| 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 = 0; |
| } |
| |
| #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 |
| |
| #ifdef DEBUG_FW_LOAD |
| void hdp_fw_load(state_struct *state) |
| { |
| DRM_INFO("loading hdmi firmware\n"); |
| CDN_API_LoadFirmware(state, |
| (u8 *)hdmitx_iram0_get_ptr(), |
| hdmitx_iram0_get_size(), |
| (u8 *)hdmitx_dram0_get_ptr(), |
| hdmitx_dram0_get_size()); |
| } |
| #endif |
| |
| int hdp_fw_check(state_struct *state) |
| { |
| u16 ver, verlib; |
| u8 echo_msg[] = "echo test"; |
| u8 echo_resp[sizeof(echo_msg) + 1]; |
| int ret; |
| |
| ret = CDN_API_General_Test_Echo_Ext_blocking(state, echo_msg, echo_resp, |
| sizeof(echo_msg), CDN_BUS_TYPE_APB); |
| |
| if (0 != strncmp(echo_msg, echo_resp, sizeof(echo_msg))) { |
| DRM_ERROR("CDN_API_General_Test_Echo_Ext_blocking - echo test failed, check firmware!"); |
| return -ENXIO; |
| } |
| DRM_INFO("CDN_API_General_Test_Echo_Ext_blocking - APB(ret = %d echo_resp = %s)\n", |
| ret, echo_resp); |
| |
| ret = CDN_API_General_getCurVersion(state, &ver, &verlib); |
| if (ret != 0) { |
| DRM_ERROR("CDN_API_General_getCurVersion - check firmware!\n"); |
| return -ENXIO; |
| } else |
| DRM_INFO("CDN_API_General_getCurVersion - ver %d verlib %d\n", |
| ver, verlib); |
| /* we can add a check here to reject older firmware |
| * versions if needed */ |
| |
| return 0; |
| } |
| |
| int hdp_fw_init(state_struct *state) |
| { |
| struct imx_hdp *hdp = state_to_imx_hdp(state); |
| u32 core_rate; |
| int ret; |
| u8 sts; |
| |
| core_rate = clk_get_rate(hdp->clks.clk_core); |
| |
| /* configure the clock */ |
| CDN_API_SetClock(state, core_rate/1000000); |
| pr_info("CDN_API_SetClock completed\n"); |
| |
| /* moved from CDN_API_LoadFirmware */ |
| cdn_apb_write(state, APB_CTRL << 2, 0); |
| DRM_INFO("Started firmware!\n"); |
| |
| ret = CDN_API_CheckAlive_blocking(state); |
| if (ret != 0) { |
| DRM_ERROR("CDN_API_CheckAlive failed - check firmware!\n"); |
| return -ENXIO; |
| } else |
| DRM_INFO("CDN_API_CheckAlive returned ret = %d\n", ret); |
| |
| /* turn on IP activity */ |
| ret = CDN_API_MainControl_blocking(state, 1, &sts); |
| DRM_INFO("CDN_API_MainControl_blocking ret = %d sts = %u\n", ret, sts); |
| |
| ret = hdp_fw_check(state); |
| if (ret != 0) { |
| DRM_ERROR("hdmi_fw_check failed!\n"); |
| return -ENXIO; |
| } |
| |
| if (hdp->is_dp) { |
| /* Line swaping - DP only */ |
| CDN_API_General_Write_Register_blocking(state, |
| ADDR_SOURCD_PHY + |
| (LANES_CONFIG << 2), |
| 0x00400000 | |
| hdp->dp_lane_mapping); |
| DRM_INFO("CDN_API_General_Write_* ... setting LANES_CONFIG\n"); |
| } |
| return 0; |
| } |
| |
| 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 = 0x4; /* RGB */ |
| if (hdp->dual_mode) |
| val |= 0x2; /* pixel link 0 and 1 are active */ |
| 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; |
| } |
| if (hdp->dual_mode) { |
| sciErr = sc_misc_set_control(hdp->ipcHndl, SC_R_DC_0, |
| SC_C_PXL_LINK_MST2_VLD, 1); |
| if (sciErr != SC_ERR_NONE) { |
| DRM_ERROR("SC_R_DC_0:SC_C_PXL_LINK_MST2_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; |
| } |
| if (hdp->dual_mode) { |
| sciErr = sc_misc_set_control(hdp->ipcHndl, SC_R_DC_0, |
| SC_C_PXL_LINK_MST2_VLD, 0); |
| if (sciErr != SC_ERR_NONE) { |
| DRM_ERROR("SC_R_DC_0:SC_C_PXL_LINK_MST2_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; |
| } |
| |
| if (hdp->dual_mode) { |
| sciErr = sc_misc_set_control(hdp->ipcHndl, SC_R_DC_0, SC_C_SYNC_CTRL, 3); |
| if (sciErr != SC_ERR_NONE) { |
| DRM_ERROR("SC_R_DC_0:SC_C_SYNC_CTRL sc_misc_set_control failed! (sciError = %d)\n", sciErr); |
| return -EINVAL; |
| } |
| } else { |
| 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; |
| } |
| |
| |
| if (hdp->dual_mode) { |
| sciErr = sc_misc_set_control(hdp->ipcHndl, SC_R_DC_0, SC_C_SYNC_CTRL, 0); |
| if (sciErr != SC_ERR_NONE) { |
| DRM_ERROR("SC_R_DC_0:SC_C_SYNC_CTRL sc_misc_set_control failed! (sciError = %d)\n", sciErr); |
| return -EINVAL; |
| } |
| } else { |
| 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_800MHZ; |
| |
| /* 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/4); |
| 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); |
| if (hdp->is_dp && hdp->link_rate > hdp->dp_link_rate) { |
| DRM_DEBUG("Lowering DP link rate from %d to %d\n", |
| hdp->link_rate, hdp->dp_link_rate); |
| hdp->link_rate = hdp->dp_link_rate; |
| } |
| |
| /* 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; |
| int vic = drm_match_cea_mode(mode); |
| |
| if (vic < 0) |
| return false; |
| |
| if (hdp && hdp->ops && hdp->ops->mode_fixup) |
| return hdp->ops->mode_fixup(&hdp->state, mode, adjusted_mode); |
| |
| 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); |
| |
| hdp->dual_mode = imx_hdp_is_dual_mode(mode); |
| |
| 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) |
| { |
| struct imx_hdp *hdp = bridge->driver_private; |
| |
| /* |
| * When switching from 10-bit to 8-bit color depths, iMX8MQ needs the |
| * PHY pixel engine to be reset after all clocks are ON, not before. |
| * So, we do it in the enable callback. |
| * |
| * Since the reset does not do any harm when switching from a 8-bit mode |
| * to another 8-bit mode, or from 8-bit to 10-bit, we can safely do it |
| * all the time. |
| */ |
| if (cpu_is_imx8mq()) |
| imx_hdp_call(hdp, pixel_engine_reset, &hdp->state); |
| } |
| |
| 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->no_edid) { |
| edid = drm_do_get_edid(connector, hdp->ops->get_edid_block, |
| &hdp->state); |
| if (edid) { |
| dev_info(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_warn(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_info(hdp->dev, "failed to get edid, use default video modes\n"); |
| num_modes = imx_hdp_default_video_modes(connector); |
| hdp->no_edid = true; |
| } |
| } else { |
| dev_warn(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 |
| * Add max support pixel rate to 297MHz in case no DDC function with no EDID */ |
| if (hdp->no_edid) { |
| if (cmdline_mode->xres != 0 && |
| cmdline_mode->xres < mode->hdisplay) |
| return MODE_BAD_HVALUE; |
| if (mode->clock > 297000) |
| return MODE_CLOCK_HIGH; |
| } |
| |
| /* For iMX8QM A0 Max support video mode is 4kp30 */ |
| if (cpu_is_imx8qm() && (imx8_get_soc_revision() < B0_SILICON_ID)) |
| if (mode->clock > 297000) |
| return MODE_CLOCK_HIGH; |
| |
| /* MAX support pixel clock rate 594MHz */ |
| if (mode->clock > 594000) |
| 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 int imx_hdp_connector_atomic_check(struct drm_connector *conn, |
| struct drm_connector_state *new_state) |
| { |
| struct drm_connector_state *old_state = |
| drm_atomic_get_old_connector_state(new_state->state, conn); |
| struct drm_crtc_state *crtc_state; |
| |
| imx_hdcp_atomic_check(conn, old_state, new_state); |
| |
| if (!new_state->crtc) |
| return 0; |
| |
| crtc_state = drm_atomic_get_new_crtc_state(new_state->state, |
| new_state->crtc); |
| |
| /* |
| * These properties are handled by fastset, and might not end up in a |
| * modeset. |
| */ |
| if (new_state->picture_aspect_ratio != |
| old_state->picture_aspect_ratio || |
| new_state->content_type != old_state->content_type || |
| new_state->scaling_mode != old_state->scaling_mode) |
| crtc_state->mode_changed = true; |
| |
| return 0; |
| } |
| |
| static const struct drm_connector_funcs imx_hdp_connector_funcs = { |
| .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, |
| }; |
| |
| 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, |
| .atomic_check = imx_hdp_connector_atomic_check, |
| |
| }; |
| |
| 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_hdcp_disable(hdp); |
| |
| 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 (conn_state->content_protection == |
| DRM_MODE_CONTENT_PROTECTION_DESIRED) |
| imx_hdcp_enable(hdp); |
| |
| 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; |
| |
| mutex_lock(&mem->mutex); |
| tmp_addr = mem->regs_base + addr; |
| temp = __raw_readl((volatile unsigned int *)tmp_addr); |
| *value = temp; |
| mutex_unlock(&mem->mutex); |
| return 0; |
| } |
| |
| static int imx8mq_hdp_write(struct hdp_mem *mem, |
| unsigned int addr, |
| unsigned int value) |
| { |
| void *tmp_addr; |
| |
| mutex_lock(&mem->mutex); |
| tmp_addr = mem->regs_base + addr; |
| __raw_writel(value, (volatile unsigned int *)tmp_addr); |
| mutex_unlock(&mem->mutex); |
| return 0; |
| } |
| |
| static int imx8mq_hdp_sread(struct hdp_mem *mem, |
| unsigned int addr, |
| unsigned int *value) |
| { |
| unsigned int temp; |
| void *tmp_addr; |
| |
| mutex_lock(&mem->mutex); |
| tmp_addr = mem->ss_base + addr; |
| temp = __raw_readl((volatile unsigned int *)tmp_addr); |
| *value = temp; |
| mutex_unlock(&mem->mutex); |
| return 0; |
| } |
| |
| static int imx8mq_hdp_swrite(struct hdp_mem *mem, |
| unsigned int addr, |
| unsigned int value) |
| { |
| void *tmp_addr; |
| |
| mutex_lock(&mem->mutex); |
| tmp_addr = mem->ss_base + addr; |
| __raw_writel(value, (volatile unsigned int *)tmp_addr); |
| mutex_unlock(&mem->mutex); |
| return 0; |
| } |
| |
| static int imx8qm_hdp_read(struct hdp_mem *mem, |
| unsigned int addr, |
| unsigned int *value) |
| { |
| unsigned int temp; |
| void *tmp_addr; |
| void *off_addr; |
| |
| mutex_lock(&mem->mutex); |
| tmp_addr = (addr & 0xfff) + mem->regs_base; |
| off_addr = 0x8 + mem->ss_base; |
| __raw_writel(addr >> 12, off_addr); |
| temp = __raw_readl((volatile unsigned int *)tmp_addr); |
| |
| *value = temp; |
| mutex_unlock(&mem->mutex); |
| return 0; |
| } |
| |
| static int imx8qm_hdp_write(struct hdp_mem *mem, |
| unsigned int addr, |
| unsigned int value) |
| { |
| void *tmp_addr; |
| void *off_addr; |
| |
| mutex_lock(&mem->mutex); |
| tmp_addr = (addr & 0xfff) + mem->regs_base; |
| off_addr = 0x8 + mem->ss_base; |
| __raw_writel(addr >> 12, off_addr); |
| |
| __raw_writel(value, (volatile unsigned int *) tmp_addr); |
| mutex_unlock(&mem->mutex); |
| |
| return 0; |
| } |
| |
| static int imx8qm_hdp_sread(struct hdp_mem *mem, |
| unsigned int addr, |
| unsigned int *value) |
| { |
| unsigned int temp; |
| void *tmp_addr; |
| void *off_addr; |
| |
| mutex_lock(&mem->mutex); |
| tmp_addr = (addr & 0xfff) + mem->regs_base; |
| off_addr = 0xc + mem->ss_base; |
| __raw_writel(addr >> 12, off_addr); |
| |
| temp = __raw_readl((volatile unsigned int *)tmp_addr); |
| *value = temp; |
| mutex_unlock(&mem->mutex); |
| return 0; |
| } |
| |
| static int imx8qm_hdp_swrite(struct hdp_mem *mem, |
| unsigned int addr, |
| unsigned int value) |
| { |
| void *tmp_addr; |
| void *off_addr; |
| |
| mutex_lock(&mem->mutex); |
| tmp_addr = (addr & 0xfff) + mem->regs_base; |
| off_addr = 0xc + mem->ss_base; |
| __raw_writel(addr >> 12, off_addr); |
| __raw_writel(value, (volatile unsigned int *)tmp_addr); |
| mutex_unlock(&mem->mutex); |
| |
| 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 = hdp_fw_load, |
| #endif |
| .fw_init = hdp_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 = hdp_fw_load, |
| #endif |
| .fw_init = hdp_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 = { |
| .audio_type = CDN_DPTX, |
| .ops = &imx8qm_dp_ops, |
| .rw = &imx8qm_rw, |
| .connector_type = DRM_MODE_CONNECTOR_DisplayPort, |
| }; |
| |
| static struct hdp_devtype imx8qm_hdmi_devtype = { |
| .audio_type = CDN_HDMITX_TYPHOON, |
| .ops = &imx8qm_hdmi_ops, |
| .rw = &imx8qm_rw, |
| .connector_type = DRM_MODE_CONNECTOR_HDMIA, |
| }; |
| |
| 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 = { |
| .fw_init = hdp_fw_check, |
| .phy_init = hdmi_phy_init_t28hpc, |
| .mode_set = hdmi_mode_set_t28hpc, |
| .mode_fixup = hdmi_mode_fixup_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, |
| .pixel_engine_reset = hdmi_phy_pix_engine_reset_t28hpc, |
| }; |
| |
| static struct hdp_devtype imx8mq_hdmi_devtype = { |
| .audio_type = CDN_HDMITX_KIRAN, |
| .ops = &imx8mq_ops, |
| .rw = &imx8mq_rw, |
| .connector_type = DRM_MODE_CONNECTOR_HDMIA, |
| |
| }; |
| |
| static struct hdp_ops imx8mq_dp_ops = { |
| .fw_init = hdp_fw_check, |
| .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 = { |
| .audio_type = CDN_DPTX, |
| .ops = &imx8mq_dp_ops, |
| .rw = &imx8mq_rw, |
| .connector_type = DRM_MODE_CONNECTOR_DisplayPort, |
| }; |
| |
| 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[g_default_mode])) |
| 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; |
| hdp->drm_dev = drm; |
| 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"); |
| |
| |
| mutex_init(&hdp->mem.mutex); |
| /* 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->no_edid = of_property_read_bool(pdev->dev.of_node, "fsl,no_edid"); |
| |
| /* EDID function is not supported by iMX8QM A0 */ |
| if (cpu_is_imx8qm() && (imx8_get_soc_revision() < B0_SILICON_ID)) |
| hdp->no_edid = true; |
| |
| if (devtype->connector_type == DRM_MODE_CONNECTOR_DisplayPort) { |
| hdp->is_dp = true; |
| hdp->is_edp = of_property_read_bool(pdev->dev.of_node, "fsl,edp"); |
| |
| ret = of_property_read_u32(pdev->dev.of_node, |
| "dp-lane-mapping", |
| &hdp->dp_lane_mapping); |
| if (ret) { |
| hdp->dp_lane_mapping = 0x1b; |
| dev_warn(dev, "Failed to get lane_mapping - using default\n"); |
| } |
| dev_info(dev, "dp-lane-mapping 0x%02x\n", hdp->dp_lane_mapping); |
| |
| ret = of_property_read_u32(pdev->dev.of_node, |
| "dp-link-rate", |
| &hdp->dp_link_rate); |
| if (ret) { |
| hdp->dp_link_rate = AFE_LINK_RATE_1_6; |
| hdp->link_rate = AFE_LINK_RATE_1_6; |
| dev_warn(dev, "Failed to get dp-link-rate - using default\n"); |
| } else |
| hdp->link_rate = hdp->dp_link_rate; |
| dev_info(dev, "dp-link-rate 0x%02x\n", hdp->dp_link_rate); |
| |
| ret = of_property_read_u32(pdev->dev.of_node, |
| "dp-num-lanes", |
| &hdp->dp_num_lanes); |
| if (ret) { |
| hdp->dp_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->dp_num_lanes); |
| |
| } |
| |
| 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->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_set_rate, &hdp->clks); |
| |
| 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[g_default_mode], |
| 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(encoder, bridge, NULL); |
| 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, |
| devtype->connector_type); |
| |
| 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); |
| |
| if (hdp->is_dp) { |
| dp_aux_init(&hdp->state, dev); |
| } |
| |
| ret = imx_hdcp_init(hdp, pdev->dev.of_node); |
| if (ret < 0) |
| DRM_WARN("Failed to initialize HDCP\n"); |
| |
| 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); |
| } |
| |
| 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"); |