// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) STMicroelectronics SA 2014 * Author: Vincent Abriou <vincent.abriou@st.com> for STMicroelectronics. */ #include <drm/drm_print.h> #include "sti_hdmi_tx3g4c28phy.h" #define HDMI_SRZ_CFG 0x504 #define HDMI_SRZ_PLL_CFG 0x510 #define HDMI_SRZ_ICNTL 0x518 #define HDMI_SRZ_CALCODE_EXT 0x520 #define HDMI_SRZ_CFG_EN BIT(0) #define HDMI_SRZ_CFG_DISABLE_BYPASS_SINK_CURRENT BIT(1) #define HDMI_SRZ_CFG_EXTERNAL_DATA BIT(16) #define HDMI_SRZ_CFG_RBIAS_EXT BIT(17) #define HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION BIT(18) #define HDMI_SRZ_CFG_EN_BIASRES_DETECTION BIT(19) #define HDMI_SRZ_CFG_EN_SRC_TERMINATION BIT(24) #define HDMI_SRZ_CFG_INTERNAL_MASK (HDMI_SRZ_CFG_EN | \ HDMI_SRZ_CFG_DISABLE_BYPASS_SINK_CURRENT | \ HDMI_SRZ_CFG_EXTERNAL_DATA | \ HDMI_SRZ_CFG_RBIAS_EXT | \ HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION | \ HDMI_SRZ_CFG_EN_BIASRES_DETECTION | \ HDMI_SRZ_CFG_EN_SRC_TERMINATION) #define PLL_CFG_EN BIT(0) #define PLL_CFG_NDIV_SHIFT (8) #define PLL_CFG_IDF_SHIFT (16) #define PLL_CFG_ODF_SHIFT (24) #define ODF_DIV_1 (0) #define ODF_DIV_2 (1) #define ODF_DIV_4 (2) #define ODF_DIV_8 (3) #define HDMI_TIMEOUT_PLL_LOCK 50 /*milliseconds */ struct plldividers_s { uint32_t min; uint32_t max; uint32_t idf; uint32_t odf; }; /* * Functional specification recommended values */ #define NB_PLL_MODE 5 static struct plldividers_s plldividers[NB_PLL_MODE] = { {0, 20000000, 1, ODF_DIV_8}, {20000000, 42500000, 2, ODF_DIV_8}, {42500000, 85000000, 4, ODF_DIV_4}, {85000000, 170000000, 8, ODF_DIV_2}, {170000000, 340000000, 16, ODF_DIV_1} }; #define NB_HDMI_PHY_CONFIG 2 static struct hdmi_phy_config hdmiphy_config[NB_HDMI_PHY_CONFIG] = { {0, 250000000, {0x0, 0x0, 0x0, 0x0} }, {250000000, 300000000, {0x1110, 0x0, 0x0, 0x0} }, }; /** * sti_hdmi_tx3g4c28phy_start - Start hdmi phy macro cell tx3g4c28 * * @hdmi: pointer on the hdmi internal structure * * Return false if an error occur */ static bool sti_hdmi_tx3g4c28phy_start(struct sti_hdmi *hdmi) { u32 ckpxpll = hdmi->mode.clock * 1000; u32 val, tmdsck, idf, odf, pllctrl = 0; bool foundplldivides = false; int i; DRM_DEBUG_DRIVER("ckpxpll = %dHz\n", ckpxpll); for (i = 0; i < NB_PLL_MODE; i++) { if (ckpxpll >= plldividers[i].min && ckpxpll < plldividers[i].max) { idf = plldividers[i].idf; odf = plldividers[i].odf; foundplldivides = true; break; } } if (!foundplldivides) { DRM_ERROR("input TMDS clock speed (%d) not supported\n", ckpxpll); goto err; } /* Assuming no pixel repetition and 24bits color */ tmdsck = ckpxpll; pllctrl |= 40 << PLL_CFG_NDIV_SHIFT; if (tmdsck > 340000000) { DRM_ERROR("output TMDS clock (%d) out of range\n", tmdsck); goto err; } pllctrl |= idf << PLL_CFG_IDF_SHIFT; pllctrl |= odf << PLL_CFG_ODF_SHIFT; /* * Configure and power up the PHY PLL */ hdmi->event_received = false; DRM_DEBUG_DRIVER("pllctrl = 0x%x\n", pllctrl); hdmi_write(hdmi, (pllctrl | PLL_CFG_EN), HDMI_SRZ_PLL_CFG); /* wait PLL interrupt */ wait_event_interruptible_timeout(hdmi->wait_event, hdmi->event_received == true, msecs_to_jiffies (HDMI_TIMEOUT_PLL_LOCK)); if ((hdmi_read(hdmi, HDMI_STA) & HDMI_STA_DLL_LCK) == 0) { DRM_ERROR("hdmi phy pll not locked\n"); goto err; } DRM_DEBUG_DRIVER("got PHY PLL Lock\n"); val = (HDMI_SRZ_CFG_EN | HDMI_SRZ_CFG_EXTERNAL_DATA | HDMI_SRZ_CFG_EN_BIASRES_DETECTION | HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION); if (tmdsck > 165000000) val |= HDMI_SRZ_CFG_EN_SRC_TERMINATION; /* * To configure the source termination and pre-emphasis appropriately * for different high speed TMDS clock frequencies a phy configuration * table must be provided, tailored to the SoC and board combination. */ for (i = 0; i < NB_HDMI_PHY_CONFIG; i++) { if ((hdmiphy_config[i].min_tmds_freq <= tmdsck) && (hdmiphy_config[i].max_tmds_freq >= tmdsck)) { val |= (hdmiphy_config[i].config[0] & ~HDMI_SRZ_CFG_INTERNAL_MASK); hdmi_write(hdmi, val, HDMI_SRZ_CFG); val = hdmiphy_config[i].config[1]; hdmi_write(hdmi, val, HDMI_SRZ_ICNTL); val = hdmiphy_config[i].config[2]; hdmi_write(hdmi, val, HDMI_SRZ_CALCODE_EXT); DRM_DEBUG_DRIVER("serializer cfg 0x%x 0x%x 0x%x\n", hdmiphy_config[i].config[0], hdmiphy_config[i].config[1], hdmiphy_config[i].config[2]); return true; } } /* * Default, power up the serializer with no pre-emphasis or * output swing correction */ hdmi_write(hdmi, val, HDMI_SRZ_CFG); hdmi_write(hdmi, 0x0, HDMI_SRZ_ICNTL); hdmi_write(hdmi, 0x0, HDMI_SRZ_CALCODE_EXT); return true; err: return false; } /** * sti_hdmi_tx3g4c28phy_stop - Stop hdmi phy macro cell tx3g4c28 * * @hdmi: pointer on the hdmi internal structure */ static void sti_hdmi_tx3g4c28phy_stop(struct sti_hdmi *hdmi) { int val = 0; DRM_DEBUG_DRIVER("\n"); hdmi->event_received = false; val = HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION; val |= HDMI_SRZ_CFG_EN_BIASRES_DETECTION; hdmi_write(hdmi, val, HDMI_SRZ_CFG); hdmi_write(hdmi, 0, HDMI_SRZ_PLL_CFG); /* wait PLL interrupt */ wait_event_interruptible_timeout(hdmi->wait_event, hdmi->event_received == true, msecs_to_jiffies (HDMI_TIMEOUT_PLL_LOCK)); if (hdmi_read(hdmi, HDMI_STA) & HDMI_STA_DLL_LCK) DRM_ERROR("hdmi phy pll not well disabled\n"); } struct hdmi_phy_ops tx3g4c28phy_ops = { .start = sti_hdmi_tx3g4c28phy_start, .stop = sti_hdmi_tx3g4c28phy_stop, };