| /* |
| * Copyright (C) STMicroelectronics SA 2014 |
| * Author: Vincent Abriou <vincent.abriou@st.com> for STMicroelectronics. |
| * License terms: GNU General Public License (GPL), version 2 |
| */ |
| |
| #include "sti_hdmi_tx3g0c55phy.h" |
| |
| #define HDMI_SRZ_PLL_CFG 0x0504 |
| #define HDMI_SRZ_TAP_1 0x0508 |
| #define HDMI_SRZ_TAP_2 0x050C |
| #define HDMI_SRZ_TAP_3 0x0510 |
| #define HDMI_SRZ_CTRL 0x0514 |
| |
| #define HDMI_SRZ_PLL_CFG_POWER_DOWN BIT(0) |
| #define HDMI_SRZ_PLL_CFG_VCOR_SHIFT 1 |
| #define HDMI_SRZ_PLL_CFG_VCOR_425MHZ 0 |
| #define HDMI_SRZ_PLL_CFG_VCOR_850MHZ 1 |
| #define HDMI_SRZ_PLL_CFG_VCOR_1700MHZ 2 |
| #define HDMI_SRZ_PLL_CFG_VCOR_3000MHZ 3 |
| #define HDMI_SRZ_PLL_CFG_VCOR_MASK 3 |
| #define HDMI_SRZ_PLL_CFG_VCOR(x) (x << HDMI_SRZ_PLL_CFG_VCOR_SHIFT) |
| #define HDMI_SRZ_PLL_CFG_NDIV_SHIFT 8 |
| #define HDMI_SRZ_PLL_CFG_NDIV_MASK (0x1F << HDMI_SRZ_PLL_CFG_NDIV_SHIFT) |
| #define HDMI_SRZ_PLL_CFG_MODE_SHIFT 16 |
| #define HDMI_SRZ_PLL_CFG_MODE_13_5_MHZ 0x1 |
| #define HDMI_SRZ_PLL_CFG_MODE_25_2_MHZ 0x4 |
| #define HDMI_SRZ_PLL_CFG_MODE_27_MHZ 0x5 |
| #define HDMI_SRZ_PLL_CFG_MODE_33_75_MHZ 0x6 |
| #define HDMI_SRZ_PLL_CFG_MODE_40_5_MHZ 0x7 |
| #define HDMI_SRZ_PLL_CFG_MODE_54_MHZ 0x8 |
| #define HDMI_SRZ_PLL_CFG_MODE_67_5_MHZ 0x9 |
| #define HDMI_SRZ_PLL_CFG_MODE_74_25_MHZ 0xA |
| #define HDMI_SRZ_PLL_CFG_MODE_81_MHZ 0xB |
| #define HDMI_SRZ_PLL_CFG_MODE_82_5_MHZ 0xC |
| #define HDMI_SRZ_PLL_CFG_MODE_108_MHZ 0xD |
| #define HDMI_SRZ_PLL_CFG_MODE_148_5_MHZ 0xE |
| #define HDMI_SRZ_PLL_CFG_MODE_165_MHZ 0xF |
| #define HDMI_SRZ_PLL_CFG_MODE_MASK 0xF |
| #define HDMI_SRZ_PLL_CFG_MODE(x) (x << HDMI_SRZ_PLL_CFG_MODE_SHIFT) |
| |
| #define HDMI_SRZ_CTRL_POWER_DOWN (1 << 0) |
| #define HDMI_SRZ_CTRL_EXTERNAL_DATA_EN (1 << 1) |
| |
| /* sysconf registers */ |
| #define HDMI_REJECTION_PLL_CONFIGURATION 0x0858 /* SYSTEM_CONFIG2534 */ |
| #define HDMI_REJECTION_PLL_STATUS 0x0948 /* SYSTEM_CONFIG2594 */ |
| |
| #define REJECTION_PLL_HDMI_ENABLE_SHIFT 0 |
| #define REJECTION_PLL_HDMI_ENABLE_MASK (0x1 << REJECTION_PLL_HDMI_ENABLE_SHIFT) |
| #define REJECTION_PLL_HDMI_PDIV_SHIFT 24 |
| #define REJECTION_PLL_HDMI_PDIV_MASK (0x7 << REJECTION_PLL_HDMI_PDIV_SHIFT) |
| #define REJECTION_PLL_HDMI_NDIV_SHIFT 16 |
| #define REJECTION_PLL_HDMI_NDIV_MASK (0xFF << REJECTION_PLL_HDMI_NDIV_SHIFT) |
| #define REJECTION_PLL_HDMI_MDIV_SHIFT 8 |
| #define REJECTION_PLL_HDMI_MDIV_MASK (0xFF << REJECTION_PLL_HDMI_MDIV_SHIFT) |
| |
| #define REJECTION_PLL_HDMI_REJ_PLL_LOCK BIT(0) |
| |
| #define HDMI_TIMEOUT_PLL_LOCK 50 /*milliseconds */ |
| |
| /** |
| * pll mode structure |
| * |
| * A pointer to an array of these structures is passed to a TMDS (HDMI) output |
| * via the control interface to provide board and SoC specific |
| * configurations of the HDMI PHY. Each entry in the array specifies a hardware |
| * specific configuration for a given TMDS clock frequency range. The array |
| * should be terminated with an entry that has all fields set to zero. |
| * |
| * @min: Lower bound of TMDS clock frequency this entry applies to |
| * @max: Upper bound of TMDS clock frequency this entry applies to |
| * @mode: SoC specific register configuration |
| */ |
| struct pllmode { |
| u32 min; |
| u32 max; |
| u32 mode; |
| }; |
| |
| #define NB_PLL_MODE 7 |
| static struct pllmode pllmodes[NB_PLL_MODE] = { |
| {13500000, 13513500, HDMI_SRZ_PLL_CFG_MODE_13_5_MHZ}, |
| {25174800, 25200000, HDMI_SRZ_PLL_CFG_MODE_25_2_MHZ}, |
| {27000000, 27027000, HDMI_SRZ_PLL_CFG_MODE_27_MHZ}, |
| {54000000, 54054000, HDMI_SRZ_PLL_CFG_MODE_54_MHZ}, |
| {72000000, 74250000, HDMI_SRZ_PLL_CFG_MODE_74_25_MHZ}, |
| {108000000, 108108000, HDMI_SRZ_PLL_CFG_MODE_108_MHZ}, |
| {148351648, 297000000, HDMI_SRZ_PLL_CFG_MODE_148_5_MHZ} |
| }; |
| |
| #define NB_HDMI_PHY_CONFIG 5 |
| static struct hdmi_phy_config hdmiphy_config[NB_HDMI_PHY_CONFIG] = { |
| {0, 40000000, {0x00101010, 0x00101010, 0x00101010, 0x02} }, |
| {40000000, 140000000, {0x00111111, 0x00111111, 0x00111111, 0x02} }, |
| {140000000, 160000000, {0x00131313, 0x00101010, 0x00101010, 0x02} }, |
| {160000000, 250000000, {0x00131313, 0x00111111, 0x00111111, 0x03FE} }, |
| {250000000, 300000000, {0x00151515, 0x00101010, 0x00101010, 0x03FE} }, |
| }; |
| |
| #define PLL_CHANGE_DELAY 1 /* ms */ |
| |
| /** |
| * Disable the pll rejection |
| * |
| * @hdmi: pointer on the hdmi internal structure |
| * |
| * return true if the pll has been disabled |
| */ |
| static bool disable_pll_rejection(struct sti_hdmi *hdmi) |
| { |
| u32 val; |
| |
| DRM_DEBUG_DRIVER("\n"); |
| |
| val = readl(hdmi->syscfg + HDMI_REJECTION_PLL_CONFIGURATION); |
| val &= ~REJECTION_PLL_HDMI_ENABLE_MASK; |
| writel(val, hdmi->syscfg + HDMI_REJECTION_PLL_CONFIGURATION); |
| |
| msleep(PLL_CHANGE_DELAY); |
| val = readl(hdmi->syscfg + HDMI_REJECTION_PLL_STATUS); |
| |
| return !(val & REJECTION_PLL_HDMI_REJ_PLL_LOCK); |
| } |
| |
| /** |
| * Enable the old BCH/rejection PLL is now reused to provide the CLKPXPLL |
| * clock input to the new PHY PLL that generates the serializer clock |
| * (TMDS*10) and the TMDS clock which is now fed back into the HDMI |
| * formatter instead of the TMDS clock line from ClockGenB. |
| * |
| * @hdmi: pointer on the hdmi internal structure |
| * |
| * return true if pll has been correctly set |
| */ |
| static bool enable_pll_rejection(struct sti_hdmi *hdmi) |
| { |
| unsigned int inputclock; |
| u32 mdiv, ndiv, pdiv, val; |
| |
| DRM_DEBUG_DRIVER("\n"); |
| |
| if (!disable_pll_rejection(hdmi)) |
| return false; |
| |
| inputclock = hdmi->mode.clock * 1000; |
| |
| DRM_DEBUG_DRIVER("hdmi rejection pll input clock = %dHz\n", inputclock); |
| |
| |
| /* Power up the HDMI rejection PLL |
| * Note: On this SoC (stiH416) we are forced to have the input clock |
| * be equal to the HDMI pixel clock. |
| * |
| * The values here have been suggested by validation however they are |
| * still provisional and subject to change. |
| * |
| * PLLout = (Fin*Mdiv) / ((2 * Ndiv) / 2^Pdiv) |
| */ |
| if (inputclock < 50000000) { |
| /* |
| * For slower clocks we need to multiply more to keep the |
| * internal VCO frequency within the physical specification |
| * of the PLL. |
| */ |
| pdiv = 4; |
| ndiv = 240; |
| mdiv = 30; |
| } else { |
| pdiv = 2; |
| ndiv = 60; |
| mdiv = 30; |
| } |
| |
| val = readl(hdmi->syscfg + HDMI_REJECTION_PLL_CONFIGURATION); |
| |
| val &= ~(REJECTION_PLL_HDMI_PDIV_MASK | |
| REJECTION_PLL_HDMI_NDIV_MASK | |
| REJECTION_PLL_HDMI_MDIV_MASK | |
| REJECTION_PLL_HDMI_ENABLE_MASK); |
| |
| val |= (pdiv << REJECTION_PLL_HDMI_PDIV_SHIFT) | |
| (ndiv << REJECTION_PLL_HDMI_NDIV_SHIFT) | |
| (mdiv << REJECTION_PLL_HDMI_MDIV_SHIFT) | |
| (0x1 << REJECTION_PLL_HDMI_ENABLE_SHIFT); |
| |
| writel(val, hdmi->syscfg + HDMI_REJECTION_PLL_CONFIGURATION); |
| |
| msleep(PLL_CHANGE_DELAY); |
| val = readl(hdmi->syscfg + HDMI_REJECTION_PLL_STATUS); |
| |
| return (val & REJECTION_PLL_HDMI_REJ_PLL_LOCK); |
| } |
| |
| /** |
| * Start hdmi phy macro cell tx3g0c55 |
| * |
| * @hdmi: pointer on the hdmi internal structure |
| * |
| * Return false if an error occur |
| */ |
| static bool sti_hdmi_tx3g0c55phy_start(struct sti_hdmi *hdmi) |
| { |
| u32 ckpxpll = hdmi->mode.clock * 1000; |
| u32 val, tmdsck, freqvco, pllctrl = 0; |
| unsigned int i; |
| |
| if (!enable_pll_rejection(hdmi)) |
| return false; |
| |
| DRM_DEBUG_DRIVER("ckpxpll = %dHz\n", ckpxpll); |
| |
| /* Assuming no pixel repetition and 24bits color */ |
| tmdsck = ckpxpll; |
| pllctrl = 2 << HDMI_SRZ_PLL_CFG_NDIV_SHIFT; |
| |
| /* |
| * Setup the PLL mode parameter based on the ckpxpll. If we haven't got |
| * a clock frequency supported by one of the specific PLL modes then we |
| * will end up using the generic mode (0) which only supports a 10x |
| * multiplier, hence only 24bit color. |
| */ |
| for (i = 0; i < NB_PLL_MODE; i++) { |
| if (ckpxpll >= pllmodes[i].min && ckpxpll <= pllmodes[i].max) |
| pllctrl |= HDMI_SRZ_PLL_CFG_MODE(pllmodes[i].mode); |
| } |
| |
| freqvco = tmdsck * 10; |
| if (freqvco <= 425000000UL) |
| pllctrl |= HDMI_SRZ_PLL_CFG_VCOR(HDMI_SRZ_PLL_CFG_VCOR_425MHZ); |
| else if (freqvco <= 850000000UL) |
| pllctrl |= HDMI_SRZ_PLL_CFG_VCOR(HDMI_SRZ_PLL_CFG_VCOR_850MHZ); |
| else if (freqvco <= 1700000000UL) |
| pllctrl |= HDMI_SRZ_PLL_CFG_VCOR(HDMI_SRZ_PLL_CFG_VCOR_1700MHZ); |
| else if (freqvco <= 2970000000UL) |
| pllctrl |= HDMI_SRZ_PLL_CFG_VCOR(HDMI_SRZ_PLL_CFG_VCOR_3000MHZ); |
| else { |
| DRM_ERROR("PHY serializer clock out of range\n"); |
| goto err; |
| } |
| |
| /* |
| * Configure and power up the PHY PLL |
| */ |
| hdmi->event_received = false; |
| DRM_DEBUG_DRIVER("pllctrl = 0x%x\n", pllctrl); |
| hdmi_write(hdmi, pllctrl, 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"); |
| |
| /* |
| * 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_write(hdmi, val, HDMI_SRZ_TAP_1); |
| val = hdmiphy_config[i].config[1]; |
| hdmi_write(hdmi, val, HDMI_SRZ_TAP_2); |
| val = hdmiphy_config[i].config[2]; |
| hdmi_write(hdmi, val, HDMI_SRZ_TAP_3); |
| val = hdmiphy_config[i].config[3]; |
| val |= HDMI_SRZ_CTRL_EXTERNAL_DATA_EN; |
| val &= ~HDMI_SRZ_CTRL_POWER_DOWN; |
| hdmi_write(hdmi, val, HDMI_SRZ_CTRL); |
| |
| DRM_DEBUG_DRIVER("serializer cfg 0x%x 0x%x 0x%x 0x%x\n", |
| hdmiphy_config[i].config[0], |
| hdmiphy_config[i].config[1], |
| hdmiphy_config[i].config[2], |
| hdmiphy_config[i].config[3]); |
| return true; |
| } |
| } |
| |
| /* |
| * Default, power up the serializer with no pre-emphasis or source |
| * termination. |
| */ |
| hdmi_write(hdmi, 0x0, HDMI_SRZ_TAP_1); |
| hdmi_write(hdmi, 0x0, HDMI_SRZ_TAP_2); |
| hdmi_write(hdmi, 0x0, HDMI_SRZ_TAP_3); |
| hdmi_write(hdmi, HDMI_SRZ_CTRL_EXTERNAL_DATA_EN, HDMI_SRZ_CTRL); |
| |
| return true; |
| |
| err: |
| disable_pll_rejection(hdmi); |
| |
| return false; |
| } |
| |
| /** |
| * Stop hdmi phy macro cell tx3g0c55 |
| * |
| * @hdmi: pointer on the hdmi internal structure |
| */ |
| static void sti_hdmi_tx3g0c55phy_stop(struct sti_hdmi *hdmi) |
| { |
| DRM_DEBUG_DRIVER("\n"); |
| |
| hdmi->event_received = false; |
| |
| hdmi_write(hdmi, HDMI_SRZ_CTRL_POWER_DOWN, HDMI_SRZ_CTRL); |
| hdmi_write(hdmi, HDMI_SRZ_PLL_CFG_POWER_DOWN, 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"); |
| |
| disable_pll_rejection(hdmi); |
| } |
| |
| struct hdmi_phy_ops tx3g0c55phy_ops = { |
| .start = sti_hdmi_tx3g0c55phy_start, |
| .stop = sti_hdmi_tx3g0c55phy_stop, |
| }; |