[PATCH] drm/dp: Move byte-by-byte AUX read fallback to drm_dp_dpcd_read()
From: Chia-Lin Kao (AceLan)
Date: Tue Jun 16 2026 - 01:38:16 EST
Move the workaround for USB-C hubs/adapters with buggy firmware (where
multi-byte AUX reads timeout but single-byte reads work) from the
high-level drm_dp_dpcd_read_data() helper down into drm_dp_dpcd_read().
This ensures all direct and indirect callers of drm_dp_dpcd_read()
benefit from the fallback mechanism, and restores the original clean
semantics and layout of include/drm/display/drm_dp_helper.h.
Also, avoid using drm_dp_dpcd_readb() in the fallback loop since it may
return 0 when the underlying transfer returns no bytes, which would be
silently treated as success. Instead, call drm_dp_dpcd_read_byte() which
goes through drm_dp_dpcd_read_data() and maps a short transfer to
-EPROTO; check err < 0 to catch all failure cases.
Fixes: a8f49a004301 ("drm/dp: Add byte-by-byte fallback for broken USB-C adapters")
Signed-off-by: Chia-Lin Kao (AceLan) <acelan.kao@xxxxxxxxxxxxx>
---
drivers/gpu/drm/display/drm_dp_helper.c | 21 +++++++++
include/drm/display/drm_dp_helper.h | 57 +++++++++----------------
2 files changed, 41 insertions(+), 37 deletions(-)
diff --git a/drivers/gpu/drm/display/drm_dp_helper.c b/drivers/gpu/drm/display/drm_dp_helper.c
index 9c31e14cc413b..d14a8b40a1f17 100644
--- a/drivers/gpu/drm/display/drm_dp_helper.c
+++ b/drivers/gpu/drm/display/drm_dp_helper.c
@@ -762,6 +762,27 @@ ssize_t drm_dp_dpcd_read(struct drm_dp_aux *aux, unsigned int offset,
ret = drm_dp_dpcd_access(aux, DP_AUX_NATIVE_READ, offset,
buffer, size);
+ if (ret < 0 && size > 1) {
+ size_t i;
+ u8 *buf = buffer;
+ int err;
+
+ /*
+ * Workaround for USB-C hubs/adapters with buggy firmware that fail
+ * multi-byte AUX reads but work with single-byte reads.
+ * Known affected devices:
+ * - Lenovo USB-C to VGA adapter (VIA VL817, idVendor=17ef, idProduct=7217)
+ * - Dell DA310 USB-C hub (idVendor=413c, idProduct=c010)
+ * Attempt byte-by-byte reading as a fallback.
+ */
+ for (i = 0; i < size; i++) {
+ err = drm_dp_dpcd_read_byte(aux, offset + i, &buf[i]);
+ if (err < 0)
+ return err;
+ }
+ ret = size;
+ }
+
drm_dp_dump_access(aux, DP_AUX_NATIVE_READ, offset, buffer, ret);
return ret;
}
diff --git a/include/drm/display/drm_dp_helper.h b/include/drm/display/drm_dp_helper.h
index 8c2d77a032f06..0126fb8080ab8 100644
--- a/include/drm/display/drm_dp_helper.h
+++ b/include/drm/display/drm_dp_helper.h
@@ -555,22 +555,6 @@ ssize_t drm_dp_dpcd_read(struct drm_dp_aux *aux, unsigned int offset,
ssize_t drm_dp_dpcd_write(struct drm_dp_aux *aux, unsigned int offset,
void *buffer, size_t size);
-/**
- * drm_dp_dpcd_readb() - read a single byte from the DPCD
- * @aux: DisplayPort AUX channel
- * @offset: address of the register to read
- * @valuep: location where the value of the register will be stored
- *
- * Returns the number of bytes transferred (1) on success, or a negative
- * error code on failure. In most of the cases you should be using
- * drm_dp_dpcd_read_byte() instead.
- */
-static inline ssize_t drm_dp_dpcd_readb(struct drm_dp_aux *aux,
- unsigned int offset, u8 *valuep)
-{
- return drm_dp_dpcd_read(aux, offset, valuep, 1);
-}
-
/**
* drm_dp_dpcd_read_data() - read a series of bytes from the DPCD
* @aux: DisplayPort AUX channel (SST or MST)
@@ -590,29 +574,12 @@ static inline int drm_dp_dpcd_read_data(struct drm_dp_aux *aux,
void *buffer, size_t size)
{
int ret;
- size_t i;
- u8 *buf = buffer;
ret = drm_dp_dpcd_read(aux, offset, buffer, size);
- if (ret >= 0) {
- if (ret < size)
- return -EPROTO;
- return 0;
- }
-
- /*
- * Workaround for USB-C hubs/adapters with buggy firmware that fail
- * multi-byte AUX reads but work with single-byte reads.
- * Known affected devices:
- * - Lenovo USB-C to VGA adapter (VIA VL817, idVendor=17ef, idProduct=7217)
- * - Dell DA310 USB-C hub (idVendor=413c, idProduct=c010)
- * Attempt byte-by-byte reading as a fallback.
- */
- for (i = 0; i < size; i++) {
- ret = drm_dp_dpcd_readb(aux, offset + i, &buf[i]);
- if (ret < 0)
- return ret;
- }
+ if (ret < 0)
+ return ret;
+ if (ret < size)
+ return -EPROTO;
return 0;
}
@@ -646,6 +613,22 @@ static inline int drm_dp_dpcd_write_data(struct drm_dp_aux *aux,
return 0;
}
+/**
+ * drm_dp_dpcd_readb() - read a single byte from the DPCD
+ * @aux: DisplayPort AUX channel
+ * @offset: address of the register to read
+ * @valuep: location where the value of the register will be stored
+ *
+ * Returns the number of bytes transferred (1) on success, or a negative
+ * error code on failure. In most of the cases you should be using
+ * drm_dp_dpcd_read_byte() instead.
+ */
+static inline ssize_t drm_dp_dpcd_readb(struct drm_dp_aux *aux,
+ unsigned int offset, u8 *valuep)
+{
+ return drm_dp_dpcd_read(aux, offset, valuep, 1);
+}
+
/**
* drm_dp_dpcd_writeb() - write a single byte to the DPCD
* @aux: DisplayPort AUX channel
--
2.53.0