1 | /* |
2 | * Copyright © 2016 Intel Corporation |
3 | * |
4 | * Permission is hereby granted, free of charge, to any person obtaining a |
5 | * copy of this software and associated documentation files (the "Software"), |
6 | * to deal in the Software without restriction, including without limitation |
7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
8 | * and/or sell copies of the Software, and to permit persons to whom the |
9 | * Software is furnished to do so, subject to the following conditions: |
10 | * |
11 | * The above copyright notice and this permission notice shall be included in |
12 | * all copies or substantial portions of the Software. |
13 | * |
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
17 | * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR |
18 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, |
19 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |
20 | * OTHER DEALINGS IN THE SOFTWARE. |
21 | */ |
22 | |
23 | #include <linux/delay.h> |
24 | #include <linux/errno.h> |
25 | #include <linux/export.h> |
26 | #include <linux/i2c.h> |
27 | #include <linux/slab.h> |
28 | #include <linux/string.h> |
29 | |
30 | #include <drm/display/drm_dp_dual_mode_helper.h> |
31 | #include <drm/drm_device.h> |
32 | #include <drm/drm_print.h> |
33 | |
34 | /** |
35 | * DOC: dp dual mode helpers |
36 | * |
37 | * Helper functions to deal with DP dual mode (aka. DP++) adaptors. |
38 | * |
39 | * Type 1: |
40 | * Adaptor registers (if any) and the sink DDC bus may be accessed via I2C. |
41 | * |
42 | * Type 2: |
43 | * Adaptor registers and sink DDC bus can be accessed either via I2C or |
44 | * I2C-over-AUX. Source devices may choose to implement either of these |
45 | * access methods. |
46 | */ |
47 | |
48 | #define DP_DUAL_MODE_SLAVE_ADDRESS 0x40 |
49 | |
50 | /** |
51 | * drm_dp_dual_mode_read - Read from the DP dual mode adaptor register(s) |
52 | * @adapter: I2C adapter for the DDC bus |
53 | * @offset: register offset |
54 | * @buffer: buffer for return data |
55 | * @size: size of the buffer |
56 | * |
57 | * Reads @size bytes from the DP dual mode adaptor registers |
58 | * starting at @offset. |
59 | * |
60 | * Returns: |
61 | * 0 on success, negative error code on failure |
62 | */ |
63 | ssize_t drm_dp_dual_mode_read(struct i2c_adapter *adapter, |
64 | u8 offset, void *buffer, size_t size) |
65 | { |
66 | u8 zero = 0; |
67 | char *tmpbuf = NULL; |
68 | /* |
69 | * As sub-addressing is not supported by all adaptors, |
70 | * always explicitly read from the start and discard |
71 | * any bytes that come before the requested offset. |
72 | * This way, no matter whether the adaptor supports it |
73 | * or not, we'll end up reading the proper data. |
74 | */ |
75 | struct i2c_msg msgs[] = { |
76 | { |
77 | .addr = DP_DUAL_MODE_SLAVE_ADDRESS, |
78 | .flags = 0, |
79 | .len = 1, |
80 | .buf = &zero, |
81 | }, |
82 | { |
83 | .addr = DP_DUAL_MODE_SLAVE_ADDRESS, |
84 | .flags = I2C_M_RD, |
85 | .len = size + offset, |
86 | .buf = buffer, |
87 | }, |
88 | }; |
89 | int ret; |
90 | |
91 | if (offset) { |
92 | tmpbuf = kmalloc(size: size + offset, GFP_KERNEL); |
93 | if (!tmpbuf) |
94 | return -ENOMEM; |
95 | |
96 | msgs[1].buf = tmpbuf; |
97 | } |
98 | |
99 | ret = i2c_transfer(adap: adapter, msgs, ARRAY_SIZE(msgs)); |
100 | if (tmpbuf) |
101 | memcpy(buffer, tmpbuf + offset, size); |
102 | |
103 | kfree(objp: tmpbuf); |
104 | |
105 | if (ret < 0) |
106 | return ret; |
107 | if (ret != ARRAY_SIZE(msgs)) |
108 | return -EPROTO; |
109 | |
110 | return 0; |
111 | } |
112 | EXPORT_SYMBOL(drm_dp_dual_mode_read); |
113 | |
114 | /** |
115 | * drm_dp_dual_mode_write - Write to the DP dual mode adaptor register(s) |
116 | * @adapter: I2C adapter for the DDC bus |
117 | * @offset: register offset |
118 | * @buffer: buffer for write data |
119 | * @size: size of the buffer |
120 | * |
121 | * Writes @size bytes to the DP dual mode adaptor registers |
122 | * starting at @offset. |
123 | * |
124 | * Returns: |
125 | * 0 on success, negative error code on failure |
126 | */ |
127 | ssize_t drm_dp_dual_mode_write(struct i2c_adapter *adapter, |
128 | u8 offset, const void *buffer, size_t size) |
129 | { |
130 | struct i2c_msg msg = { |
131 | .addr = DP_DUAL_MODE_SLAVE_ADDRESS, |
132 | .flags = 0, |
133 | .len = 1 + size, |
134 | .buf = NULL, |
135 | }; |
136 | void *data; |
137 | int ret; |
138 | |
139 | data = kmalloc(size: msg.len, GFP_KERNEL); |
140 | if (!data) |
141 | return -ENOMEM; |
142 | |
143 | msg.buf = data; |
144 | |
145 | memcpy(data, &offset, 1); |
146 | memcpy(data + 1, buffer, size); |
147 | |
148 | ret = i2c_transfer(adap: adapter, msgs: &msg, num: 1); |
149 | |
150 | kfree(objp: data); |
151 | |
152 | if (ret < 0) |
153 | return ret; |
154 | if (ret != 1) |
155 | return -EPROTO; |
156 | |
157 | return 0; |
158 | } |
159 | EXPORT_SYMBOL(drm_dp_dual_mode_write); |
160 | |
161 | static bool is_hdmi_adaptor(const char hdmi_id[DP_DUAL_MODE_HDMI_ID_LEN]) |
162 | { |
163 | static const char dp_dual_mode_hdmi_id[DP_DUAL_MODE_HDMI_ID_LEN] = |
164 | "DP-HDMI ADAPTOR\x04" ; |
165 | |
166 | return memcmp(p: hdmi_id, q: dp_dual_mode_hdmi_id, |
167 | size: sizeof(dp_dual_mode_hdmi_id)) == 0; |
168 | } |
169 | |
170 | static bool is_type1_adaptor(uint8_t adaptor_id) |
171 | { |
172 | return adaptor_id == 0 || adaptor_id == 0xff; |
173 | } |
174 | |
175 | static bool is_type2_adaptor(uint8_t adaptor_id) |
176 | { |
177 | return adaptor_id == (DP_DUAL_MODE_TYPE_TYPE2 | |
178 | DP_DUAL_MODE_REV_TYPE2); |
179 | } |
180 | |
181 | static bool is_lspcon_adaptor(const char hdmi_id[DP_DUAL_MODE_HDMI_ID_LEN], |
182 | const uint8_t adaptor_id) |
183 | { |
184 | return is_hdmi_adaptor(hdmi_id) && |
185 | (adaptor_id == (DP_DUAL_MODE_TYPE_TYPE2 | |
186 | DP_DUAL_MODE_TYPE_HAS_DPCD)); |
187 | } |
188 | |
189 | /** |
190 | * drm_dp_dual_mode_detect - Identify the DP dual mode adaptor |
191 | * @dev: &drm_device to use |
192 | * @adapter: I2C adapter for the DDC bus |
193 | * |
194 | * Attempt to identify the type of the DP dual mode adaptor used. |
195 | * |
196 | * Note that when the answer is @DRM_DP_DUAL_MODE_UNKNOWN it's not |
197 | * certain whether we're dealing with a native HDMI port or |
198 | * a type 1 DVI dual mode adaptor. The driver will have to use |
199 | * some other hardware/driver specific mechanism to make that |
200 | * distinction. |
201 | * |
202 | * Returns: |
203 | * The type of the DP dual mode adaptor used |
204 | */ |
205 | enum drm_dp_dual_mode_type drm_dp_dual_mode_detect(const struct drm_device *dev, |
206 | struct i2c_adapter *adapter) |
207 | { |
208 | char hdmi_id[DP_DUAL_MODE_HDMI_ID_LEN] = {}; |
209 | uint8_t adaptor_id = 0x00; |
210 | ssize_t ret; |
211 | |
212 | /* |
213 | * Let's see if the adaptor is there the by reading the |
214 | * HDMI ID registers. |
215 | * |
216 | * Note that type 1 DVI adaptors are not required to implemnt |
217 | * any registers, and that presents a problem for detection. |
218 | * If the i2c transfer is nacked, we may or may not be dealing |
219 | * with a type 1 DVI adaptor. Some other mechanism of detecting |
220 | * the presence of the adaptor is required. One way would be |
221 | * to check the state of the CONFIG1 pin, Another method would |
222 | * simply require the driver to know whether the port is a DP++ |
223 | * port or a native HDMI port. Both of these methods are entirely |
224 | * hardware/driver specific so we can't deal with them here. |
225 | */ |
226 | ret = drm_dp_dual_mode_read(adapter, DP_DUAL_MODE_HDMI_ID, |
227 | hdmi_id, sizeof(hdmi_id)); |
228 | drm_dbg_kms(dev, "DP dual mode HDMI ID: %*pE (err %zd)\n" , |
229 | ret ? 0 : (int)sizeof(hdmi_id), hdmi_id, ret); |
230 | if (ret) |
231 | return DRM_DP_DUAL_MODE_UNKNOWN; |
232 | |
233 | ret = drm_dp_dual_mode_read(adapter, DP_DUAL_MODE_ADAPTOR_ID, |
234 | &adaptor_id, sizeof(adaptor_id)); |
235 | drm_dbg_kms(dev, "DP dual mode adaptor ID: %02x (err %zd)\n" , adaptor_id, ret); |
236 | if (ret == 0) { |
237 | if (is_lspcon_adaptor(hdmi_id, adaptor_id)) |
238 | return DRM_DP_DUAL_MODE_LSPCON; |
239 | if (is_type2_adaptor(adaptor_id)) { |
240 | if (is_hdmi_adaptor(hdmi_id)) |
241 | return DRM_DP_DUAL_MODE_TYPE2_HDMI; |
242 | else |
243 | return DRM_DP_DUAL_MODE_TYPE2_DVI; |
244 | } |
245 | /* |
246 | * If not a proper type 1 ID, still assume type 1, but let |
247 | * the user know that we may have misdetected the type. |
248 | */ |
249 | if (!is_type1_adaptor(adaptor_id)) |
250 | drm_err(dev, "Unexpected DP dual mode adaptor ID %02x\n" , adaptor_id); |
251 | |
252 | } |
253 | |
254 | if (is_hdmi_adaptor(hdmi_id)) |
255 | return DRM_DP_DUAL_MODE_TYPE1_HDMI; |
256 | else |
257 | return DRM_DP_DUAL_MODE_TYPE1_DVI; |
258 | } |
259 | EXPORT_SYMBOL(drm_dp_dual_mode_detect); |
260 | |
261 | /** |
262 | * drm_dp_dual_mode_max_tmds_clock - Max TMDS clock for DP dual mode adaptor |
263 | * @dev: &drm_device to use |
264 | * @type: DP dual mode adaptor type |
265 | * @adapter: I2C adapter for the DDC bus |
266 | * |
267 | * Determine the max TMDS clock the adaptor supports based on the |
268 | * type of the dual mode adaptor and the DP_DUAL_MODE_MAX_TMDS_CLOCK |
269 | * register (on type2 adaptors). As some type 1 adaptors have |
270 | * problems with registers (see comments in drm_dp_dual_mode_detect()) |
271 | * we don't read the register on those, instead we simply assume |
272 | * a 165 MHz limit based on the specification. |
273 | * |
274 | * Returns: |
275 | * Maximum supported TMDS clock rate for the DP dual mode adaptor in kHz. |
276 | */ |
277 | int drm_dp_dual_mode_max_tmds_clock(const struct drm_device *dev, enum drm_dp_dual_mode_type type, |
278 | struct i2c_adapter *adapter) |
279 | { |
280 | uint8_t max_tmds_clock; |
281 | ssize_t ret; |
282 | |
283 | /* native HDMI so no limit */ |
284 | if (type == DRM_DP_DUAL_MODE_NONE) |
285 | return 0; |
286 | |
287 | /* |
288 | * Type 1 adaptors are limited to 165MHz |
289 | * Type 2 adaptors can tells us their limit |
290 | */ |
291 | if (type < DRM_DP_DUAL_MODE_TYPE2_DVI) |
292 | return 165000; |
293 | |
294 | ret = drm_dp_dual_mode_read(adapter, DP_DUAL_MODE_MAX_TMDS_CLOCK, |
295 | &max_tmds_clock, sizeof(max_tmds_clock)); |
296 | if (ret || max_tmds_clock == 0x00 || max_tmds_clock == 0xff) { |
297 | drm_dbg_kms(dev, "Failed to query max TMDS clock\n" ); |
298 | return 165000; |
299 | } |
300 | |
301 | return max_tmds_clock * 5000 / 2; |
302 | } |
303 | EXPORT_SYMBOL(drm_dp_dual_mode_max_tmds_clock); |
304 | |
305 | /** |
306 | * drm_dp_dual_mode_get_tmds_output - Get the state of the TMDS output buffers in the DP dual mode adaptor |
307 | * @dev: &drm_device to use |
308 | * @type: DP dual mode adaptor type |
309 | * @adapter: I2C adapter for the DDC bus |
310 | * @enabled: current state of the TMDS output buffers |
311 | * |
312 | * Get the state of the TMDS output buffers in the adaptor. For |
313 | * type2 adaptors this is queried from the DP_DUAL_MODE_TMDS_OEN |
314 | * register. As some type 1 adaptors have problems with registers |
315 | * (see comments in drm_dp_dual_mode_detect()) we don't read the |
316 | * register on those, instead we simply assume that the buffers |
317 | * are always enabled. |
318 | * |
319 | * Returns: |
320 | * 0 on success, negative error code on failure |
321 | */ |
322 | int drm_dp_dual_mode_get_tmds_output(const struct drm_device *dev, |
323 | enum drm_dp_dual_mode_type type, struct i2c_adapter *adapter, |
324 | bool *enabled) |
325 | { |
326 | uint8_t tmds_oen; |
327 | ssize_t ret; |
328 | |
329 | if (type < DRM_DP_DUAL_MODE_TYPE2_DVI) { |
330 | *enabled = true; |
331 | return 0; |
332 | } |
333 | |
334 | ret = drm_dp_dual_mode_read(adapter, DP_DUAL_MODE_TMDS_OEN, |
335 | &tmds_oen, sizeof(tmds_oen)); |
336 | if (ret) { |
337 | drm_dbg_kms(dev, "Failed to query state of TMDS output buffers\n" ); |
338 | return ret; |
339 | } |
340 | |
341 | *enabled = !(tmds_oen & DP_DUAL_MODE_TMDS_DISABLE); |
342 | |
343 | return 0; |
344 | } |
345 | EXPORT_SYMBOL(drm_dp_dual_mode_get_tmds_output); |
346 | |
347 | /** |
348 | * drm_dp_dual_mode_set_tmds_output - Enable/disable TMDS output buffers in the DP dual mode adaptor |
349 | * @dev: &drm_device to use |
350 | * @type: DP dual mode adaptor type |
351 | * @adapter: I2C adapter for the DDC bus |
352 | * @enable: enable (as opposed to disable) the TMDS output buffers |
353 | * |
354 | * Set the state of the TMDS output buffers in the adaptor. For |
355 | * type2 this is set via the DP_DUAL_MODE_TMDS_OEN register. |
356 | * Type1 adaptors do not support any register writes. |
357 | * |
358 | * Returns: |
359 | * 0 on success, negative error code on failure |
360 | */ |
361 | int drm_dp_dual_mode_set_tmds_output(const struct drm_device *dev, enum drm_dp_dual_mode_type type, |
362 | struct i2c_adapter *adapter, bool enable) |
363 | { |
364 | uint8_t tmds_oen = enable ? 0 : DP_DUAL_MODE_TMDS_DISABLE; |
365 | ssize_t ret; |
366 | int retry; |
367 | |
368 | if (type < DRM_DP_DUAL_MODE_TYPE2_DVI) |
369 | return 0; |
370 | |
371 | /* |
372 | * LSPCON adapters in low-power state may ignore the first write, so |
373 | * read back and verify the written value a few times. |
374 | */ |
375 | for (retry = 0; retry < 3; retry++) { |
376 | uint8_t tmp; |
377 | |
378 | ret = drm_dp_dual_mode_write(adapter, DP_DUAL_MODE_TMDS_OEN, |
379 | &tmds_oen, sizeof(tmds_oen)); |
380 | if (ret) { |
381 | drm_dbg_kms(dev, "Failed to %s TMDS output buffers (%d attempts)\n" , |
382 | enable ? "enable" : "disable" , retry + 1); |
383 | return ret; |
384 | } |
385 | |
386 | ret = drm_dp_dual_mode_read(adapter, DP_DUAL_MODE_TMDS_OEN, |
387 | &tmp, sizeof(tmp)); |
388 | if (ret) { |
389 | drm_dbg_kms(dev, |
390 | "I2C read failed during TMDS output buffer %s (%d attempts)\n" , |
391 | enable ? "enabling" : "disabling" , retry + 1); |
392 | return ret; |
393 | } |
394 | |
395 | if (tmp == tmds_oen) |
396 | return 0; |
397 | } |
398 | |
399 | drm_dbg_kms(dev, "I2C write value mismatch during TMDS output buffer %s\n" , |
400 | enable ? "enabling" : "disabling" ); |
401 | |
402 | return -EIO; |
403 | } |
404 | EXPORT_SYMBOL(drm_dp_dual_mode_set_tmds_output); |
405 | |
406 | /** |
407 | * drm_dp_get_dual_mode_type_name - Get the name of the DP dual mode adaptor type as a string |
408 | * @type: DP dual mode adaptor type |
409 | * |
410 | * Returns: |
411 | * String representation of the DP dual mode adaptor type |
412 | */ |
413 | const char *drm_dp_get_dual_mode_type_name(enum drm_dp_dual_mode_type type) |
414 | { |
415 | switch (type) { |
416 | case DRM_DP_DUAL_MODE_NONE: |
417 | return "none" ; |
418 | case DRM_DP_DUAL_MODE_TYPE1_DVI: |
419 | return "type 1 DVI" ; |
420 | case DRM_DP_DUAL_MODE_TYPE1_HDMI: |
421 | return "type 1 HDMI" ; |
422 | case DRM_DP_DUAL_MODE_TYPE2_DVI: |
423 | return "type 2 DVI" ; |
424 | case DRM_DP_DUAL_MODE_TYPE2_HDMI: |
425 | return "type 2 HDMI" ; |
426 | case DRM_DP_DUAL_MODE_LSPCON: |
427 | return "lspcon" ; |
428 | default: |
429 | WARN_ON(type != DRM_DP_DUAL_MODE_UNKNOWN); |
430 | return "unknown" ; |
431 | } |
432 | } |
433 | EXPORT_SYMBOL(drm_dp_get_dual_mode_type_name); |
434 | |
435 | /** |
436 | * drm_lspcon_get_mode: Get LSPCON's current mode of operation by |
437 | * reading offset (0x80, 0x41) |
438 | * @dev: &drm_device to use |
439 | * @adapter: I2C-over-aux adapter |
440 | * @mode: current lspcon mode of operation output variable |
441 | * |
442 | * Returns: |
443 | * 0 on success, sets the current_mode value to appropriate mode |
444 | * -error on failure |
445 | */ |
446 | int drm_lspcon_get_mode(const struct drm_device *dev, struct i2c_adapter *adapter, |
447 | enum drm_lspcon_mode *mode) |
448 | { |
449 | u8 data; |
450 | int ret = 0; |
451 | int retry; |
452 | |
453 | if (!mode) { |
454 | drm_err(dev, "NULL input\n" ); |
455 | return -EINVAL; |
456 | } |
457 | |
458 | /* Read Status: i2c over aux */ |
459 | for (retry = 0; retry < 6; retry++) { |
460 | if (retry) |
461 | usleep_range(min: 500, max: 1000); |
462 | |
463 | ret = drm_dp_dual_mode_read(adapter, |
464 | DP_DUAL_MODE_LSPCON_CURRENT_MODE, |
465 | &data, sizeof(data)); |
466 | if (!ret) |
467 | break; |
468 | } |
469 | |
470 | if (ret < 0) { |
471 | drm_dbg_kms(dev, "LSPCON read(0x80, 0x41) failed\n" ); |
472 | return -EFAULT; |
473 | } |
474 | |
475 | if (data & DP_DUAL_MODE_LSPCON_MODE_PCON) |
476 | *mode = DRM_LSPCON_MODE_PCON; |
477 | else |
478 | *mode = DRM_LSPCON_MODE_LS; |
479 | return 0; |
480 | } |
481 | EXPORT_SYMBOL(drm_lspcon_get_mode); |
482 | |
483 | /** |
484 | * drm_lspcon_set_mode: Change LSPCON's mode of operation by |
485 | * writing offset (0x80, 0x40) |
486 | * @dev: &drm_device to use |
487 | * @adapter: I2C-over-aux adapter |
488 | * @mode: required mode of operation |
489 | * |
490 | * Returns: |
491 | * 0 on success, -error on failure/timeout |
492 | */ |
493 | int drm_lspcon_set_mode(const struct drm_device *dev, struct i2c_adapter *adapter, |
494 | enum drm_lspcon_mode mode) |
495 | { |
496 | u8 data = 0; |
497 | int ret; |
498 | int time_out = 200; |
499 | enum drm_lspcon_mode current_mode; |
500 | |
501 | if (mode == DRM_LSPCON_MODE_PCON) |
502 | data = DP_DUAL_MODE_LSPCON_MODE_PCON; |
503 | |
504 | /* Change mode */ |
505 | ret = drm_dp_dual_mode_write(adapter, DP_DUAL_MODE_LSPCON_MODE_CHANGE, |
506 | &data, sizeof(data)); |
507 | if (ret < 0) { |
508 | drm_err(dev, "LSPCON mode change failed\n" ); |
509 | return ret; |
510 | } |
511 | |
512 | /* |
513 | * Confirm mode change by reading the status bit. |
514 | * Sometimes, it takes a while to change the mode, |
515 | * so wait and retry until time out or done. |
516 | */ |
517 | do { |
518 | ret = drm_lspcon_get_mode(dev, adapter, ¤t_mode); |
519 | if (ret) { |
520 | drm_err(dev, "can't confirm LSPCON mode change\n" ); |
521 | return ret; |
522 | } else { |
523 | if (current_mode != mode) { |
524 | msleep(msecs: 10); |
525 | time_out -= 10; |
526 | } else { |
527 | drm_dbg_kms(dev, "LSPCON mode changed to %s\n" , |
528 | mode == DRM_LSPCON_MODE_LS ? "LS" : "PCON" ); |
529 | return 0; |
530 | } |
531 | } |
532 | } while (time_out); |
533 | |
534 | drm_err(dev, "LSPCON mode change timed out\n" ); |
535 | return -ETIMEDOUT; |
536 | } |
537 | EXPORT_SYMBOL(drm_lspcon_set_mode); |
538 | |