Commit | Line | Data |
---|---|---|
2c6d1fff HV |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * DisplayPort CEC-Tunneling-over-AUX support | |
4 | * | |
5 | * Copyright 2018 Cisco Systems, Inc. and/or its affiliates. All rights reserved. | |
6 | */ | |
7 | ||
8 | #include <linux/kernel.h> | |
9 | #include <linux/module.h> | |
10 | #include <linux/slab.h> | |
11 | #include <drm/drm_dp_helper.h> | |
12 | #include <media/cec.h> | |
13 | ||
14 | /* | |
15 | * Unfortunately it turns out that we have a chicken-and-egg situation | |
16 | * here. Quite a few active (mini-)DP-to-HDMI or USB-C-to-HDMI adapters | |
17 | * have a converter chip that supports CEC-Tunneling-over-AUX (usually the | |
18 | * Parade PS176), but they do not wire up the CEC pin, thus making CEC | |
9bcf6d98 HV |
19 | * useless. Note that MegaChips 2900-based adapters appear to have good |
20 | * support for CEC tunneling. Those adapters that I have tested using | |
21 | * this chipset all have the CEC line connected. | |
2c6d1fff HV |
22 | * |
23 | * Sadly there is no way for this driver to know this. What happens is | |
24 | * that a /dev/cecX device is created that is isolated and unable to see | |
25 | * any of the other CEC devices. Quite literally the CEC wire is cut | |
26 | * (or in this case, never connected in the first place). | |
27 | * | |
28 | * The reason so few adapters support this is that this tunneling protocol | |
29 | * was never supported by any OS. So there was no easy way of testing it, | |
30 | * and no incentive to correctly wire up the CEC pin. | |
31 | * | |
32 | * Hopefully by creating this driver it will be easier for vendors to | |
33 | * finally fix their adapters and test the CEC functionality. | |
34 | * | |
35 | * I keep a list of known working adapters here: | |
36 | * | |
37 | * https://hverkuil.home.xs4all.nl/cec-status.txt | |
38 | * | |
39 | * Please mail me (hverkuil@xs4all.nl) if you find an adapter that works | |
40 | * and is not yet listed there. | |
41 | * | |
42 | * Note that the current implementation does not support CEC over an MST hub. | |
43 | * As far as I can see there is no mechanism defined in the DisplayPort | |
44 | * standard to transport CEC interrupts over an MST device. It might be | |
45 | * possible to do this through polling, but I have not been able to get that | |
46 | * to work. | |
47 | */ | |
48 | ||
49 | /** | |
50 | * DOC: dp cec helpers | |
51 | * | |
52 | * These functions take care of supporting the CEC-Tunneling-over-AUX | |
53 | * feature of DisplayPort-to-HDMI adapters. | |
54 | */ | |
55 | ||
56 | /* | |
57 | * When the EDID is unset because the HPD went low, then the CEC DPCD registers | |
58 | * typically can no longer be read (true for a DP-to-HDMI adapter since it is | |
59 | * powered by the HPD). However, some displays toggle the HPD off and on for a | |
60 | * short period for one reason or another, and that would cause the CEC adapter | |
61 | * to be removed and added again, even though nothing else changed. | |
62 | * | |
63 | * This module parameter sets a delay in seconds before the CEC adapter is | |
64 | * actually unregistered. Only if the HPD does not return within that time will | |
65 | * the CEC adapter be unregistered. | |
66 | * | |
67 | * If it is set to a value >= NEVER_UNREG_DELAY, then the CEC adapter will never | |
68 | * be unregistered for as long as the connector remains registered. | |
69 | * | |
70 | * If it is set to 0, then the CEC adapter will be unregistered immediately as | |
71 | * soon as the HPD disappears. | |
72 | * | |
73 | * The default is one second to prevent short HPD glitches from unregistering | |
74 | * the CEC adapter. | |
75 | * | |
76 | * Note that for integrated HDMI branch devices that support CEC the DPCD | |
77 | * registers remain available even if the HPD goes low since it is not powered | |
78 | * by the HPD. In that case the CEC adapter will never be unregistered during | |
79 | * the life time of the connector. At least, this is the theory since I do not | |
80 | * have hardware with an integrated HDMI branch device that supports CEC. | |
81 | */ | |
82 | #define NEVER_UNREG_DELAY 1000 | |
83 | static unsigned int drm_dp_cec_unregister_delay = 1; | |
84 | module_param(drm_dp_cec_unregister_delay, uint, 0600); | |
85 | MODULE_PARM_DESC(drm_dp_cec_unregister_delay, | |
86 | "CEC unregister delay in seconds, 0: no delay, >= 1000: never unregister"); | |
87 | ||
88 | static int drm_dp_cec_adap_enable(struct cec_adapter *adap, bool enable) | |
89 | { | |
90 | struct drm_dp_aux *aux = cec_get_drvdata(adap); | |
91 | u32 val = enable ? DP_CEC_TUNNELING_ENABLE : 0; | |
92 | ssize_t err = 0; | |
93 | ||
94 | err = drm_dp_dpcd_writeb(aux, DP_CEC_TUNNELING_CONTROL, val); | |
95 | return (enable && err < 0) ? err : 0; | |
96 | } | |
97 | ||
98 | static int drm_dp_cec_adap_log_addr(struct cec_adapter *adap, u8 addr) | |
99 | { | |
100 | struct drm_dp_aux *aux = cec_get_drvdata(adap); | |
101 | /* Bit 15 (logical address 15) should always be set */ | |
102 | u16 la_mask = 1 << CEC_LOG_ADDR_BROADCAST; | |
103 | u8 mask[2]; | |
104 | ssize_t err; | |
105 | ||
106 | if (addr != CEC_LOG_ADDR_INVALID) | |
107 | la_mask |= adap->log_addrs.log_addr_mask | (1 << addr); | |
108 | mask[0] = la_mask & 0xff; | |
109 | mask[1] = la_mask >> 8; | |
110 | err = drm_dp_dpcd_write(aux, DP_CEC_LOGICAL_ADDRESS_MASK, mask, 2); | |
111 | return (addr != CEC_LOG_ADDR_INVALID && err < 0) ? err : 0; | |
112 | } | |
113 | ||
114 | static int drm_dp_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, | |
115 | u32 signal_free_time, struct cec_msg *msg) | |
116 | { | |
117 | struct drm_dp_aux *aux = cec_get_drvdata(adap); | |
118 | unsigned int retries = min(5, attempts - 1); | |
119 | ssize_t err; | |
120 | ||
121 | err = drm_dp_dpcd_write(aux, DP_CEC_TX_MESSAGE_BUFFER, | |
122 | msg->msg, msg->len); | |
123 | if (err < 0) | |
124 | return err; | |
125 | ||
126 | err = drm_dp_dpcd_writeb(aux, DP_CEC_TX_MESSAGE_INFO, | |
127 | (msg->len - 1) | (retries << 4) | | |
128 | DP_CEC_TX_MESSAGE_SEND); | |
129 | return err < 0 ? err : 0; | |
130 | } | |
131 | ||
132 | static int drm_dp_cec_adap_monitor_all_enable(struct cec_adapter *adap, | |
133 | bool enable) | |
134 | { | |
135 | struct drm_dp_aux *aux = cec_get_drvdata(adap); | |
136 | ssize_t err; | |
137 | u8 val; | |
138 | ||
139 | if (!(adap->capabilities & CEC_CAP_MONITOR_ALL)) | |
140 | return 0; | |
141 | ||
142 | err = drm_dp_dpcd_readb(aux, DP_CEC_TUNNELING_CONTROL, &val); | |
143 | if (err >= 0) { | |
144 | if (enable) | |
145 | val |= DP_CEC_SNOOPING_ENABLE; | |
146 | else | |
147 | val &= ~DP_CEC_SNOOPING_ENABLE; | |
148 | err = drm_dp_dpcd_writeb(aux, DP_CEC_TUNNELING_CONTROL, val); | |
149 | } | |
150 | return (enable && err < 0) ? err : 0; | |
151 | } | |
152 | ||
153 | static void drm_dp_cec_adap_status(struct cec_adapter *adap, | |
154 | struct seq_file *file) | |
155 | { | |
156 | struct drm_dp_aux *aux = cec_get_drvdata(adap); | |
157 | struct drm_dp_desc desc; | |
158 | struct drm_dp_dpcd_ident *id = &desc.ident; | |
159 | ||
160 | if (drm_dp_read_desc(aux, &desc, true)) | |
161 | return; | |
ea5569ec | 162 | seq_printf(file, "OUI: %*phD\n", |
2c6d1fff HV |
163 | (int)sizeof(id->oui), id->oui); |
164 | seq_printf(file, "ID: %*pE\n", | |
165 | (int)strnlen(id->device_id, sizeof(id->device_id)), | |
166 | id->device_id); | |
167 | seq_printf(file, "HW Rev: %d.%d\n", id->hw_rev >> 4, id->hw_rev & 0xf); | |
168 | /* | |
169 | * Show this both in decimal and hex: at least one vendor | |
170 | * always reports this in hex. | |
171 | */ | |
172 | seq_printf(file, "FW/SW Rev: %d.%d (0x%02x.0x%02x)\n", | |
173 | id->sw_major_rev, id->sw_minor_rev, | |
174 | id->sw_major_rev, id->sw_minor_rev); | |
175 | } | |
176 | ||
177 | static const struct cec_adap_ops drm_dp_cec_adap_ops = { | |
178 | .adap_enable = drm_dp_cec_adap_enable, | |
179 | .adap_log_addr = drm_dp_cec_adap_log_addr, | |
180 | .adap_transmit = drm_dp_cec_adap_transmit, | |
181 | .adap_monitor_all_enable = drm_dp_cec_adap_monitor_all_enable, | |
182 | .adap_status = drm_dp_cec_adap_status, | |
183 | }; | |
184 | ||
185 | static int drm_dp_cec_received(struct drm_dp_aux *aux) | |
186 | { | |
187 | struct cec_adapter *adap = aux->cec.adap; | |
188 | struct cec_msg msg; | |
189 | u8 rx_msg_info; | |
190 | ssize_t err; | |
191 | ||
192 | err = drm_dp_dpcd_readb(aux, DP_CEC_RX_MESSAGE_INFO, &rx_msg_info); | |
193 | if (err < 0) | |
194 | return err; | |
195 | ||
196 | if (!(rx_msg_info & DP_CEC_RX_MESSAGE_ENDED)) | |
197 | return 0; | |
198 | ||
199 | msg.len = (rx_msg_info & DP_CEC_RX_MESSAGE_LEN_MASK) + 1; | |
200 | err = drm_dp_dpcd_read(aux, DP_CEC_RX_MESSAGE_BUFFER, msg.msg, msg.len); | |
201 | if (err < 0) | |
202 | return err; | |
203 | ||
204 | cec_received_msg(adap, &msg); | |
205 | return 0; | |
206 | } | |
207 | ||
208 | static void drm_dp_cec_handle_irq(struct drm_dp_aux *aux) | |
209 | { | |
210 | struct cec_adapter *adap = aux->cec.adap; | |
211 | u8 flags; | |
212 | ||
213 | if (drm_dp_dpcd_readb(aux, DP_CEC_TUNNELING_IRQ_FLAGS, &flags) < 0) | |
214 | return; | |
215 | ||
216 | if (flags & DP_CEC_RX_MESSAGE_INFO_VALID) | |
217 | drm_dp_cec_received(aux); | |
218 | ||
219 | if (flags & DP_CEC_TX_MESSAGE_SENT) | |
220 | cec_transmit_attempt_done(adap, CEC_TX_STATUS_OK); | |
221 | else if (flags & DP_CEC_TX_LINE_ERROR) | |
222 | cec_transmit_attempt_done(adap, CEC_TX_STATUS_ERROR | | |
223 | CEC_TX_STATUS_MAX_RETRIES); | |
224 | else if (flags & | |
225 | (DP_CEC_TX_ADDRESS_NACK_ERROR | DP_CEC_TX_DATA_NACK_ERROR)) | |
226 | cec_transmit_attempt_done(adap, CEC_TX_STATUS_NACK | | |
227 | CEC_TX_STATUS_MAX_RETRIES); | |
228 | drm_dp_dpcd_writeb(aux, DP_CEC_TUNNELING_IRQ_FLAGS, flags); | |
229 | } | |
230 | ||
231 | /** | |
232 | * drm_dp_cec_irq() - handle CEC interrupt, if any | |
233 | * @aux: DisplayPort AUX channel | |
234 | * | |
235 | * Should be called when handling an IRQ_HPD request. If CEC-tunneling-over-AUX | |
236 | * is present, then it will check for a CEC_IRQ and handle it accordingly. | |
237 | */ | |
238 | void drm_dp_cec_irq(struct drm_dp_aux *aux) | |
239 | { | |
240 | u8 cec_irq; | |
241 | int ret; | |
242 | ||
5ce70c79 HV |
243 | /* No transfer function was set, so not a DP connector */ |
244 | if (!aux->transfer) | |
245 | return; | |
246 | ||
2c6d1fff HV |
247 | mutex_lock(&aux->cec.lock); |
248 | if (!aux->cec.adap) | |
249 | goto unlock; | |
250 | ||
251 | ret = drm_dp_dpcd_readb(aux, DP_DEVICE_SERVICE_IRQ_VECTOR_ESI1, | |
252 | &cec_irq); | |
253 | if (ret < 0 || !(cec_irq & DP_CEC_IRQ)) | |
254 | goto unlock; | |
255 | ||
256 | drm_dp_cec_handle_irq(aux); | |
257 | drm_dp_dpcd_writeb(aux, DP_DEVICE_SERVICE_IRQ_VECTOR_ESI1, DP_CEC_IRQ); | |
258 | unlock: | |
259 | mutex_unlock(&aux->cec.lock); | |
260 | } | |
261 | EXPORT_SYMBOL(drm_dp_cec_irq); | |
262 | ||
263 | static bool drm_dp_cec_cap(struct drm_dp_aux *aux, u8 *cec_cap) | |
264 | { | |
265 | u8 cap = 0; | |
266 | ||
267 | if (drm_dp_dpcd_readb(aux, DP_CEC_TUNNELING_CAPABILITY, &cap) != 1 || | |
268 | !(cap & DP_CEC_TUNNELING_CAPABLE)) | |
269 | return false; | |
270 | if (cec_cap) | |
271 | *cec_cap = cap; | |
272 | return true; | |
273 | } | |
274 | ||
275 | /* | |
276 | * Called if the HPD was low for more than drm_dp_cec_unregister_delay | |
277 | * seconds. This unregisters the CEC adapter. | |
278 | */ | |
279 | static void drm_dp_cec_unregister_work(struct work_struct *work) | |
280 | { | |
281 | struct drm_dp_aux *aux = container_of(work, struct drm_dp_aux, | |
282 | cec.unregister_work.work); | |
283 | ||
284 | mutex_lock(&aux->cec.lock); | |
285 | cec_unregister_adapter(aux->cec.adap); | |
286 | aux->cec.adap = NULL; | |
287 | mutex_unlock(&aux->cec.lock); | |
288 | } | |
289 | ||
290 | /* | |
291 | * A new EDID is set. If there is no CEC adapter, then create one. If | |
292 | * there was a CEC adapter, then check if the CEC adapter properties | |
293 | * were unchanged and just update the CEC physical address. Otherwise | |
294 | * unregister the old CEC adapter and create a new one. | |
295 | */ | |
296 | void drm_dp_cec_set_edid(struct drm_dp_aux *aux, const struct edid *edid) | |
297 | { | |
298 | u32 cec_caps = CEC_CAP_DEFAULTS | CEC_CAP_NEEDS_HPD; | |
299 | unsigned int num_las = 1; | |
300 | u8 cap; | |
301 | ||
5ce70c79 HV |
302 | /* No transfer function was set, so not a DP connector */ |
303 | if (!aux->transfer) | |
304 | return; | |
305 | ||
2c6d1fff HV |
306 | #ifndef CONFIG_MEDIA_CEC_RC |
307 | /* | |
308 | * CEC_CAP_RC is part of CEC_CAP_DEFAULTS, but it is stripped by | |
309 | * cec_allocate_adapter() if CONFIG_MEDIA_CEC_RC is undefined. | |
310 | * | |
311 | * Do this here as well to ensure the tests against cec_caps are | |
312 | * correct. | |
313 | */ | |
314 | cec_caps &= ~CEC_CAP_RC; | |
315 | #endif | |
316 | cancel_delayed_work_sync(&aux->cec.unregister_work); | |
317 | ||
318 | mutex_lock(&aux->cec.lock); | |
319 | if (!drm_dp_cec_cap(aux, &cap)) { | |
320 | /* CEC is not supported, unregister any existing adapter */ | |
321 | cec_unregister_adapter(aux->cec.adap); | |
322 | aux->cec.adap = NULL; | |
323 | goto unlock; | |
324 | } | |
325 | ||
326 | if (cap & DP_CEC_SNOOPING_CAPABLE) | |
327 | cec_caps |= CEC_CAP_MONITOR_ALL; | |
328 | if (cap & DP_CEC_MULTIPLE_LA_CAPABLE) | |
329 | num_las = CEC_MAX_LOG_ADDRS; | |
330 | ||
331 | if (aux->cec.adap) { | |
332 | if (aux->cec.adap->capabilities == cec_caps && | |
333 | aux->cec.adap->available_log_addrs == num_las) { | |
334 | /* Unchanged, so just set the phys addr */ | |
335 | cec_s_phys_addr_from_edid(aux->cec.adap, edid); | |
336 | goto unlock; | |
337 | } | |
338 | /* | |
339 | * The capabilities changed, so unregister the old | |
340 | * adapter first. | |
341 | */ | |
342 | cec_unregister_adapter(aux->cec.adap); | |
343 | } | |
344 | ||
345 | /* Create a new adapter */ | |
346 | aux->cec.adap = cec_allocate_adapter(&drm_dp_cec_adap_ops, | |
347 | aux, aux->cec.name, cec_caps, | |
348 | num_las); | |
349 | if (IS_ERR(aux->cec.adap)) { | |
350 | aux->cec.adap = NULL; | |
351 | goto unlock; | |
352 | } | |
353 | if (cec_register_adapter(aux->cec.adap, aux->cec.parent)) { | |
354 | cec_delete_adapter(aux->cec.adap); | |
355 | aux->cec.adap = NULL; | |
356 | } else { | |
357 | /* | |
358 | * Update the phys addr for the new CEC adapter. When called | |
359 | * from drm_dp_cec_register_connector() edid == NULL, so in | |
360 | * that case the phys addr is just invalidated. | |
361 | */ | |
362 | cec_s_phys_addr_from_edid(aux->cec.adap, edid); | |
363 | } | |
364 | unlock: | |
365 | mutex_unlock(&aux->cec.lock); | |
366 | } | |
367 | EXPORT_SYMBOL(drm_dp_cec_set_edid); | |
368 | ||
369 | /* | |
370 | * The EDID disappeared (likely because of the HPD going down). | |
371 | */ | |
372 | void drm_dp_cec_unset_edid(struct drm_dp_aux *aux) | |
373 | { | |
5ce70c79 HV |
374 | /* No transfer function was set, so not a DP connector */ |
375 | if (!aux->transfer) | |
376 | return; | |
377 | ||
2c6d1fff HV |
378 | cancel_delayed_work_sync(&aux->cec.unregister_work); |
379 | ||
380 | mutex_lock(&aux->cec.lock); | |
381 | if (!aux->cec.adap) | |
382 | goto unlock; | |
383 | ||
384 | cec_phys_addr_invalidate(aux->cec.adap); | |
385 | /* | |
386 | * We're done if we want to keep the CEC device | |
387 | * (drm_dp_cec_unregister_delay is >= NEVER_UNREG_DELAY) or if the | |
388 | * DPCD still indicates the CEC capability (expected for an integrated | |
389 | * HDMI branch device). | |
390 | */ | |
391 | if (drm_dp_cec_unregister_delay < NEVER_UNREG_DELAY && | |
392 | !drm_dp_cec_cap(aux, NULL)) { | |
393 | /* | |
394 | * Unregister the CEC adapter after drm_dp_cec_unregister_delay | |
395 | * seconds. This to debounce short HPD off-and-on cycles from | |
396 | * displays. | |
397 | */ | |
398 | schedule_delayed_work(&aux->cec.unregister_work, | |
399 | drm_dp_cec_unregister_delay * HZ); | |
400 | } | |
401 | unlock: | |
402 | mutex_unlock(&aux->cec.lock); | |
403 | } | |
404 | EXPORT_SYMBOL(drm_dp_cec_unset_edid); | |
405 | ||
406 | /** | |
407 | * drm_dp_cec_register_connector() - register a new connector | |
408 | * @aux: DisplayPort AUX channel | |
409 | * @name: name of the CEC device | |
410 | * @parent: parent device | |
411 | * | |
412 | * A new connector was registered with associated CEC adapter name and | |
413 | * CEC adapter parent device. After registering the name and parent | |
414 | * drm_dp_cec_set_edid() is called to check if the connector supports | |
415 | * CEC and to register a CEC adapter if that is the case. | |
416 | */ | |
417 | void drm_dp_cec_register_connector(struct drm_dp_aux *aux, const char *name, | |
418 | struct device *parent) | |
419 | { | |
420 | WARN_ON(aux->cec.adap); | |
5ce70c79 HV |
421 | if (WARN_ON(!aux->transfer)) |
422 | return; | |
2c6d1fff HV |
423 | aux->cec.name = name; |
424 | aux->cec.parent = parent; | |
425 | INIT_DELAYED_WORK(&aux->cec.unregister_work, | |
426 | drm_dp_cec_unregister_work); | |
2c6d1fff HV |
427 | } |
428 | EXPORT_SYMBOL(drm_dp_cec_register_connector); | |
429 | ||
430 | /** | |
431 | * drm_dp_cec_unregister_connector() - unregister the CEC adapter, if any | |
432 | * @aux: DisplayPort AUX channel | |
433 | */ | |
434 | void drm_dp_cec_unregister_connector(struct drm_dp_aux *aux) | |
435 | { | |
436 | if (!aux->cec.adap) | |
437 | return; | |
438 | cancel_delayed_work_sync(&aux->cec.unregister_work); | |
439 | cec_unregister_adapter(aux->cec.adap); | |
440 | aux->cec.adap = NULL; | |
441 | } | |
442 | EXPORT_SYMBOL(drm_dp_cec_unregister_connector); |