397 lines
9.7 KiB
C
397 lines
9.7 KiB
C
/*
|
|
* Copyright (C) 2012 Red Hat
|
|
* Copyright (c) 2015 - 2016 DisplayLink (UK) Ltd.
|
|
*
|
|
* Based on parts on udlfb.c:
|
|
* Copyright (C) 2009 its respective authors
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU General Public
|
|
* License v2. See the file COPYING in the main directory of this archive for
|
|
* more details.
|
|
*/
|
|
|
|
#include <drm/drmP.h>
|
|
#include <drm/drm_crtc.h>
|
|
#include <drm/drm_crtc_helper.h>
|
|
#include <uapi/drm/evdi_drm.h>
|
|
#include "evdi_drv.h"
|
|
#include "evdi_cursor.h"
|
|
|
|
struct evdi_flip_queue {
|
|
struct mutex lock;
|
|
struct workqueue_struct *wq;
|
|
struct delayed_work work;
|
|
struct drm_crtc *crtc;
|
|
struct drm_pending_vblank_event *event;
|
|
u64 flip_time; /* in jiffies */
|
|
u64 vblank_interval; /* in jiffies */
|
|
};
|
|
|
|
static void evdi_crtc_dpms(struct drm_crtc *crtc, int mode)
|
|
{
|
|
struct evdi_device *evdi = crtc->dev->dev_private;
|
|
|
|
evdi_painter_dpms_notify(evdi, mode);
|
|
}
|
|
|
|
static bool evdi_crtc_mode_fixup(
|
|
__always_unused struct drm_crtc *crtc,
|
|
__always_unused const struct drm_display_mode *mode,
|
|
__always_unused struct drm_display_mode *adjusted_mode)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
|
|
static int evdi_crtc_mode_set(struct drm_crtc *crtc,
|
|
struct drm_display_mode *mode,
|
|
struct drm_display_mode *adjusted_mode,
|
|
__always_unused int x,
|
|
__always_unused int y,
|
|
struct drm_framebuffer *old_fb)
|
|
{
|
|
struct drm_device *dev = NULL;
|
|
struct evdi_device *evdi = NULL;
|
|
struct evdi_framebuffer *efb = NULL;
|
|
struct evdi_flip_queue *flip_queue = NULL;
|
|
struct drm_clip_rect rect;
|
|
|
|
if (crtc->primary == NULL) {
|
|
EVDI_DEBUG("evdi_crtc_mode_set primary plane is NULL");
|
|
return 0;
|
|
}
|
|
|
|
EVDI_ENTER();
|
|
|
|
efb = to_evdi_fb(crtc->primary->fb);
|
|
if (old_fb) {
|
|
struct evdi_framebuffer *eold_fb = to_evdi_fb(old_fb);
|
|
|
|
eold_fb->active = false;
|
|
}
|
|
efb->active = true;
|
|
|
|
dev = efb->base.dev;
|
|
evdi = dev->dev_private;
|
|
evdi_painter_mode_changed_notify(evdi, &efb->base, adjusted_mode);
|
|
|
|
/* update flip queue vblank interval */
|
|
flip_queue = evdi->flip_queue;
|
|
if (flip_queue) {
|
|
mutex_lock(&flip_queue->lock);
|
|
flip_queue->vblank_interval = HZ / drm_mode_vrefresh(mode);
|
|
mutex_unlock(&flip_queue->lock);
|
|
}
|
|
|
|
/* damage all of it */
|
|
evdi_painter_set_new_scanout_buffer(evdi, efb);
|
|
evdi_painter_commit_scanout_buffer(evdi);
|
|
|
|
rect.x1 = 0;
|
|
rect.y1 = 0;
|
|
rect.x2 = efb->base.width;
|
|
rect.y2 = efb->base.height;
|
|
evdi_painter_mark_dirty(evdi, &rect);
|
|
EVDI_EXIT();
|
|
return 0;
|
|
}
|
|
|
|
static void evdi_crtc_disable(struct drm_crtc *crtc)
|
|
{
|
|
struct evdi_device *evdi = crtc->dev->dev_private;
|
|
|
|
EVDI_CHECKPT();
|
|
evdi_painter_crtc_state_notify(evdi, DRM_MODE_DPMS_OFF);
|
|
}
|
|
|
|
static void evdi_crtc_destroy(struct drm_crtc *crtc)
|
|
{
|
|
EVDI_CHECKPT();
|
|
drm_crtc_cleanup(crtc);
|
|
kfree(crtc);
|
|
}
|
|
|
|
static void evdi_sched_page_flip(struct work_struct *work)
|
|
{
|
|
struct evdi_flip_queue *flip_queue =
|
|
container_of(container_of(work, struct delayed_work, work),
|
|
struct evdi_flip_queue, work);
|
|
struct drm_crtc *crtc;
|
|
struct drm_device *dev;
|
|
struct drm_pending_vblank_event *event;
|
|
struct drm_framebuffer *fb;
|
|
|
|
mutex_lock(&flip_queue->lock);
|
|
crtc = flip_queue->crtc;
|
|
dev = crtc->dev;
|
|
event = flip_queue->event;
|
|
fb = crtc->primary->fb;
|
|
flip_queue->event = NULL;
|
|
mutex_unlock(&flip_queue->lock);
|
|
|
|
EVDI_CHECKPT();
|
|
if (fb) {
|
|
struct evdi_device *evdi = dev->dev_private;
|
|
const struct drm_clip_rect rect = {
|
|
0, 0, fb->width, fb->height };
|
|
|
|
evdi_painter_commit_scanout_buffer(evdi);
|
|
evdi_painter_mark_dirty(evdi, &rect);
|
|
}
|
|
if (event) {
|
|
unsigned long flags = 0;
|
|
|
|
spin_lock_irqsave(&dev->event_lock, flags);
|
|
drm_send_vblank_event(dev, 0, event);
|
|
spin_unlock_irqrestore(&dev->event_lock, flags);
|
|
}
|
|
}
|
|
|
|
static int evdi_crtc_page_flip(struct drm_crtc *crtc,
|
|
struct drm_framebuffer *fb,
|
|
struct drm_pending_vblank_event *event,
|
|
__always_unused uint32_t page_flip_flags)
|
|
{
|
|
struct drm_device *dev = crtc->dev;
|
|
struct evdi_device *evdi = dev->dev_private;
|
|
struct evdi_flip_queue *flip_queue = evdi->flip_queue;
|
|
|
|
if (!flip_queue || !flip_queue->wq) {
|
|
DRM_ERROR("Uninitialized page flip queue\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
mutex_lock(&flip_queue->lock);
|
|
|
|
EVDI_CHECKPT();
|
|
atomic_inc(&evdi->frame_count);
|
|
flip_queue->crtc = crtc;
|
|
if (fb) {
|
|
struct evdi_framebuffer *efb = to_evdi_fb(fb);
|
|
struct drm_framebuffer *old_fb = crtc->primary->fb;
|
|
|
|
if (old_fb) {
|
|
struct evdi_framebuffer *eold_fb = to_evdi_fb(old_fb);
|
|
|
|
eold_fb->active = false;
|
|
}
|
|
efb->active = true;
|
|
crtc->primary->fb = fb;
|
|
evdi_painter_set_new_scanout_buffer(evdi, efb);
|
|
}
|
|
if (event) {
|
|
if (flip_queue->event) {
|
|
unsigned long flags = 0;
|
|
|
|
spin_lock_irqsave(&dev->event_lock, flags);
|
|
drm_send_vblank_event(dev, 0, flip_queue->event);
|
|
spin_unlock_irqrestore(&dev->event_lock, flags);
|
|
}
|
|
flip_queue->event = event;
|
|
}
|
|
if (!delayed_work_pending(&flip_queue->work)) {
|
|
u64 now = jiffies;
|
|
u64 next_flip =
|
|
flip_queue->flip_time + flip_queue->vblank_interval;
|
|
flip_queue->flip_time = (next_flip < now) ? now : next_flip;
|
|
queue_delayed_work(flip_queue->wq, &flip_queue->work,
|
|
flip_queue->flip_time - now);
|
|
}
|
|
|
|
mutex_unlock(&flip_queue->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int evdi_crtc_cursor_set(struct drm_crtc *crtc,
|
|
struct drm_file *file,
|
|
uint32_t handle,
|
|
uint32_t width,
|
|
uint32_t height,
|
|
int32_t hot_x,
|
|
int32_t hot_y)
|
|
{
|
|
struct drm_device *dev = crtc->dev;
|
|
struct evdi_device *evdi = dev->dev_private;
|
|
struct drm_gem_object *obj = NULL;
|
|
struct evdi_gem_object *eobj = NULL;
|
|
/*
|
|
* evdi_crtc_cursor_set is callback function using
|
|
* deprecated cursor entry point.
|
|
* There is no info about underlaying pixel format.
|
|
* Hence we are assuming that it is in ARGB 32bpp format.
|
|
* This format it the only one supported in cursor composition
|
|
* function.
|
|
* This format is also enforced during framebuffer creation.
|
|
*
|
|
* Proper format will be available when driver start support
|
|
* universal planes for cursor.
|
|
*/
|
|
uint32_t format = DRM_FORMAT_ARGB8888;
|
|
uint32_t stride = 4 * width;
|
|
|
|
EVDI_CHECKPT();
|
|
if (handle) {
|
|
mutex_lock(&dev->struct_mutex);
|
|
obj = drm_gem_object_lookup(crtc->dev, file, handle);
|
|
if (obj)
|
|
eobj = to_evdi_bo(obj);
|
|
else
|
|
EVDI_ERROR("Failed to lookup gem object.\n");
|
|
mutex_unlock(&dev->struct_mutex);
|
|
}
|
|
|
|
evdi_cursor_set(evdi->cursor,
|
|
eobj, width, height, hot_x, hot_y,
|
|
format, stride);
|
|
drm_gem_object_unreference_unlocked(obj);
|
|
|
|
if (evdi_enable_cursor_blending)
|
|
return evdi_crtc_page_flip(crtc, NULL, NULL, 0);
|
|
else
|
|
evdi_painter_send_cursor_set(evdi->painter, evdi->cursor);
|
|
return 0;
|
|
}
|
|
|
|
static int evdi_crtc_cursor_move(struct drm_crtc *crtc, int x, int y)
|
|
{
|
|
struct drm_device *dev = crtc->dev;
|
|
struct evdi_device *evdi = dev->dev_private;
|
|
|
|
evdi_cursor_move(evdi->cursor, x, y);
|
|
|
|
if (evdi_enable_cursor_blending)
|
|
return evdi_crtc_page_flip(crtc, NULL, NULL, 0);
|
|
else
|
|
evdi_painter_send_cursor_move(evdi->painter, evdi->cursor);
|
|
return 0;
|
|
}
|
|
|
|
static void evdi_crtc_prepare(__always_unused struct drm_crtc *crtc)
|
|
{
|
|
}
|
|
|
|
static void evdi_crtc_commit(struct drm_crtc *crtc)
|
|
{
|
|
struct evdi_device *evdi = crtc->dev->dev_private;
|
|
|
|
EVDI_CHECKPT();
|
|
evdi_painter_crtc_state_notify(evdi, DRM_MODE_DPMS_ON);
|
|
}
|
|
|
|
static struct drm_crtc_helper_funcs evdi_helper_funcs = {
|
|
.dpms = evdi_crtc_dpms,
|
|
.mode_fixup = evdi_crtc_mode_fixup,
|
|
.mode_set = evdi_crtc_mode_set,
|
|
.prepare = evdi_crtc_prepare,
|
|
.commit = evdi_crtc_commit,
|
|
.disable = evdi_crtc_disable,
|
|
};
|
|
|
|
static const struct drm_crtc_funcs evdi_crtc_funcs = {
|
|
.set_config = drm_crtc_helper_set_config,
|
|
.destroy = evdi_crtc_destroy,
|
|
.page_flip = evdi_crtc_page_flip,
|
|
.cursor_set2 = evdi_crtc_cursor_set,
|
|
.cursor_move = evdi_crtc_cursor_move,
|
|
};
|
|
|
|
static int evdi_crtc_init(struct drm_device *dev)
|
|
{
|
|
struct drm_crtc *crtc;
|
|
int status = 0;
|
|
|
|
EVDI_CHECKPT();
|
|
crtc = kzalloc(sizeof(struct drm_crtc), GFP_KERNEL);
|
|
if (crtc == NULL)
|
|
return -ENOMEM;
|
|
|
|
status = drm_crtc_init(dev, crtc, &evdi_crtc_funcs);
|
|
EVDI_DEBUG("drm_crtc_init: %d\n", status);
|
|
drm_crtc_helper_add(crtc, &evdi_helper_funcs);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int evdi_flip_workqueue_init(struct drm_device *dev)
|
|
{
|
|
struct evdi_device *evdi = dev->dev_private;
|
|
struct evdi_flip_queue *flip_queue =
|
|
kzalloc(sizeof(struct evdi_flip_queue), GFP_KERNEL);
|
|
|
|
EVDI_CHECKPT();
|
|
if (WARN_ON(!flip_queue))
|
|
return -ENOMEM;
|
|
mutex_init(&flip_queue->lock);
|
|
flip_queue->wq = create_singlethread_workqueue("flip");
|
|
if (WARN_ON(!flip_queue->wq)) {
|
|
mutex_destroy(&flip_queue->lock);
|
|
kfree(flip_queue);
|
|
return -ENOMEM;
|
|
}
|
|
INIT_DELAYED_WORK(&flip_queue->work, evdi_sched_page_flip);
|
|
flip_queue->flip_time = jiffies;
|
|
flip_queue->vblank_interval = HZ / 60;
|
|
evdi->flip_queue = flip_queue;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void evdi_flip_workqueue_cleanup(struct drm_device *dev)
|
|
{
|
|
struct evdi_device *evdi = dev->dev_private;
|
|
struct evdi_flip_queue *flip_queue = evdi->flip_queue;
|
|
|
|
if (!flip_queue)
|
|
return;
|
|
|
|
EVDI_CHECKPT();
|
|
if (flip_queue->wq) {
|
|
flush_workqueue(flip_queue->wq);
|
|
destroy_workqueue(flip_queue->wq);
|
|
}
|
|
mutex_destroy(&flip_queue->lock);
|
|
kfree(flip_queue);
|
|
}
|
|
|
|
static const struct drm_mode_config_funcs evdi_mode_funcs = {
|
|
.fb_create = evdi_fb_user_fb_create,
|
|
.output_poll_changed = NULL,
|
|
};
|
|
|
|
int evdi_modeset_init(struct drm_device *dev)
|
|
{
|
|
struct drm_encoder *encoder;
|
|
|
|
EVDI_CHECKPT();
|
|
drm_mode_config_init(dev);
|
|
|
|
dev->mode_config.min_width = 640;
|
|
dev->mode_config.min_height = 480;
|
|
|
|
dev->mode_config.max_width = 3840;
|
|
dev->mode_config.max_height = 2160;
|
|
|
|
dev->mode_config.prefer_shadow = 0;
|
|
dev->mode_config.preferred_depth = 24;
|
|
|
|
dev->mode_config.funcs = &evdi_mode_funcs;
|
|
|
|
drm_mode_create_dirty_info_property(dev);
|
|
|
|
evdi_crtc_init(dev);
|
|
|
|
encoder = evdi_encoder_init(dev);
|
|
|
|
evdi_connector_init(dev, encoder);
|
|
|
|
return evdi_flip_workqueue_init(dev);
|
|
}
|
|
|
|
void evdi_modeset_cleanup(struct drm_device *dev)
|
|
{
|
|
EVDI_CHECKPT();
|
|
evdi_flip_workqueue_cleanup(dev);
|
|
drm_mode_config_cleanup(dev);
|
|
}
|