|  | // SPDX-License-Identifier: GPL-2.0 | 
|  | /* | 
|  | * Copyright (C) STMicroelectronics SA 2014 | 
|  | * Author: Vincent Abriou <vincent.abriou@st.com> for STMicroelectronics. | 
|  | */ | 
|  |  | 
|  | #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} }, | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * 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; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * 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, | 
|  | }; |