Commit | Line | Data |
---|---|---|
5402626c BG |
1 | /* |
2 | * Copyright (C) STMicroelectronics SA 2014 | |
3 | * Author: Vincent Abriou <vincent.abriou@st.com> for STMicroelectronics. | |
4 | * License terms: GNU General Public License (GPL), version 2 | |
5 | */ | |
6 | ||
7 | #include "sti_hdmi_tx3g4c28phy.h" | |
8 | ||
9 | #define HDMI_SRZ_CFG 0x504 | |
10 | #define HDMI_SRZ_PLL_CFG 0x510 | |
11 | #define HDMI_SRZ_ICNTL 0x518 | |
12 | #define HDMI_SRZ_CALCODE_EXT 0x520 | |
13 | ||
14 | #define HDMI_SRZ_CFG_EN BIT(0) | |
15 | #define HDMI_SRZ_CFG_DISABLE_BYPASS_SINK_CURRENT BIT(1) | |
16 | #define HDMI_SRZ_CFG_EXTERNAL_DATA BIT(16) | |
17 | #define HDMI_SRZ_CFG_RBIAS_EXT BIT(17) | |
18 | #define HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION BIT(18) | |
19 | #define HDMI_SRZ_CFG_EN_BIASRES_DETECTION BIT(19) | |
20 | #define HDMI_SRZ_CFG_EN_SRC_TERMINATION BIT(24) | |
21 | ||
22 | #define HDMI_SRZ_CFG_INTERNAL_MASK (HDMI_SRZ_CFG_EN | \ | |
23 | HDMI_SRZ_CFG_DISABLE_BYPASS_SINK_CURRENT | \ | |
24 | HDMI_SRZ_CFG_EXTERNAL_DATA | \ | |
25 | HDMI_SRZ_CFG_RBIAS_EXT | \ | |
26 | HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION | \ | |
27 | HDMI_SRZ_CFG_EN_BIASRES_DETECTION | \ | |
28 | HDMI_SRZ_CFG_EN_SRC_TERMINATION) | |
29 | ||
30 | #define PLL_CFG_EN BIT(0) | |
31 | #define PLL_CFG_NDIV_SHIFT (8) | |
32 | #define PLL_CFG_IDF_SHIFT (16) | |
33 | #define PLL_CFG_ODF_SHIFT (24) | |
34 | ||
35 | #define ODF_DIV_1 (0) | |
36 | #define ODF_DIV_2 (1) | |
37 | #define ODF_DIV_4 (2) | |
38 | #define ODF_DIV_8 (3) | |
39 | ||
40 | #define HDMI_TIMEOUT_PLL_LOCK 50 /*milliseconds */ | |
41 | ||
42 | struct plldividers_s { | |
43 | uint32_t min; | |
44 | uint32_t max; | |
45 | uint32_t idf; | |
46 | uint32_t odf; | |
47 | }; | |
48 | ||
49 | /* | |
50 | * Functional specification recommended values | |
51 | */ | |
52 | #define NB_PLL_MODE 5 | |
53 | static struct plldividers_s plldividers[NB_PLL_MODE] = { | |
54 | {0, 20000000, 1, ODF_DIV_8}, | |
55 | {20000000, 42500000, 2, ODF_DIV_8}, | |
56 | {42500000, 85000000, 4, ODF_DIV_4}, | |
57 | {85000000, 170000000, 8, ODF_DIV_2}, | |
58 | {170000000, 340000000, 16, ODF_DIV_1} | |
59 | }; | |
60 | ||
61 | #define NB_HDMI_PHY_CONFIG 2 | |
62 | static struct hdmi_phy_config hdmiphy_config[NB_HDMI_PHY_CONFIG] = { | |
63 | {0, 250000000, {0x0, 0x0, 0x0, 0x0} }, | |
64 | {250000000, 300000000, {0x1110, 0x0, 0x0, 0x0} }, | |
65 | }; | |
66 | ||
67 | /** | |
68 | * Start hdmi phy macro cell tx3g4c28 | |
69 | * | |
70 | * @hdmi: pointer on the hdmi internal structure | |
71 | * | |
72 | * Return false if an error occur | |
73 | */ | |
74 | static bool sti_hdmi_tx3g4c28phy_start(struct sti_hdmi *hdmi) | |
75 | { | |
76 | u32 ckpxpll = hdmi->mode.clock * 1000; | |
77 | u32 val, tmdsck, idf, odf, pllctrl = 0; | |
78 | bool foundplldivides = false; | |
79 | int i; | |
80 | ||
81 | DRM_DEBUG_DRIVER("ckpxpll = %dHz\n", ckpxpll); | |
82 | ||
83 | for (i = 0; i < NB_PLL_MODE; i++) { | |
84 | if (ckpxpll >= plldividers[i].min && | |
85 | ckpxpll < plldividers[i].max) { | |
86 | idf = plldividers[i].idf; | |
87 | odf = plldividers[i].odf; | |
88 | foundplldivides = true; | |
89 | break; | |
90 | } | |
91 | } | |
92 | ||
93 | if (!foundplldivides) { | |
94 | DRM_ERROR("input TMDS clock speed (%d) not supported\n", | |
95 | ckpxpll); | |
96 | goto err; | |
97 | } | |
98 | ||
99 | /* Assuming no pixel repetition and 24bits color */ | |
100 | tmdsck = ckpxpll; | |
101 | pllctrl |= 40 << PLL_CFG_NDIV_SHIFT; | |
102 | ||
103 | if (tmdsck > 340000000) { | |
104 | DRM_ERROR("output TMDS clock (%d) out of range\n", tmdsck); | |
105 | goto err; | |
106 | } | |
107 | ||
108 | pllctrl |= idf << PLL_CFG_IDF_SHIFT; | |
109 | pllctrl |= odf << PLL_CFG_ODF_SHIFT; | |
110 | ||
111 | /* | |
112 | * Configure and power up the PHY PLL | |
113 | */ | |
114 | hdmi->event_received = false; | |
115 | DRM_DEBUG_DRIVER("pllctrl = 0x%x\n", pllctrl); | |
116 | hdmi_write(hdmi, (pllctrl | PLL_CFG_EN), HDMI_SRZ_PLL_CFG); | |
117 | ||
118 | /* wait PLL interrupt */ | |
119 | wait_event_interruptible_timeout(hdmi->wait_event, | |
120 | hdmi->event_received == true, | |
121 | msecs_to_jiffies | |
122 | (HDMI_TIMEOUT_PLL_LOCK)); | |
123 | ||
124 | if ((hdmi_read(hdmi, HDMI_STA) & HDMI_STA_DLL_LCK) == 0) { | |
125 | DRM_ERROR("hdmi phy pll not locked\n"); | |
126 | goto err; | |
127 | } | |
128 | ||
129 | DRM_DEBUG_DRIVER("got PHY PLL Lock\n"); | |
130 | ||
131 | val = (HDMI_SRZ_CFG_EN | | |
132 | HDMI_SRZ_CFG_EXTERNAL_DATA | | |
133 | HDMI_SRZ_CFG_EN_BIASRES_DETECTION | | |
134 | HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION); | |
135 | ||
136 | if (tmdsck > 165000000) | |
137 | val |= HDMI_SRZ_CFG_EN_SRC_TERMINATION; | |
138 | ||
139 | /* | |
140 | * To configure the source termination and pre-emphasis appropriately | |
141 | * for different high speed TMDS clock frequencies a phy configuration | |
142 | * table must be provided, tailored to the SoC and board combination. | |
143 | */ | |
144 | for (i = 0; i < NB_HDMI_PHY_CONFIG; i++) { | |
145 | if ((hdmiphy_config[i].min_tmds_freq <= tmdsck) && | |
146 | (hdmiphy_config[i].max_tmds_freq >= tmdsck)) { | |
147 | val |= (hdmiphy_config[i].config[0] | |
148 | & ~HDMI_SRZ_CFG_INTERNAL_MASK); | |
149 | hdmi_write(hdmi, val, HDMI_SRZ_CFG); | |
150 | ||
151 | val = hdmiphy_config[i].config[1]; | |
152 | hdmi_write(hdmi, val, HDMI_SRZ_ICNTL); | |
153 | ||
154 | val = hdmiphy_config[i].config[2]; | |
155 | hdmi_write(hdmi, val, HDMI_SRZ_CALCODE_EXT); | |
156 | ||
157 | DRM_DEBUG_DRIVER("serializer cfg 0x%x 0x%x 0x%x\n", | |
158 | hdmiphy_config[i].config[0], | |
159 | hdmiphy_config[i].config[1], | |
160 | hdmiphy_config[i].config[2]); | |
161 | return true; | |
162 | } | |
163 | } | |
164 | ||
165 | /* | |
166 | * Default, power up the serializer with no pre-emphasis or | |
167 | * output swing correction | |
168 | */ | |
169 | hdmi_write(hdmi, val, HDMI_SRZ_CFG); | |
170 | hdmi_write(hdmi, 0x0, HDMI_SRZ_ICNTL); | |
171 | hdmi_write(hdmi, 0x0, HDMI_SRZ_CALCODE_EXT); | |
172 | ||
173 | return true; | |
174 | ||
175 | err: | |
176 | return false; | |
177 | } | |
178 | ||
179 | /** | |
180 | * Stop hdmi phy macro cell tx3g4c28 | |
181 | * | |
182 | * @hdmi: pointer on the hdmi internal structure | |
183 | */ | |
184 | static void sti_hdmi_tx3g4c28phy_stop(struct sti_hdmi *hdmi) | |
185 | { | |
186 | int val = 0; | |
187 | ||
188 | DRM_DEBUG_DRIVER("\n"); | |
189 | ||
190 | hdmi->event_received = false; | |
191 | ||
192 | val = HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION; | |
193 | val |= HDMI_SRZ_CFG_EN_BIASRES_DETECTION; | |
194 | ||
195 | hdmi_write(hdmi, val, HDMI_SRZ_CFG); | |
196 | hdmi_write(hdmi, 0, HDMI_SRZ_PLL_CFG); | |
197 | ||
198 | /* wait PLL interrupt */ | |
199 | wait_event_interruptible_timeout(hdmi->wait_event, | |
200 | hdmi->event_received == true, | |
201 | msecs_to_jiffies | |
202 | (HDMI_TIMEOUT_PLL_LOCK)); | |
203 | ||
204 | if (hdmi_read(hdmi, HDMI_STA) & HDMI_STA_DLL_LCK) | |
205 | DRM_ERROR("hdmi phy pll not well disabled\n"); | |
206 | } | |
207 | ||
208 | struct hdmi_phy_ops tx3g4c28phy_ops = { | |
209 | .start = sti_hdmi_tx3g4c28phy_start, | |
210 | .stop = sti_hdmi_tx3g4c28phy_stop, | |
211 | }; |