summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c
blob: 8e0ceb0ced337c22211e0004e652f55a9fb9b2c2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
/*
 * 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_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,
};