drm/dp: add helpers for capture of frame CRCs
authorTomeu Vizoso <tomeu.vizoso@collabora.com>
Fri, 3 Mar 2017 13:39:34 +0000 (14:39 +0100)
committerSean Paul <seanpaul@chromium.org>
Mon, 6 Mar 2017 17:14:27 +0000 (12:14 -0500)
Adds helpers for starting and stopping capture of frame CRCs through the
DPCD. When capture is on, a worker waits for vblanks and retrieves the
frame CRC to put it in the queue on the CRTC that is using the
eDP connector, so it's passed to userspace.

v2: Reuse drm_crtc_wait_one_vblank
    Update locking, as drm_crtc_add_crc_entry now takes the lock

v3: Don't call wake_up_interruptible directly, that's now done in
    drm_crtc_add_crc_entry.

v4: Style fixes (Sean Paul)
    Reworked retry of CRC reads (Sean Paul)
    Flush worker after stopping CRC generationa (Sean Paul)

v5: Move back to make the retry explicitly once

v6: Set and use the drm_crtc backpointer (Sean Paul)

Signed-off-by: Tomeu Vizoso <tomeu.vizoso@collabora.com>
Signed-off-by: Sean Paul <seanpaul@chromium.org>
Link: http://patchwork.freedesktop.org/patch/msgid/20170303133936.14964-3-tomeu.vizoso@collabora.com
drivers/gpu/drm/drm_dp_helper.c
include/drm/drm_dp_helper.h

index 68908c1d5ca1bcca96ce99efe8fa52db33d38567..c40cfe2e63ab333a5dfa4225396d4f120cdfe537 100644 (file)
@@ -981,6 +981,78 @@ static const struct i2c_lock_operations drm_dp_i2c_lock_ops = {
        .unlock_bus = unlock_bus,
 };
 
