Commit 40e1a70b authored by Noralf Trønnes's avatar Noralf Trønnes

drm: Add GUD USB Display driver

This adds a USB display driver with the intention that it can be
used with future USB interfaced low end displays/adapters. The Linux
gadget device driver will serve as the canonical device implementation.

The following DRM properties are supported:
- Plane rotation
- Connector TV properties

There is also support for backlight brightness exposed as a backlight
device.

Display modes can be made available to the host driver either as DRM
display modes or through EDID. If both are present, EDID is just passed
on to userspace.

Performance is preferred over color depth, so if the device supports
RGB565, DRM_CAP_DUMB_PREFERRED_DEPTH will return 16.

If the device transfer buffer can't fit an uncompressed framebuffer
update, the update is split up into parts that do fit.

Optimal user experience is achieved by providing damage reports either by
setting FB_DAMAGE_CLIPS on pageflips or calling DRM_IOCTL_MODE_DIRTYFB.

LZ4 compression is used if the device supports it.

The driver supports a one bit monochrome transfer format: R1. This is not
implemented in the gadget driver. It is added in preparation for future
monochrome e-ink displays.

The driver is MIT licensed to smooth the path for any BSD port of the
driver.

v2:
- Use devm_drm_dev_alloc() and drmm_mode_config_init()
- drm_fbdev_generic_setup: Use preferred_bpp=0, 16 was a copy paste error
- The drm_backlight_helper is dropped, copy in the code
- Support protocol version backwards compatibility for device

v3:
- Use donated Openmoko USB pid
- Use direct compression from framebuffer when pitch matches, not only on
  full frames, so split updates can benefit
- Use __le16 in struct gud_drm_req_get_connector_status
- Set edid property when the device only provides edid
- Clear compression fields in struct gud_drm_req_set_buffer
- Fix protocol version negotiation
- Remove mode->vrefresh, it's calculated

v4:
- Drop the status req polling which was a workaround for something that
  turned out to be a dwc2 udc driver problem
- Add a flag for the Linux gadget to require a status request on
  SET operations. Other devices will only get status req on STALL errors
- Use protocol specific error codes (Peter)
- Add a flag for devices that want to receive the entire framebuffer on
  each flush (Lubomir)
- Retry a failed framebuffer flush
- If mode has changed wait for worker and clear pending damage before
  queuing up new damage, fb width/height might have changed
- Increase error counter on bulk transfer failures
- Use DRM_MODE_CONNECTOR_USB
- Handle R1 kmalloc error (Peter)
- Don't try and replicate the USB get descriptor request standard for the
  display descriptor (Peter)
- Make max_buffer_size optional (Peter), drop the pow2 requirement since
  it's not necessary anymore.
- Don't pre-alloc a control request buffer, it was only 4k
- Let gud.h describe the whole protocol explicitly and don't let DRM
  leak into it (Peter)
- Drop display mode .hskew and .vscan from the protocol
- Shorten names: s/GUD_DRM_/GUD_/ s/gud_drm_/gud_/ (Peter)
- Fix gud_pipe_check() connector picking when switching connector
- Drop gud_drm_driver_gem_create_object() cached is default now
- Retrieve USB device from struct drm_device.dev instead of keeping a
  pointer
- Honour fb->offsets[0]
- Fix mode fetching when connector status is forced
- Check EDID length reported by the device
- Use drm_do_get_edid() so userspace can overrride EDID
- Set epoch counter to signal connector status change
- gud_drm_driver can be const now

v5:
- GUD_DRM_FORMAT_R1: Use non-human ascii values (Daniel)
- Change name to: GUD USB Display (Thomas, Simon)
- Change one __u32 -> __le32 in protocol header
- Always log fb flush errors, unless the previous one failed
- Run backlight update in a worker to avoid upsetting lockdep (Daniel)
- Drop backlight_ops.get_brightness, there's no readback from the device
  so it doesn't really add anything.
- Set dma mask, needed by dma-buf importers

v6:
- Use obj-y in Makefile (Peter)
- Fix missing le32_to_cpu() when using GUD_DISPLAY_MAGIC (Peter)
- Set initial brightness on backlight device

v7:
- LZ4_compress_default() can return zero, check for that
- Fix memory leak in gud_pipe_check() error path (Peter)
- Improve debug and error messages (Peter)
- Don't pass length in protocol structs (Peter)
- Pass USB interface to gud_usb_control_msg() et al. (Peter)
- Improve gud_connector_fill_properties() (Peter)
- Add GUD_PIXEL_FORMAT_RGB111 (Peter)
- Remove GUD_REQ_SET_VERSION (Peter)
- Fix DRM_IOCTL_MODE_OBJ_SETPROPERTY and the rotation property
- Fix dma-buf import (Thomas)

