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