blob: 5bb7fecd69b793aac3d55bb4e711e3baa200c138 [file] [log] [blame]
/*
* 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;
}
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);
}
int imx8qm_pixel_link_init(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;
}
/* config dpu1 di0 to hdmi/dp mode */
sc_misc_set_control(hdp->ipcHndl, SC_R_DC_0, SC_C_PXL_LINK_MST1_ADDR, 1);
sc_misc_set_control(hdp->ipcHndl, SC_R_DC_0, SC_C_PXL_LINK_MST1_VLD, 1);
sc_misc_set_control(hdp->ipcHndl, SC_R_DC_0, SC_C_SYNC_CTRL0, 1);
return true;
}
void imx8qm_pixel_link_deinit(state_struct *state)
{
struct imx_hdp *hdp = state_to_imx_hdp(state);
/* config dpu1 di0 to default mode */
sc_misc_set_control(hdp->ipcHndl, SC_R_DC_0, SC_C_PXL_LINK_MST1_ADDR, 0);
sc_misc_set_control(hdp->ipcHndl, SC_R_DC_0, SC_C_PXL_LINK_MST1_VLD, 0);
sc_misc_set_control(hdp->ipcHndl, SC_R_DC_0, SC_C_SYNC_CTRL0, 0);
sc_ipc_close(hdp->mu_id);
}
void imx8qm_phy_reset(sc_ipc_t ipcHndl, 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);
}
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 clk pxl 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;
/* 24MHz for DP */
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)
{
u32 clk_rate;
/* hdmi/dp ipg/core clock */
clk_rate = clk_get_rate(clks->dig_pll);
if (clk_rate == PLL_1188MHZ) {
clk_set_rate(clks->dig_pll, PLL_1188MHZ);
clk_set_rate(clks->clk_core, PLL_1188MHZ/10);
clk_set_rate(clks->clk_ipg, PLL_1188MHZ/14);
} else {
clk_set_rate(clks->dig_pll, PLL_675MHZ);
clk_set_rate(clks->clk_core, PLL_675MHZ/5);
clk_set_rate(clks->clk_ipg, PLL_675MHZ/8);
}
/* Set Default av pll clock */
clk_set_rate(clks->av_pll, 24000000);
}
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->bpc >= 10) {
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_connector_get_modes(struct drm_connector *connector)
{
struct drm_display_mode *mode;
struct imx_hdp *hdp = container_of(connector, struct imx_hdp, connector);
struct edid *edid;
int num_modes = 0;
int ret;
int i;
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);
ret = drm_add_edid_modes(connector, edid);
/* Store the ELD */
drm_edid_to_eld(connector, edid);
kfree(edid);
}
} else {
dev_dbg(hdp->dev, "failed to get edid\n");
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);
}
num_modes = i;
}
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;
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;
/* 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_property *property, uint64_t val)
{
struct imx_hdp *hdp = container_of(connector, struct imx_hdp,
connector);
int ret;
struct drm_connector_state *conn_state;
union hdmi_infoframe frame;
struct hdr_static_metadata *hdr_metadata;
ret = drm_atomic_helper_connector_set_property(connector,
property, val);
if (ret < 0)
return ret;
conn_state = connector->state;
if (conn_state->hdr_source_metadata_blob_ptr &&
conn_state->hdr_source_metadata_blob_ptr->length &&
hdp->ops->write_hdr_metadata) {
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);
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,
.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,
};
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)
{
}
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)
return;
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);
}
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_init = imx8qm_pixel_link_init,
.pixel_link_deinit = imx8qm_pixel_link_deinit,
.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,
.mode_set = hdmi_mode_set,
.get_edid_block = hdmi_get_edid_block,
.get_hpd_state = hdmi_get_hpd_state,
.phy_reset = imx8qm_phy_reset,
.pixel_link_init = imx8qm_pixel_link_init,
.pixel_link_deinit = imx8qm_pixel_link_deinit,
.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,
};
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 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},
{ }
};
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 */
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;
}
hdp->is_cec = of_property_read_bool(pdev->dev.of_node, "fsl,cec");
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, pixel_link_init, &hdp->state);
if (ret < 0) {
DRM_ERROR("Failed to initialize clock %d\n", ret);
return ret;
}
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, 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_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);
imx_hdp_call(hdp, pixel_link_deinit, &hdp->state);
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");