+static int drm_dp_aux_get_crc(struct drm_dp_aux *aux, u8 *crc)
+{
+       u8 buf, count;
+       int ret;
+
+       ret = drm_dp_dpcd_readb(aux, DP_TEST_SINK, &buf);
+       if (ret < 0)
+               return ret;
+
+       WARN_ON(!(buf & DP_TEST_SINK_START));
+
+       ret = drm_dp_dpcd_readb(aux, DP_TEST_SINK_MISC, &buf);
+       if (ret < 0)
+               return ret;
+
+       count = buf & DP_TEST_COUNT_MASK;
+       if (count == aux->crc_count)
+               return -EAGAIN; /* No CRC yet */
+
+       aux->crc_count = count;
+
+       /*
+        * At DP_TEST_CRC_R_CR, there's 6 bytes containing CRC data, 2 bytes
+        * per component (RGB or CrYCb).
+        */
+       ret = drm_dp_dpcd_read(aux, DP_TEST_CRC_R_CR, crc, 6);
+       if (ret < 0)
+               return ret;
+
+       return 0;
+}
+
+static void drm_dp_aux_crc_work(struct work_struct *work)
+{
+       struct drm_dp_aux *aux = container_of(work, struct drm_dp_aux,
+                                             crc_work);
+       struct drm_crtc *crtc;
+       u8 crc_bytes[6];
+       uint32_t crcs[3];
+       int ret;
+
+       if (WARN_ON(!aux->crtc))
+               return;
+
+       crtc = aux->crtc;
+       while (crtc->crc.opened) {
+               drm_crtc_wait_one_vblank(crtc);
+               if (!crtc->crc.opened)
+                       break;
+
+               ret = drm_dp_aux_get_crc(aux, crc_bytes);
+               if (ret == -EAGAIN) {
+                       usleep_range(1000, 2000);
+                       ret = drm_dp_aux_get_crc(aux, crc_bytes);
+               }
+
+               if (ret == -EAGAIN) {
+                       DRM_DEBUG_KMS("Get CRC failed after retrying: %d\n",
+                                     ret);
+                       continue;
+               } else if (ret) {
+                       DRM_DEBUG_KMS("Failed to get a CRC: %d\n", ret);
+                       continue;
+               }
+
+               crcs[0] = crc_bytes[0] | crc_bytes[1] << 8;
+               crcs[1] = crc_bytes[2] | crc_bytes[3] << 8;
+               crcs[2] = crc_bytes[4] | crc_bytes[5] << 8;
+               drm_crtc_add_crc_entry(crtc, false, 0, crcs);
+       }
+}
+
 /**
  * drm_dp_aux_init() - minimally initialise an aux channel
  * @aux: DisplayPort AUX channel
@@ -993,6 +1065,7 @@ static const struct i2c_lock_operations drm_dp_i2c_lock_ops = {
 void drm_dp_aux_init(struct drm_dp_aux *aux)
 {
        mutex_init(&aux->hw_mutex);
+       INIT_WORK(&aux->crc_work, drm_dp_aux_crc_work);
 
        aux->ddc.algo = &drm_dp_i2c_algo;
        aux->ddc.algo_data = aux;
@@ -1081,3 +1154,56 @@ int drm_dp_psr_setup_time(const u8 psr_cap[EDP_PSR_RECEIVER_CAP_SIZE])
 EXPORT_SYMBOL(drm_dp_psr_setup_time);
 
 #undef PSR_SETUP_TIME
+
+/**
+ * drm_dp_start_crc() - start capture of frame CRCs
+ * @aux: DisplayPort AUX channel
+ *
+ * Returns 0 on success or a negative error code on failure.
+ */
+int drm_dp_start_crc(struct drm_dp_aux *aux, struct drm_crtc *crtc)
+{
+       u8 buf;
+       int ret;
+
+       ret = drm_dp_dpcd_readb(aux, DP_TEST_SINK, &buf);
+       if (ret < 0)
+               return ret;
+
+       ret = drm_dp_dpcd_writeb(aux, DP_TEST_SINK, buf | DP_TEST_SINK_START);
+       if (ret < 0)
+               return ret;
+
+       aux->crc_count = 0;
+       aux->crtc = crtc;
+       schedule_work(&aux->crc_work);
+
+       return 0;
+}
+EXPORT_SYMBOL(drm_dp_start_crc);
+
+/**
+ * drm_dp_stop_crc() - stop capture of frame CRCs
+ * @aux: DisplayPort AUX channel
+ *
+ * Returns 0 on success or a negative error code on failure.
+ */
+int drm_dp_stop_crc(struct drm_dp_aux *aux)
+{
+       u8 buf;
+       int ret;
+
+       ret = drm_dp_dpcd_readb(aux, DP_TEST_SINK, &buf);
+       if (ret < 0)
+               return ret;
+
+       ret = drm_dp_dpcd_writeb(aux, DP_TEST_SINK, buf & ~DP_TEST_SINK_START);
+       if (ret < 0)
+               return ret;
+
+       flush_work(&aux->crc_work);
+       aux->crtc = NULL;
+
+       return 0;
+}
+EXPORT_SYMBOL(drm_dp_stop_crc);
index afe515855f0aebf8b9a8eb5cb7d7aa4290fc5d15..c34984c8d5d0175569629115841d21a47fd839f3 100644 (file)
@@ -734,6 +734,8 @@ struct drm_dp_aux_msg {
  * @dev: pointer to struct device that is the parent for this AUX channel
  * @crtc: backpointer to the crtc that is currently using this AUX channel
  * @hw_mutex: internal mutex used for locking transfers
+ * @crc_work: worker that captures CRCs for each frame
+ * @crc_count: counter of captured frame CRCs
  * @transfer: transfers a message representing a single AUX transaction
  *
  * The .dev field should be set to a pointer to the device that implements
@@ -771,6 +773,8 @@ struct drm_dp_aux {
        struct device *dev;
        struct drm_crtc *crtc;
        struct mutex hw_mutex;
+       struct work_struct crc_work;
+       u8 crc_count;
        ssize_t (*transfer)(struct drm_dp_aux *aux,
                            struct drm_dp_aux_msg *msg);
        /**
@@ -849,4 +853,7 @@ void drm_dp_aux_init(struct drm_dp_aux *aux);
 int drm_dp_aux_register(struct drm_dp_aux *aux);
 void drm_dp_aux_unregister(struct drm_dp_aux *aux);
 
+int drm_dp_start_crc(struct drm_dp_aux *aux, struct drm_crtc *crtc);
+int drm_dp_stop_crc(struct drm_dp_aux *aux);
+
 #endif /* _DRM_DP_HELPER_H_ */