15402626cSBenjamin Gaignard /*
25402626cSBenjamin Gaignard  * Copyright (C) STMicroelectronics SA 2014
35402626cSBenjamin Gaignard  * Author: Vincent Abriou <vincent.abriou@st.com> for STMicroelectronics.
45402626cSBenjamin Gaignard  * License terms:  GNU General Public License (GPL), version 2
55402626cSBenjamin Gaignard  */
65402626cSBenjamin Gaignard 
75402626cSBenjamin Gaignard #include "sti_hdmi_tx3g4c28phy.h"
85402626cSBenjamin Gaignard 
95402626cSBenjamin Gaignard #define HDMI_SRZ_CFG                             0x504
105402626cSBenjamin Gaignard #define HDMI_SRZ_PLL_CFG                         0x510
115402626cSBenjamin Gaignard #define HDMI_SRZ_ICNTL                           0x518
125402626cSBenjamin Gaignard #define HDMI_SRZ_CALCODE_EXT                     0x520
135402626cSBenjamin Gaignard 
145402626cSBenjamin Gaignard #define HDMI_SRZ_CFG_EN                          BIT(0)
155402626cSBenjamin Gaignard #define HDMI_SRZ_CFG_DISABLE_BYPASS_SINK_CURRENT BIT(1)
165402626cSBenjamin Gaignard #define HDMI_SRZ_CFG_EXTERNAL_DATA               BIT(16)
175402626cSBenjamin Gaignard #define HDMI_SRZ_CFG_RBIAS_EXT                   BIT(17)
185402626cSBenjamin Gaignard #define HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION      BIT(18)
195402626cSBenjamin Gaignard #define HDMI_SRZ_CFG_EN_BIASRES_DETECTION        BIT(19)
205402626cSBenjamin Gaignard #define HDMI_SRZ_CFG_EN_SRC_TERMINATION          BIT(24)
215402626cSBenjamin Gaignard 
225402626cSBenjamin Gaignard #define HDMI_SRZ_CFG_INTERNAL_MASK  (HDMI_SRZ_CFG_EN     | \
235402626cSBenjamin Gaignard 		HDMI_SRZ_CFG_DISABLE_BYPASS_SINK_CURRENT | \
245402626cSBenjamin Gaignard 		HDMI_SRZ_CFG_EXTERNAL_DATA               | \
255402626cSBenjamin Gaignard 		HDMI_SRZ_CFG_RBIAS_EXT                   | \
265402626cSBenjamin Gaignard 		HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION      | \
275402626cSBenjamin Gaignard 		HDMI_SRZ_CFG_EN_BIASRES_DETECTION        | \
285402626cSBenjamin Gaignard 		HDMI_SRZ_CFG_EN_SRC_TERMINATION)
295402626cSBenjamin Gaignard 
305402626cSBenjamin Gaignard #define PLL_CFG_EN                               BIT(0)
315402626cSBenjamin Gaignard #define PLL_CFG_NDIV_SHIFT                       (8)
325402626cSBenjamin Gaignard #define PLL_CFG_IDF_SHIFT                        (16)
335402626cSBenjamin Gaignard #define PLL_CFG_ODF_SHIFT                        (24)
345402626cSBenjamin Gaignard 
355402626cSBenjamin Gaignard #define ODF_DIV_1                                (0)
365402626cSBenjamin Gaignard #define ODF_DIV_2                                (1)
375402626cSBenjamin Gaignard #define ODF_DIV_4                                (2)
385402626cSBenjamin Gaignard #define ODF_DIV_8                                (3)
395402626cSBenjamin Gaignard 
405402626cSBenjamin Gaignard #define HDMI_TIMEOUT_PLL_LOCK  50  /*milliseconds */
415402626cSBenjamin Gaignard 
425402626cSBenjamin Gaignard struct plldividers_s {
435402626cSBenjamin Gaignard 	uint32_t min;
445402626cSBenjamin Gaignard 	uint32_t max;
455402626cSBenjamin Gaignard 	uint32_t idf;
465402626cSBenjamin Gaignard 	uint32_t odf;
475402626cSBenjamin Gaignard };
485402626cSBenjamin Gaignard 
495402626cSBenjamin Gaignard /*
505402626cSBenjamin Gaignard  * Functional specification recommended values
515402626cSBenjamin Gaignard  */
525402626cSBenjamin Gaignard #define NB_PLL_MODE 5
535402626cSBenjamin Gaignard static struct plldividers_s plldividers[NB_PLL_MODE] = {
545402626cSBenjamin Gaignard 	{0, 20000000, 1, ODF_DIV_8},
555402626cSBenjamin Gaignard 	{20000000, 42500000, 2, ODF_DIV_8},
565402626cSBenjamin Gaignard 	{42500000, 85000000, 4, ODF_DIV_4},
575402626cSBenjamin Gaignard 	{85000000, 170000000, 8, ODF_DIV_2},
585402626cSBenjamin Gaignard 	{170000000, 340000000, 16, ODF_DIV_1}
595402626cSBenjamin Gaignard };
605402626cSBenjamin Gaignard 
615402626cSBenjamin Gaignard #define NB_HDMI_PHY_CONFIG 2
625402626cSBenjamin Gaignard static struct hdmi_phy_config hdmiphy_config[NB_HDMI_PHY_CONFIG] = {
635402626cSBenjamin Gaignard 	{0, 250000000, {0x0, 0x0, 0x0, 0x0} },
645402626cSBenjamin Gaignard 	{250000000, 300000000, {0x1110, 0x0, 0x0, 0x0} },
655402626cSBenjamin Gaignard };
665402626cSBenjamin Gaignard 
675402626cSBenjamin Gaignard /**
685402626cSBenjamin Gaignard  * Start hdmi phy macro cell tx3g4c28
695402626cSBenjamin Gaignard  *
705402626cSBenjamin Gaignard  * @hdmi: pointer on the hdmi internal structure
715402626cSBenjamin Gaignard  *
725402626cSBenjamin Gaignard  * Return false if an error occur
735402626cSBenjamin Gaignard  */
745402626cSBenjamin Gaignard static bool sti_hdmi_tx3g4c28phy_start(struct sti_hdmi *hdmi)
755402626cSBenjamin Gaignard {
765402626cSBenjamin Gaignard 	u32 ckpxpll = hdmi->mode.clock * 1000;
775402626cSBenjamin Gaignard 	u32 val, tmdsck, idf, odf, pllctrl = 0;
785402626cSBenjamin Gaignard 	bool foundplldivides = false;
795402626cSBenjamin Gaignard 	int i;
805402626cSBenjamin Gaignard 
815402626cSBenjamin Gaignard 	DRM_DEBUG_DRIVER("ckpxpll = %dHz\n", ckpxpll);
825402626cSBenjamin Gaignard 
835402626cSBenjamin Gaignard 	for (i = 0; i < NB_PLL_MODE; i++) {
845402626cSBenjamin Gaignard 		if (ckpxpll >= plldividers[i].min &&
855402626cSBenjamin Gaignard 		    ckpxpll < plldividers[i].max) {
865402626cSBenjamin Gaignard 			idf = plldividers[i].idf;
875402626cSBenjamin Gaignard 			odf = plldividers[i].odf;
885402626cSBenjamin Gaignard 			foundplldivides = true;
895402626cSBenjamin Gaignard 			break;
905402626cSBenjamin Gaignard 		}
915402626cSBenjamin Gaignard 	}
925402626cSBenjamin Gaignard 
935402626cSBenjamin Gaignard 	if (!foundplldivides) {
945402626cSBenjamin Gaignard 		DRM_ERROR("input TMDS clock speed (%d) not supported\n",
955402626cSBenjamin Gaignard 			  ckpxpll);
965402626cSBenjamin Gaignard 		goto err;
975402626cSBenjamin Gaignard 	}
985402626cSBenjamin Gaignard 
995402626cSBenjamin Gaignard 	/* Assuming no pixel repetition and 24bits color */
1005402626cSBenjamin Gaignard 	tmdsck = ckpxpll;
1015402626cSBenjamin Gaignard 	pllctrl |= 40 << PLL_CFG_NDIV_SHIFT;
1025402626cSBenjamin Gaignard 
1035402626cSBenjamin Gaignard 	if (tmdsck > 340000000) {
1045402626cSBenjamin Gaignard 		DRM_ERROR("output TMDS clock (%d) out of range\n", tmdsck);
1055402626cSBenjamin Gaignard 		goto err;
1065402626cSBenjamin Gaignard 	}
1075402626cSBenjamin Gaignard 
1085402626cSBenjamin Gaignard 	pllctrl |= idf << PLL_CFG_IDF_SHIFT;
1095402626cSBenjamin Gaignard 	pllctrl |= odf << PLL_CFG_ODF_SHIFT;
1105402626cSBenjamin Gaignard 
1115402626cSBenjamin Gaignard 	/*
1125402626cSBenjamin Gaignard 	 * Configure and power up the PHY PLL
1135402626cSBenjamin Gaignard 	 */
1145402626cSBenjamin Gaignard 	hdmi->event_received = false;
1155402626cSBenjamin Gaignard 	DRM_DEBUG_DRIVER("pllctrl = 0x%x\n", pllctrl);
1165402626cSBenjamin Gaignard 	hdmi_write(hdmi, (pllctrl | PLL_CFG_EN), HDMI_SRZ_PLL_CFG);
1175402626cSBenjamin Gaignard 
1185402626cSBenjamin Gaignard 	/* wait PLL interrupt */
1195402626cSBenjamin Gaignard 	wait_event_interruptible_timeout(hdmi->wait_event,
1205402626cSBenjamin Gaignard 					 hdmi->event_received == true,
1215402626cSBenjamin Gaignard 					 msecs_to_jiffies
1225402626cSBenjamin Gaignard 					 (HDMI_TIMEOUT_PLL_LOCK));
1235402626cSBenjamin Gaignard 
1245402626cSBenjamin Gaignard 	if ((hdmi_read(hdmi, HDMI_STA) & HDMI_STA_DLL_LCK) == 0) {
1255402626cSBenjamin Gaignard 		DRM_ERROR("hdmi phy pll not locked\n");
1265402626cSBenjamin Gaignard 		goto err;
1275402626cSBenjamin Gaignard 	}
1285402626cSBenjamin Gaignard 
1295402626cSBenjamin Gaignard 	DRM_DEBUG_DRIVER("got PHY PLL Lock\n");
1305402626cSBenjamin Gaignard 
1315402626cSBenjamin Gaignard 	val = (HDMI_SRZ_CFG_EN |
1325402626cSBenjamin Gaignard 	       HDMI_SRZ_CFG_EXTERNAL_DATA |
1335402626cSBenjamin Gaignard 	       HDMI_SRZ_CFG_EN_BIASRES_DETECTION |
1345402626cSBenjamin Gaignard 	       HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION);
1355402626cSBenjamin Gaignard 
1365402626cSBenjamin Gaignard 	if (tmdsck > 165000000)
1375402626cSBenjamin Gaignard 		val |= HDMI_SRZ_CFG_EN_SRC_TERMINATION;
1385402626cSBenjamin Gaignard 
1395402626cSBenjamin Gaignard 	/*
1405402626cSBenjamin Gaignard 	 * To configure the source termination and pre-emphasis appropriately
1415402626cSBenjamin Gaignard 	 * for different high speed TMDS clock frequencies a phy configuration
1425402626cSBenjamin Gaignard 	 * table must be provided, tailored to the SoC and board combination.
1435402626cSBenjamin Gaignard 	 */
1445402626cSBenjamin Gaignard 	for (i = 0; i < NB_HDMI_PHY_CONFIG; i++) {
1455402626cSBenjamin Gaignard 		if ((hdmiphy_config[i].min_tmds_freq <= tmdsck) &&
1465402626cSBenjamin Gaignard 		    (hdmiphy_config[i].max_tmds_freq >= tmdsck)) {
1475402626cSBenjamin Gaignard 			val |= (hdmiphy_config[i].config[0]
1485402626cSBenjamin Gaignard 				& ~HDMI_SRZ_CFG_INTERNAL_MASK);
1495402626cSBenjamin Gaignard 			hdmi_write(hdmi, val, HDMI_SRZ_CFG);
1505402626cSBenjamin Gaignard 
1515402626cSBenjamin Gaignard 			val = hdmiphy_config[i].config[1];
1525402626cSBenjamin Gaignard 			hdmi_write(hdmi, val, HDMI_SRZ_ICNTL);
1535402626cSBenjamin Gaignard 
1545402626cSBenjamin Gaignard 			val = hdmiphy_config[i].config[2];
1555402626cSBenjamin Gaignard 			hdmi_write(hdmi, val, HDMI_SRZ_CALCODE_EXT);
1565402626cSBenjamin Gaignard 
1575402626cSBenjamin Gaignard 			DRM_DEBUG_DRIVER("serializer cfg 0x%x 0x%x 0x%x\n",
1585402626cSBenjamin Gaignard 					 hdmiphy_config[i].config[0],
1595402626cSBenjamin Gaignard 					 hdmiphy_config[i].config[1],
1605402626cSBenjamin Gaignard 					 hdmiphy_config[i].config[2]);
1615402626cSBenjamin Gaignard 			return true;
1625402626cSBenjamin Gaignard 		}
1635402626cSBenjamin Gaignard 	}
1645402626cSBenjamin Gaignard 
1655402626cSBenjamin Gaignard 	/*
1665402626cSBenjamin Gaignard 	 * Default, power up the serializer with no pre-emphasis or
1675402626cSBenjamin Gaignard 	 * output swing correction
1685402626cSBenjamin Gaignard 	 */
1695402626cSBenjamin Gaignard 	hdmi_write(hdmi, val,  HDMI_SRZ_CFG);
1705402626cSBenjamin Gaignard 	hdmi_write(hdmi, 0x0, HDMI_SRZ_ICNTL);
1715402626cSBenjamin Gaignard 	hdmi_write(hdmi, 0x0, HDMI_SRZ_CALCODE_EXT);
1725402626cSBenjamin Gaignard 
1735402626cSBenjamin Gaignard 	return true;
1745402626cSBenjamin Gaignard 
1755402626cSBenjamin Gaignard err:
1765402626cSBenjamin Gaignard 	return false;
1775402626cSBenjamin Gaignard }
1785402626cSBenjamin Gaignard 
1795402626cSBenjamin Gaignard /**
1805402626cSBenjamin Gaignard  * Stop hdmi phy macro cell tx3g4c28
1815402626cSBenjamin Gaignard  *
1825402626cSBenjamin Gaignard  * @hdmi: pointer on the hdmi internal structure
1835402626cSBenjamin Gaignard  */
1845402626cSBenjamin Gaignard static void sti_hdmi_tx3g4c28phy_stop(struct sti_hdmi *hdmi)
1855402626cSBenjamin Gaignard {
1865402626cSBenjamin Gaignard 	int val = 0;
1875402626cSBenjamin Gaignard 
1885402626cSBenjamin Gaignard 	DRM_DEBUG_DRIVER("\n");
1895402626cSBenjamin Gaignard 
1905402626cSBenjamin Gaignard 	hdmi->event_received = false;
1915402626cSBenjamin Gaignard 
1925402626cSBenjamin Gaignard 	val = HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION;
1935402626cSBenjamin Gaignard 	val |= HDMI_SRZ_CFG_EN_BIASRES_DETECTION;
1945402626cSBenjamin Gaignard 
1955402626cSBenjamin Gaignard 	hdmi_write(hdmi, val, HDMI_SRZ_CFG);
1965402626cSBenjamin Gaignard 	hdmi_write(hdmi, 0, HDMI_SRZ_PLL_CFG);
1975402626cSBenjamin Gaignard 
1985402626cSBenjamin Gaignard 	/* wait PLL interrupt */
1995402626cSBenjamin Gaignard 	wait_event_interruptible_timeout(hdmi->wait_event,
2005402626cSBenjamin Gaignard 					 hdmi->event_received == true,
2015402626cSBenjamin Gaignard 					 msecs_to_jiffies
2025402626cSBenjamin Gaignard 					 (HDMI_TIMEOUT_PLL_LOCK));
2035402626cSBenjamin Gaignard 
2045402626cSBenjamin Gaignard 	if (hdmi_read(hdmi, HDMI_STA) & HDMI_STA_DLL_LCK)
2055402626cSBenjamin Gaignard 		DRM_ERROR("hdmi phy pll not well disabled\n");
2065402626cSBenjamin Gaignard }
2075402626cSBenjamin Gaignard 
2085402626cSBenjamin Gaignard struct hdmi_phy_ops tx3g4c28phy_ops = {
2095402626cSBenjamin Gaignard 	.start = sti_hdmi_tx3g4c28phy_start,
2105402626cSBenjamin Gaignard 	.stop = sti_hdmi_tx3g4c28phy_stop,
2115402626cSBenjamin Gaignard };
212