mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-25 06:41:21 +07:00
9fda81e00e
Udl has a single display pipeline with a primary plane; perfect for simple-pipe helpers. Convert it over. The old encoder and CRTC code becomes unused and obsolete. Exported formats for the primary plane are RGB565 and XRGB8888, with the latter being emulated. The 16-bit format is the default and what is used when communicating with the device. This patch enables atomic modesetting for udl devices. v3: * remove unused field crtc from struct udl_device * set crtc_state->no_vblank at beginning of enable() v2: * move suspend/resume changes into separate patch * remove non-atomic code Signed-off-by: Thomas Zimmermann <tzimmermann@suse.de> Reviewed-by: Emil Velikov <emil.l.velikov@gmail.com> Link: https://patchwork.freedesktop.org/patch/msgid/20191210084905.5570-3-tzimmermann@suse.de
459 lines
12 KiB
C
459 lines
12 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (C) 2012 Red Hat
|
|
*
|
|
* based in parts on udlfb.c:
|
|
* Copyright (C) 2009 Roberto De Ioris <roberto@unbit.it>
|
|
* Copyright (C) 2009 Jaya Kumar <jayakumar.lkml@gmail.com>
|
|
* Copyright (C) 2009 Bernie Thompson <bernie@plugable.com>
|
|
|
|
*/
|
|
|
|
#include <drm/drm_atomic_helper.h>
|
|
#include <drm/drm_crtc_helper.h>
|
|
#include <drm/drm_gem_framebuffer_helper.h>
|
|
#include <drm/drm_modeset_helper_vtables.h>
|
|
#include <drm/drm_vblank.h>
|
|
|
|
#include "udl_drv.h"
|
|
|
|
#define UDL_COLOR_DEPTH_16BPP 0
|
|
|
|
/*
|
|
* All DisplayLink bulk operations start with 0xAF, followed by specific code
|
|
* All operations are written to buffers which then later get sent to device
|
|
*/
|
|
static char *udl_set_register(char *buf, u8 reg, u8 val)
|
|
{
|
|
*buf++ = 0xAF;
|
|
*buf++ = 0x20;
|
|
*buf++ = reg;
|
|
*buf++ = val;
|
|
return buf;
|
|
}
|
|
|
|
static char *udl_vidreg_lock(char *buf)
|
|
{
|
|
return udl_set_register(buf, 0xFF, 0x00);
|
|
}
|
|
|
|
static char *udl_vidreg_unlock(char *buf)
|
|
{
|
|
return udl_set_register(buf, 0xFF, 0xFF);
|
|
}
|
|
|
|
/*
|
|
* On/Off for driving the DisplayLink framebuffer to the display
|
|
* 0x00 H and V sync on
|
|
* 0x01 H and V sync off (screen blank but powered)
|
|
* 0x07 DPMS powerdown (requires modeset to come back)
|
|
*/
|
|
static char *udl_set_blank(char *buf, int dpms_mode)
|
|
{
|
|
u8 reg;
|
|
switch (dpms_mode) {
|
|
case DRM_MODE_DPMS_OFF:
|
|
reg = 0x07;
|
|
break;
|
|
case DRM_MODE_DPMS_STANDBY:
|
|
reg = 0x05;
|
|
break;
|
|
case DRM_MODE_DPMS_SUSPEND:
|
|
reg = 0x01;
|
|
break;
|
|
case DRM_MODE_DPMS_ON:
|
|
reg = 0x00;
|
|
break;
|
|
}
|
|
|
|
return udl_set_register(buf, 0x1f, reg);
|
|
}
|
|
|
|
static char *udl_set_color_depth(char *buf, u8 selection)
|
|
{
|
|
return udl_set_register(buf, 0x00, selection);
|
|
}
|
|
|
|
static char *udl_set_base16bpp(char *wrptr, u32 base)
|
|
{
|
|
/* the base pointer is 16 bits wide, 0x20 is hi byte. */
|
|
wrptr = udl_set_register(wrptr, 0x20, base >> 16);
|
|
wrptr = udl_set_register(wrptr, 0x21, base >> 8);
|
|
return udl_set_register(wrptr, 0x22, base);
|
|
}
|
|
|
|
/*
|
|
* DisplayLink HW has separate 16bpp and 8bpp framebuffers.
|
|
* In 24bpp modes, the low 323 RGB bits go in the 8bpp framebuffer
|
|
*/
|
|
static char *udl_set_base8bpp(char *wrptr, u32 base)
|
|
{
|
|
wrptr = udl_set_register(wrptr, 0x26, base >> 16);
|
|
wrptr = udl_set_register(wrptr, 0x27, base >> 8);
|
|
return udl_set_register(wrptr, 0x28, base);
|
|
}
|
|
|
|
static char *udl_set_register_16(char *wrptr, u8 reg, u16 value)
|
|
{
|
|
wrptr = udl_set_register(wrptr, reg, value >> 8);
|
|
return udl_set_register(wrptr, reg+1, value);
|
|
}
|
|
|
|
/*
|
|
* This is kind of weird because the controller takes some
|
|
* register values in a different byte order than other registers.
|
|
*/
|
|
static char *udl_set_register_16be(char *wrptr, u8 reg, u16 value)
|
|
{
|
|
wrptr = udl_set_register(wrptr, reg, value);
|
|
return udl_set_register(wrptr, reg+1, value >> 8);
|
|
}
|
|
|
|
/*
|
|
* LFSR is linear feedback shift register. The reason we have this is
|
|
* because the display controller needs to minimize the clock depth of
|
|
* various counters used in the display path. So this code reverses the
|
|
* provided value into the lfsr16 value by counting backwards to get
|
|
* the value that needs to be set in the hardware comparator to get the
|
|
* same actual count. This makes sense once you read above a couple of
|
|
* times and think about it from a hardware perspective.
|
|
*/
|
|
static u16 udl_lfsr16(u16 actual_count)
|
|
{
|
|
u32 lv = 0xFFFF; /* This is the lfsr value that the hw starts with */
|
|
|
|
while (actual_count--) {
|
|
lv = ((lv << 1) |
|
|
(((lv >> 15) ^ (lv >> 4) ^ (lv >> 2) ^ (lv >> 1)) & 1))
|
|
& 0xFFFF;
|
|
}
|
|
|
|
return (u16) lv;
|
|
}
|
|
|
|
/*
|
|
* This does LFSR conversion on the value that is to be written.
|
|
* See LFSR explanation above for more detail.
|
|
*/
|
|
static char *udl_set_register_lfsr16(char *wrptr, u8 reg, u16 value)
|
|
{
|
|
return udl_set_register_16(wrptr, reg, udl_lfsr16(value));
|
|
}
|
|
|
|
/*
|
|
* This takes a standard fbdev screeninfo struct and all of its monitor mode
|
|
* details and converts them into the DisplayLink equivalent register commands.
|
|
ERR(vreg(dev, 0x00, (color_depth == 16) ? 0 : 1));
|
|
ERR(vreg_lfsr16(dev, 0x01, xDisplayStart));
|
|
ERR(vreg_lfsr16(dev, 0x03, xDisplayEnd));
|
|
ERR(vreg_lfsr16(dev, 0x05, yDisplayStart));
|
|
ERR(vreg_lfsr16(dev, 0x07, yDisplayEnd));
|
|
ERR(vreg_lfsr16(dev, 0x09, xEndCount));
|
|
ERR(vreg_lfsr16(dev, 0x0B, hSyncStart));
|
|
ERR(vreg_lfsr16(dev, 0x0D, hSyncEnd));
|
|
ERR(vreg_big_endian(dev, 0x0F, hPixels));
|
|
ERR(vreg_lfsr16(dev, 0x11, yEndCount));
|
|
ERR(vreg_lfsr16(dev, 0x13, vSyncStart));
|
|
ERR(vreg_lfsr16(dev, 0x15, vSyncEnd));
|
|
ERR(vreg_big_endian(dev, 0x17, vPixels));
|
|
ERR(vreg_little_endian(dev, 0x1B, pixelClock5KHz));
|
|
|
|
ERR(vreg(dev, 0x1F, 0));
|
|
|
|
ERR(vbuf(dev, WRITE_VIDREG_UNLOCK, DSIZEOF(WRITE_VIDREG_UNLOCK)));
|
|
*/
|
|
static char *udl_set_vid_cmds(char *wrptr, struct drm_display_mode *mode)
|
|
{
|
|
u16 xds, yds;
|
|
u16 xde, yde;
|
|
u16 yec;
|
|
|
|
/* x display start */
|
|
xds = mode->crtc_htotal - mode->crtc_hsync_start;
|
|
wrptr = udl_set_register_lfsr16(wrptr, 0x01, xds);
|
|
/* x display end */
|
|
xde = xds + mode->crtc_hdisplay;
|
|
wrptr = udl_set_register_lfsr16(wrptr, 0x03, xde);
|
|
|
|
/* y display start */
|
|
yds = mode->crtc_vtotal - mode->crtc_vsync_start;
|
|
wrptr = udl_set_register_lfsr16(wrptr, 0x05, yds);
|
|
/* y display end */
|
|
yde = yds + mode->crtc_vdisplay;
|
|
wrptr = udl_set_register_lfsr16(wrptr, 0x07, yde);
|
|
|
|
/* x end count is active + blanking - 1 */
|
|
wrptr = udl_set_register_lfsr16(wrptr, 0x09,
|
|
mode->crtc_htotal - 1);
|
|
|
|
/* libdlo hardcodes hsync start to 1 */
|
|
wrptr = udl_set_register_lfsr16(wrptr, 0x0B, 1);
|
|
|
|
/* hsync end is width of sync pulse + 1 */
|
|
wrptr = udl_set_register_lfsr16(wrptr, 0x0D,
|
|
mode->crtc_hsync_end - mode->crtc_hsync_start + 1);
|
|
|
|
/* hpixels is active pixels */
|
|
wrptr = udl_set_register_16(wrptr, 0x0F, mode->hdisplay);
|
|
|
|
/* yendcount is vertical active + vertical blanking */
|
|
yec = mode->crtc_vtotal;
|
|
wrptr = udl_set_register_lfsr16(wrptr, 0x11, yec);
|
|
|
|
/* libdlo hardcodes vsync start to 0 */
|
|
wrptr = udl_set_register_lfsr16(wrptr, 0x13, 0);
|
|
|
|
/* vsync end is width of vsync pulse */
|
|
wrptr = udl_set_register_lfsr16(wrptr, 0x15, mode->crtc_vsync_end - mode->crtc_vsync_start);
|
|
|
|
/* vpixels is active pixels */
|
|
wrptr = udl_set_register_16(wrptr, 0x17, mode->crtc_vdisplay);
|
|
|
|
wrptr = udl_set_register_16be(wrptr, 0x1B,
|
|
mode->clock / 5);
|
|
|
|
return wrptr;
|
|
}
|
|
|
|
static char *udl_dummy_render(char *wrptr)
|
|
{
|
|
*wrptr++ = 0xAF;
|
|
*wrptr++ = 0x6A; /* copy */
|
|
*wrptr++ = 0x00; /* from addr */
|
|
*wrptr++ = 0x00;
|
|
*wrptr++ = 0x00;
|
|
*wrptr++ = 0x01; /* one pixel */
|
|
*wrptr++ = 0x00; /* to address */
|
|
*wrptr++ = 0x00;
|
|
*wrptr++ = 0x00;
|
|
return wrptr;
|
|
}
|
|
|
|
static int udl_crtc_write_mode_to_hw(struct drm_crtc *crtc)
|
|
{
|
|
struct drm_device *dev = crtc->dev;
|
|
struct udl_device *udl = dev->dev_private;
|
|
struct urb *urb;
|
|
char *buf;
|
|
int retval;
|
|
|
|
urb = udl_get_urb(dev);
|
|
if (!urb)
|
|
return -ENOMEM;
|
|
|
|
buf = (char *)urb->transfer_buffer;
|
|
|
|
memcpy(buf, udl->mode_buf, udl->mode_buf_len);
|
|
retval = udl_submit_urb(dev, urb, udl->mode_buf_len);
|
|
DRM_DEBUG("write mode info %d\n", udl->mode_buf_len);
|
|
return retval;
|
|
}
|
|
|
|
|
|
static void udl_crtc_dpms(struct drm_crtc *crtc, int mode)
|
|
{
|
|
struct drm_device *dev = crtc->dev;
|
|
struct udl_device *udl = dev->dev_private;
|
|
int retval;
|
|
|
|
if (mode == DRM_MODE_DPMS_OFF) {
|
|
char *buf;
|
|
struct urb *urb;
|
|
urb = udl_get_urb(dev);
|
|
if (!urb)
|
|
return;
|
|
|
|
buf = (char *)urb->transfer_buffer;
|
|
buf = udl_vidreg_lock(buf);
|
|
buf = udl_set_blank(buf, mode);
|
|
buf = udl_vidreg_unlock(buf);
|
|
|
|
buf = udl_dummy_render(buf);
|
|
retval = udl_submit_urb(dev, urb, buf - (char *)
|
|
urb->transfer_buffer);
|
|
} else {
|
|
if (udl->mode_buf_len == 0) {
|
|
DRM_ERROR("Trying to enable DPMS with no mode\n");
|
|
return;
|
|
}
|
|
udl_crtc_write_mode_to_hw(crtc);
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
* Simple display pipeline
|
|
*/
|
|
|
|
static const uint32_t udl_simple_display_pipe_formats[] = {
|
|
DRM_FORMAT_RGB565,
|
|
DRM_FORMAT_XRGB8888,
|
|
};
|
|
|
|
static enum drm_mode_status
|
|
udl_simple_display_pipe_mode_valid(struct drm_simple_display_pipe *pipe,
|
|
const struct drm_display_mode *mode)
|
|
{
|
|
return MODE_OK;
|
|
}
|
|
|
|
static void
|
|
udl_simple_display_pipe_enable(struct drm_simple_display_pipe *pipe,
|
|
struct drm_crtc_state *crtc_state,
|
|
struct drm_plane_state *plane_state)
|
|
{
|
|
struct drm_crtc *crtc = &pipe->crtc;
|
|
struct drm_device *dev = crtc->dev;
|
|
struct drm_framebuffer *fb = plane_state->fb;
|
|
struct udl_device *udl = dev->dev_private;
|
|
struct drm_display_mode *mode = &crtc_state->mode;
|
|
char *buf;
|
|
char *wrptr;
|
|
int color_depth = UDL_COLOR_DEPTH_16BPP;
|
|
|
|
crtc_state->no_vblank = true;
|
|
|
|
buf = (char *)udl->mode_buf;
|
|
|
|
/* This first section has to do with setting the base address on the
|
|
* controller associated with the display. There are 2 base
|
|
* pointers, currently, we only use the 16 bpp segment.
|
|
*/
|
|
wrptr = udl_vidreg_lock(buf);
|
|
wrptr = udl_set_color_depth(wrptr, color_depth);
|
|
/* set base for 16bpp segment to 0 */
|
|
wrptr = udl_set_base16bpp(wrptr, 0);
|
|
/* set base for 8bpp segment to end of fb */
|
|
wrptr = udl_set_base8bpp(wrptr, 2 * mode->vdisplay * mode->hdisplay);
|
|
|
|
wrptr = udl_set_vid_cmds(wrptr, mode);
|
|
wrptr = udl_set_blank(wrptr, DRM_MODE_DPMS_ON);
|
|
wrptr = udl_vidreg_unlock(wrptr);
|
|
|
|
wrptr = udl_dummy_render(wrptr);
|
|
|
|
spin_lock(&udl->active_fb_16_lock);
|
|
udl->active_fb_16 = fb;
|
|
spin_unlock(&udl->active_fb_16_lock);
|
|
udl->mode_buf_len = wrptr - buf;
|
|
|
|
udl_handle_damage(fb, 0, 0, fb->width, fb->height);
|
|
|
|
udl_crtc_dpms(&pipe->crtc, DRM_MODE_DPMS_ON);
|
|
}
|
|
|
|
static void
|
|
udl_simple_display_pipe_disable(struct drm_simple_display_pipe *pipe)
|
|
{
|
|
udl_crtc_dpms(&pipe->crtc, DRM_MODE_DPMS_OFF);
|
|
}
|
|
|
|
static int
|
|
udl_simple_display_pipe_check(struct drm_simple_display_pipe *pipe,
|
|
struct drm_plane_state *plane_state,
|
|
struct drm_crtc_state *crtc_state)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
udl_simple_display_pipe_update(struct drm_simple_display_pipe *pipe,
|
|
struct drm_plane_state *old_plane_state)
|
|
{
|
|
struct drm_device *dev = pipe->crtc.dev;
|
|
struct udl_device *udl = dev->dev_private;
|
|
struct drm_framebuffer *fb = pipe->plane.state->fb;
|
|
|
|
spin_lock(&udl->active_fb_16_lock);
|
|
udl->active_fb_16 = fb;
|
|
spin_unlock(&udl->active_fb_16_lock);
|
|
|
|
if (!fb)
|
|
return;
|
|
|
|
udl_handle_damage(fb, 0, 0, fb->width, fb->height);
|
|
}
|
|
|
|
static const
|
|
struct drm_simple_display_pipe_funcs udl_simple_display_pipe_funcs = {
|
|
.mode_valid = udl_simple_display_pipe_mode_valid,
|
|
.enable = udl_simple_display_pipe_enable,
|
|
.disable = udl_simple_display_pipe_disable,
|
|
.check = udl_simple_display_pipe_check,
|
|
.update = udl_simple_display_pipe_update,
|
|
.prepare_fb = drm_gem_fb_simple_display_pipe_prepare_fb,
|
|
};
|
|
|
|
/*
|
|
* Modesetting
|
|
*/
|
|
|
|
static const struct drm_mode_config_funcs udl_mode_funcs = {
|
|
.fb_create = udl_fb_user_fb_create,
|
|
.atomic_check = drm_atomic_helper_check,
|
|
.atomic_commit = drm_atomic_helper_commit,
|
|
};
|
|
|
|
int udl_modeset_init(struct drm_device *dev)
|
|
{
|
|
size_t format_count = ARRAY_SIZE(udl_simple_display_pipe_formats);
|
|
struct udl_device *udl = dev->dev_private;
|
|
struct drm_connector *connector;
|
|
int ret;
|
|
|
|
drm_mode_config_init(dev);
|
|
|
|
dev->mode_config.min_width = 640;
|
|
dev->mode_config.min_height = 480;
|
|
|
|
dev->mode_config.max_width = 2048;
|
|
dev->mode_config.max_height = 2048;
|
|
|
|
dev->mode_config.prefer_shadow = 0;
|
|
dev->mode_config.preferred_depth = 24;
|
|
|
|
dev->mode_config.funcs = &udl_mode_funcs;
|
|
|
|
connector = udl_connector_init(dev);
|
|
if (IS_ERR(connector)) {
|
|
ret = PTR_ERR(connector);
|
|
goto err_drm_mode_config_cleanup;
|
|
}
|
|
|
|
format_count = ARRAY_SIZE(udl_simple_display_pipe_formats);
|
|
|
|
ret = drm_simple_display_pipe_init(dev, &udl->display_pipe,
|
|
&udl_simple_display_pipe_funcs,
|
|
udl_simple_display_pipe_formats,
|
|
format_count, NULL, connector);
|
|
if (ret)
|
|
goto err_drm_mode_config_cleanup;
|
|
|
|
drm_mode_config_reset(dev);
|
|
|
|
return 0;
|
|
|
|
err_drm_mode_config_cleanup:
|
|
drm_mode_config_cleanup(dev);
|
|
return ret;
|
|
}
|
|
|
|
void udl_modeset_restore(struct drm_device *dev)
|
|
{
|
|
struct udl_device *udl = dev->dev_private;
|
|
struct drm_crtc *crtc = &udl->display_pipe.crtc;
|
|
struct drm_plane *primary = &udl->display_pipe.plane;
|
|
struct drm_framebuffer *fb = primary->fb;
|
|
|
|
if (!fb)
|
|
return;
|
|
|
|
udl_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
|
|
udl_handle_damage(fb, 0, 0, fb->width, fb->height);
|
|
}
|
|
|
|
void udl_modeset_cleanup(struct drm_device *dev)
|
|
{
|
|
drm_mode_config_cleanup(dev);
|
|
}
|