v8:
- Forgot to filter RGB111 from reaching userspace
- Handle a device that only returns unknown device properties (Peter)
- s/GUD_PIXEL_FORMAT_RGB111/GUD_PIXEL_FORMAT_XRGB1111/ (Peter)
- Fix R1 and XRGB1111 format conversion
- Add FIXME about Big Endian being broken (Peter, Ilia)

Cc: Lubomir Rintel <lkundrak@v3.sk>
Acked-by: default avatarDaniel Vetter <daniel.vetter@ffwll.ch>
Reviewed-by: default avatarPeter Stuge <peter@stuge.se>
Tested-by: default avatarPeter Stuge <peter@stuge.se>
Signed-off-by: default avatarNoralf Trønnes <noralf@tronnes.org>
Link: https://patchwork.freedesktop.org/patch/msgid/20210313112545.37527-4-noralf@tronnes.org
parent dc659a4e
......@@ -5586,6 +5586,14 @@ S: Maintained
F: Documentation/devicetree/bindings/display/panel/feiyang,fy07024di26a30d.yaml
F: drivers/gpu/drm/panel/panel-feiyang-fy07024di26a30d.c
DRM DRIVER FOR GENERIC USB DISPLAY
M: Noralf Trønnes <noralf@tronnes.org>
S: Maintained
W: https://github.com/notro/gud/wiki
T: git git://anongit.freedesktop.org/drm/drm-misc
F: drivers/gpu/drm/gud/
F: include/drm/gud.h
DRM DRIVER FOR GRAIN MEDIA GM12U320 PROJECTORS
M: Hans de Goede <hdegoede@redhat.com>
S: Maintained
......
......@@ -384,6 +384,8 @@ source "drivers/gpu/drm/tidss/Kconfig"
source "drivers/gpu/drm/xlnx/Kconfig"
source "drivers/gpu/drm/gud/Kconfig"
# Keep legacy drivers last
menuconfig DRM_LEGACY
......
......@@ -125,3 +125,4 @@ obj-$(CONFIG_DRM_ASPEED_GFX) += aspeed/
obj-$(CONFIG_DRM_MCDE) += mcde/
obj-$(CONFIG_DRM_TIDSS) += tidss/
obj-y += xlnx/
obj-y += gud/
# SPDX-License-Identifier: GPL-2.0
config DRM_GUD
tristate "GUD USB Display"
depends on DRM && USB
select LZ4_COMPRESS
select DRM_KMS_HELPER
select DRM_GEM_SHMEM_HELPER
select BACKLIGHT_CLASS_DEVICE
help
This is a DRM display driver for GUD USB Displays or display
adapters.
If M is selected the module will be called gud.
# SPDX-License-Identifier: GPL-2.0
gud-y := gud_drv.o gud_pipe.o gud_connector.o
obj-$(CONFIG_DRM_GUD) += gud.o
This diff is collapsed.
This diff is collapsed.
/* SPDX-License-Identifier: MIT */
#ifndef __LINUX_GUD_INTERNAL_H
#define __LINUX_GUD_INTERNAL_H
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/usb.h>
#include <linux/workqueue.h>
#include <uapi/drm/drm_fourcc.h>
#include <drm/drm_modes.h>
#include <drm/drm_simple_kms_helper.h>
struct gud_device {
struct drm_device drm;
struct drm_simple_display_pipe pipe;
struct device *dmadev;
struct work_struct work;
u32 flags;
const struct drm_format_info *xrgb8888_emulation_format;
u16 *properties;
unsigned int num_properties;
unsigned int bulk_pipe;
void *bulk_buf;
size_t bulk_len;
u8 compression;
void *lz4_comp_mem;
void *compress_buf;
u64 stats_length;
u64 stats_actual_length;
unsigned int stats_num_errors;
struct mutex ctrl_lock; /* Serialize get/set and status transfers */
struct mutex damage_lock; /* Protects the following members: */
struct drm_framebuffer *fb;
struct drm_rect damage;
bool prev_flush_failed;
};
static inline struct gud_device *to_gud_device(struct drm_device *drm)
{
return container_of(drm, struct gud_device, drm);
}
static inline struct usb_device *gud_to_usb_device(struct gud_device *gdrm)
{
return interface_to_usbdev(to_usb_interface(gdrm->drm.dev));
}
int gud_usb_get(struct gud_device *gdrm, u8 request, u16 index, void *buf, size_t len);
int gud_usb_set(struct gud_device *gdrm, u8 request, u16 index, void *buf, size_t len);
int gud_usb_get_u8(struct gud_device *gdrm, u8 request, u16 index, u8 *val);
int gud_usb_set_u8(struct gud_device *gdrm, u8 request, u8 val);
void gud_clear_damage(struct gud_device *gdrm);
void gud_flush_work(struct work_struct *work);
int gud_pipe_check(struct drm_simple_display_pipe *pipe,
struct drm_plane_state *new_plane_state,
struct drm_crtc_state *new_crtc_state);
void gud_pipe_update(struct drm_simple_display_pipe *pipe,
struct drm_plane_state *old_state);
int gud_connector_fill_properties(struct drm_connector_state *connector_state,
struct gud_property_req *properties);
int gud_get_connectors(struct gud_device *gdrm);
/* Driver internal fourcc transfer formats */
#define GUD_DRM_FORMAT_R1 0x00000122
#define GUD_DRM_FORMAT_XRGB1111 0x03121722
static inline u8 gud_from_fourcc(u32 fourcc)
{
switch (fourcc) {
case GUD_DRM_FORMAT_R1:
return GUD_PIXEL_FORMAT_R1;
case GUD_DRM_FORMAT_XRGB1111:
return GUD_PIXEL_FORMAT_XRGB1111;
case DRM_FORMAT_RGB565:
return GUD_PIXEL_FORMAT_RGB565;
case DRM_FORMAT_XRGB8888:
return GUD_PIXEL_FORMAT_XRGB8888;
case DRM_FORMAT_ARGB8888:
return GUD_PIXEL_FORMAT_ARGB8888;
};
return 0;
}
static inline u32 gud_to_fourcc(u8 format)
{
switch (format) {
case GUD_PIXEL_FORMAT_R1:
return GUD_DRM_FORMAT_R1;
case GUD_PIXEL_FORMAT_XRGB1111:
return GUD_DRM_FORMAT_XRGB1111;
case GUD_PIXEL_FORMAT_RGB565:
return DRM_FORMAT_RGB565;
case GUD_PIXEL_FORMAT_XRGB8888:
return DRM_FORMAT_XRGB8888;
case GUD_PIXEL_FORMAT_ARGB8888:
return DRM_FORMAT_ARGB8888;
};
return 0;
}
static inline void gud_from_display_mode(struct gud_display_mode_req *dst,
const struct drm_display_mode *src)
{
u32 flags = src->flags & GUD_DISPLAY_MODE_FLAG_USER_MASK;
if (src->type & DRM_MODE_TYPE_PREFERRED)
flags |= GUD_DISPLAY_MODE_FLAG_PREFERRED;
dst->clock = cpu_to_le32(src->clock);
dst->hdisplay = cpu_to_le16(src->hdisplay);
dst->hsync_start = cpu_to_le16(src->hsync_start);
dst->hsync_end = cpu_to_le16(src->hsync_end);
dst->htotal = cpu_to_le16(src->htotal);
dst->vdisplay = cpu_to_le16(src->vdisplay);
dst->vsync_start = cpu_to_le16(src->vsync_start);
dst->vsync_end = cpu_to_le16(src->vsync_end);
dst->vtotal = cpu_to_le16(src->vtotal);
dst->flags = cpu_to_le32(flags);
}
static inline void gud_to_display_mode(struct drm_display_mode *dst,
const struct gud_display_mode_req *src)
{
u32 flags = le32_to_cpu(src->flags);
memset(dst, 0, sizeof(*dst));
dst->clock = le32_to_cpu(src->clock);
dst->hdisplay = le16_to_cpu(src->hdisplay);
dst->hsync_start = le16_to_cpu(src->hsync_start);
dst->hsync_end = le16_to_cpu(src->hsync_end);
dst->htotal = le16_to_cpu(src->htotal);
dst->vdisplay = le16_to_cpu(src->vdisplay);
dst->vsync_start = le16_to_cpu(src->vsync_start);
dst->vsync_end = le16_to_cpu(src->vsync_end);
dst->vtotal = le16_to_cpu(src->vtotal);
dst->flags = flags & GUD_DISPLAY_MODE_FLAG_USER_MASK;
dst->type = DRM_MODE_TYPE_DRIVER;
if (flags & GUD_DISPLAY_MODE_FLAG_PREFERRED)
dst->type |= DRM_MODE_TYPE_PREFERRED;
drm_mode_set_name(dst);
}
#endif
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment