drm/vkms: Add cursor plane support
[linux-2.6-microblaze.git] / drivers / gpu / drm / vkms / vkms_crtc.c
index 875fca6..177bbcb 100644 (file)
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_crtc_helper.h>
 
-static enum hrtimer_restart vkms_vblank_simulate(struct hrtimer *timer)
+static void _vblank_handle(struct vkms_output *output)
 {
-       struct vkms_output *output = container_of(timer, struct vkms_output,
-                                                 vblank_hrtimer);
        struct drm_crtc *crtc = &output->crtc;
-       int ret_overrun;
+       struct vkms_crtc_state *state = to_vkms_crtc_state(crtc->state);
        bool ret;
 
+       spin_lock(&output->lock);
        ret = drm_crtc_handle_vblank(crtc);
        if (!ret)
                DRM_ERROR("vkms failure on handling vblank");
 
+       if (state && output->crc_enabled) {
+               u64 frame = drm_crtc_accurate_vblank_count(crtc);
+
+               /* update frame_start only if a queued vkms_crc_work_handle()
+                * has read the data
+                */
+               spin_lock(&output->state_lock);
+               if (!state->frame_start)
+                       state->frame_start = frame;
+               spin_unlock(&output->state_lock);
+
+               ret = queue_work(output->crc_workq, &state->crc_work);
+               if (!ret)
+                       DRM_WARN("failed to queue vkms_crc_work_handle");
+       }
+
+       spin_unlock(&output->lock);
+}
+
+static enum hrtimer_restart vkms_vblank_simulate(struct hrtimer *timer)
+{
+       struct vkms_output *output = container_of(timer, struct vkms_output,
+                                                 vblank_hrtimer);
+       int ret_overrun;
+
+       _vblank_handle(output);
+
        ret_overrun = hrtimer_forward_now(&output->vblank_hrtimer,
                                          output->period_ns);
 
@@ -64,15 +90,68 @@ bool vkms_get_vblank_timestamp(struct drm_device *dev, unsigned int pipe,
        return true;
 }
 
+static void vkms_atomic_crtc_reset(struct drm_crtc *crtc)
+{
+       struct vkms_crtc_state *vkms_state = NULL;
+
+       if (crtc->state) {
+               vkms_state = to_vkms_crtc_state(crtc->state);
+               __drm_atomic_helper_crtc_destroy_state(crtc->state);
+               kfree(vkms_state);
+               crtc->state = NULL;
+       }
+
+       vkms_state = kzalloc(sizeof(*vkms_state), GFP_KERNEL);
+       if (!vkms_state)
+               return;
+
+       crtc->state = &vkms_state->base;
+       crtc->state->crtc = crtc;
+}
+
+static struct drm_crtc_state *
+vkms_atomic_crtc_duplicate_state(struct drm_crtc *crtc)
+{
+       struct vkms_crtc_state *vkms_state;
+
+       if (WARN_ON(!crtc->state))
+               return NULL;
+
+       vkms_state = kzalloc(sizeof(*vkms_state), GFP_KERNEL);
+       if (!vkms_state)
+               return NULL;
+
+       __drm_atomic_helper_crtc_duplicate_state(crtc, &vkms_state->base);
+
+       INIT_WORK(&vkms_state->crc_work, vkms_crc_work_handle);
+
+       return &vkms_state->base;
+}
+
+static void vkms_atomic_crtc_destroy_state(struct drm_crtc *crtc,
+                                          struct drm_crtc_state *state)
+{
+       struct vkms_crtc_state *vkms_state = to_vkms_crtc_state(state);
+
+       __drm_atomic_helper_crtc_destroy_state(state);
+
+       if (vkms_state) {
+               flush_work(&vkms_state->crc_work);
+               kfree(vkms_state);
+       }
+}
+
 static const struct drm_crtc_funcs vkms_crtc_funcs = {
        .set_config             = drm_atomic_helper_set_config,
        .destroy                = drm_crtc_cleanup,
        .page_flip              = drm_atomic_helper_page_flip,
-       .reset                  = drm_atomic_helper_crtc_reset,
-       .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
-       .atomic_destroy_state   = drm_atomic_helper_crtc_destroy_state,
+       .reset                  = vkms_atomic_crtc_reset,
+       .atomic_duplicate_state = vkms_atomic_crtc_duplicate_state,
+       .atomic_destroy_state   = vkms_atomic_crtc_destroy_state,
        .enable_vblank          = vkms_enable_vblank,
        .disable_vblank         = vkms_disable_vblank,
+       .set_crc_source         = vkms_set_crc_source,
+       .verify_crc_source      = vkms_verify_crc_source,
 };
 
 static void vkms_crtc_atomic_enable(struct drm_crtc *crtc,
@@ -87,9 +166,21 @@ static void vkms_crtc_atomic_disable(struct drm_crtc *crtc,
        drm_crtc_vblank_off(crtc);
 }
 
+static void vkms_crtc_atomic_begin(struct drm_crtc *crtc,
+                                  struct drm_crtc_state *old_crtc_state)
+{
+       struct vkms_output *vkms_output = drm_crtc_to_vkms_output(crtc);
+
+       /* This lock is held across the atomic commit to block vblank timer
+        * from scheduling vkms_crc_work_handle until the crc_data is updated
+        */
+       spin_lock_irq(&vkms_output->lock);
+}
+
 static void vkms_crtc_atomic_flush(struct drm_crtc *crtc,
                                   struct drm_crtc_state *old_crtc_state)
 {
+       struct vkms_output *vkms_output = drm_crtc_to_vkms_output(crtc);
        unsigned long flags;
 
        if (crtc->state->event) {
@@ -104,9 +195,12 @@ static void vkms_crtc_atomic_flush(struct drm_crtc *crtc,
 
                crtc->state->event = NULL;
        }
+
+       spin_unlock_irq(&vkms_output->lock);
 }
 
 static const struct drm_crtc_helper_funcs vkms_crtc_helper_funcs = {
+       .atomic_begin   = vkms_crtc_atomic_begin,
        .atomic_flush   = vkms_crtc_atomic_flush,
        .atomic_enable  = vkms_crtc_atomic_enable,
        .atomic_disable = vkms_crtc_atomic_disable,
@@ -115,6 +209,7 @@ static const struct drm_crtc_helper_funcs vkms_crtc_helper_funcs = {
 int vkms_crtc_init(struct drm_device *dev, struct drm_crtc *crtc,
                   struct drm_plane *primary, struct drm_plane *cursor)
 {
+       struct vkms_output *vkms_out = drm_crtc_to_vkms_output(crtc);
        int ret;
 
        ret = drm_crtc_init_with_planes(dev, crtc, primary, cursor,
@@ -126,5 +221,10 @@ int vkms_crtc_init(struct drm_device *dev, struct drm_crtc *crtc,
 
        drm_crtc_helper_add(crtc, &vkms_crtc_helper_funcs);
 
+       spin_lock_init(&vkms_out->lock);
+       spin_lock_init(&vkms_out->state_lock);
+
+       vkms_out->crc_workq = alloc_ordered_workqueue("vkms_crc_workq", 0);
+
        return ret;
 }