diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:27:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:27:49 +0000 |
commit | ace9429bb58fd418f0c81d4c2835699bddf6bde6 (patch) | |
tree | b2d64bc10158fdd5497876388cd68142ca374ed3 /drivers/gpu/drm/udl | |
parent | Initial commit. (diff) | |
download | linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.tar.xz linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.zip |
Adding upstream version 6.6.15.upstream/6.6.15
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/gpu/drm/udl')
-rw-r--r-- | drivers/gpu/drm/udl/Kconfig | 12 | ||||
-rw-r--r-- | drivers/gpu/drm/udl/Makefile | 4 | ||||
-rw-r--r-- | drivers/gpu/drm/udl/udl_drv.c | 163 | ||||
-rw-r--r-- | drivers/gpu/drm/udl/udl_drv.h | 105 | ||||
-rw-r--r-- | drivers/gpu/drm/udl/udl_main.c | 360 | ||||
-rw-r--r-- | drivers/gpu/drm/udl/udl_modeset.c | 605 | ||||
-rw-r--r-- | drivers/gpu/drm/udl/udl_proto.h | 68 | ||||
-rw-r--r-- | drivers/gpu/drm/udl/udl_transfer.c | 217 |
8 files changed, 1534 insertions, 0 deletions
diff --git a/drivers/gpu/drm/udl/Kconfig b/drivers/gpu/drm/udl/Kconfig new file mode 100644 index 0000000000..c744175c69 --- /dev/null +++ b/drivers/gpu/drm/udl/Kconfig @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DRM_UDL + tristate "DisplayLink" + depends on DRM + depends on USB + depends on USB_ARCH_HAS_HCD + depends on MMU + select DRM_GEM_SHMEM_HELPER + select DRM_KMS_HELPER + help + This is a KMS driver for the USB displaylink video adapters. + Say M/Y to add support for these devices via drm/kms interfaces. diff --git a/drivers/gpu/drm/udl/Makefile b/drivers/gpu/drm/udl/Makefile new file mode 100644 index 0000000000..3f6db17945 --- /dev/null +++ b/drivers/gpu/drm/udl/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +udl-y := udl_drv.o udl_modeset.o udl_main.o udl_transfer.o + +obj-$(CONFIG_DRM_UDL) := udl.o diff --git a/drivers/gpu/drm/udl/udl_drv.c b/drivers/gpu/drm/udl/udl_drv.c new file mode 100644 index 0000000000..1506094a80 --- /dev/null +++ b/drivers/gpu/drm/udl/udl_drv.c @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2012 Red Hat + */ + +#include <linux/module.h> + +#include <drm/drm_drv.h> +#include <drm/drm_fbdev_generic.h> +#include <drm/drm_file.h> +#include <drm/drm_gem_shmem_helper.h> +#include <drm/drm_managed.h> +#include <drm/drm_modeset_helper.h> +#include <drm/drm_ioctl.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_print.h> + +#include "udl_drv.h" + +static int udl_usb_suspend(struct usb_interface *interface, + pm_message_t message) +{ + struct drm_device *dev = usb_get_intfdata(interface); + int ret; + + ret = drm_mode_config_helper_suspend(dev); + if (ret) + return ret; + + udl_sync_pending_urbs(dev); + return 0; +} + +static int udl_usb_resume(struct usb_interface *interface) +{ + struct drm_device *dev = usb_get_intfdata(interface); + + return drm_mode_config_helper_resume(dev); +} + +static int udl_usb_reset_resume(struct usb_interface *interface) +{ + struct drm_device *dev = usb_get_intfdata(interface); + struct udl_device *udl = to_udl(dev); + + udl_select_std_channel(udl); + + return drm_mode_config_helper_resume(dev); +} + +/* + * FIXME: Dma-buf sharing requires DMA support by the importing device. + * This function is a workaround to make USB devices work as well. + * See todo.rst for how to fix the issue in the dma-buf framework. + */ +static struct drm_gem_object *udl_driver_gem_prime_import(struct drm_device *dev, + struct dma_buf *dma_buf) +{ + struct udl_device *udl = to_udl(dev); + + if (!udl->dmadev) + return ERR_PTR(-ENODEV); + + return drm_gem_prime_import_dev(dev, dma_buf, udl->dmadev); +} + +DEFINE_DRM_GEM_FOPS(udl_driver_fops); + +static const struct drm_driver driver = { + .driver_features = DRIVER_ATOMIC | DRIVER_GEM | DRIVER_MODESET, + + /* GEM hooks */ + .fops = &udl_driver_fops, + DRM_GEM_SHMEM_DRIVER_OPS, + .gem_prime_import = udl_driver_gem_prime_import, + + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .date = DRIVER_DATE, + .major = DRIVER_MAJOR, + .minor = DRIVER_MINOR, + .patchlevel = DRIVER_PATCHLEVEL, +}; + +static struct udl_device *udl_driver_create(struct usb_interface *interface) +{ + struct udl_device *udl; + int r; + + udl = devm_drm_dev_alloc(&interface->dev, &driver, + struct udl_device, drm); + if (IS_ERR(udl)) + return udl; + + r = udl_init(udl); + if (r) + return ERR_PTR(r); + + usb_set_intfdata(interface, udl); + + return udl; +} + +static int udl_usb_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + int r; + struct udl_device *udl; + + udl = udl_driver_create(interface); + if (IS_ERR(udl)) + return PTR_ERR(udl); + + r = drm_dev_register(&udl->drm, 0); + if (r) + return r; + + DRM_INFO("Initialized udl on minor %d\n", udl->drm.primary->index); + + drm_fbdev_generic_setup(&udl->drm, 0); + + return 0; +} + +static void udl_usb_disconnect(struct usb_interface *interface) +{ + struct drm_device *dev = usb_get_intfdata(interface); + + drm_kms_helper_poll_fini(dev); + udl_drop_usb(dev); + drm_dev_unplug(dev); +} + +/* + * There are many DisplayLink-based graphics products, all with unique PIDs. + * So we match on DisplayLink's VID + Vendor-Defined Interface Class (0xff) + * We also require a match on SubClass (0x00) and Protocol (0x00), + * which is compatible with all known USB 2.0 era graphics chips and firmware, + * but allows DisplayLink to increment those for any future incompatible chips + */ +static const struct usb_device_id id_table[] = { + {.idVendor = 0x17e9, .bInterfaceClass = 0xff, + .bInterfaceSubClass = 0x00, + .bInterfaceProtocol = 0x00, + .match_flags = USB_DEVICE_ID_MATCH_VENDOR | + USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS | + USB_DEVICE_ID_MATCH_INT_PROTOCOL,}, + {}, +}; +MODULE_DEVICE_TABLE(usb, id_table); + +static struct usb_driver udl_driver = { + .name = "udl", + .probe = udl_usb_probe, + .disconnect = udl_usb_disconnect, + .suspend = udl_usb_suspend, + .resume = udl_usb_resume, + .reset_resume = udl_usb_reset_resume, + .id_table = id_table, +}; +module_usb_driver(udl_driver); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/udl/udl_drv.h b/drivers/gpu/drm/udl/udl_drv.h new file mode 100644 index 0000000000..282ebd6c02 --- /dev/null +++ b/drivers/gpu/drm/udl/udl_drv.h @@ -0,0 +1,105 @@ +/* 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> + */ + +#ifndef UDL_DRV_H +#define UDL_DRV_H + +#include <linux/mm_types.h> +#include <linux/usb.h> + +#include <drm/drm_connector.h> +#include <drm/drm_crtc.h> +#include <drm/drm_device.h> +#include <drm/drm_encoder.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_gem.h> +#include <drm/drm_plane.h> + +struct drm_mode_create_dumb; + +#define DRIVER_NAME "udl" +#define DRIVER_DESC "DisplayLink" +#define DRIVER_DATE "20120220" + +#define DRIVER_MAJOR 0 +#define DRIVER_MINOR 0 +#define DRIVER_PATCHLEVEL 1 + +struct udl_device; + +struct urb_node { + struct list_head entry; + struct udl_device *dev; + struct urb *urb; +}; + +struct urb_list { + struct list_head list; + spinlock_t lock; + wait_queue_head_t sleep; + int available; + int count; + size_t size; +}; + +struct udl_connector { + struct drm_connector connector; + /* last udl_detect edid */ + struct edid *edid; +}; + +static inline struct udl_connector *to_udl_connector(struct drm_connector *connector) +{ + return container_of(connector, struct udl_connector, connector); +} + +struct udl_device { + struct drm_device drm; + struct device *dev; + struct device *dmadev; + + struct drm_plane primary_plane; + struct drm_crtc crtc; + struct drm_encoder encoder; + + struct mutex gem_lock; + + int sku_pixel_limit; + + struct urb_list urbs; +}; + +#define to_udl(x) container_of(x, struct udl_device, drm) + +static inline struct usb_device *udl_to_usb_device(struct udl_device *udl) +{ + return interface_to_usbdev(to_usb_interface(udl->drm.dev)); +} + +/* modeset */ +int udl_modeset_init(struct drm_device *dev); +struct drm_connector *udl_connector_init(struct drm_device *dev); + +struct urb *udl_get_urb(struct drm_device *dev); + +int udl_submit_urb(struct drm_device *dev, struct urb *urb, size_t len); +void udl_sync_pending_urbs(struct drm_device *dev); +void udl_urb_completion(struct urb *urb); + +int udl_init(struct udl_device *udl); + +int udl_render_hline(struct drm_device *dev, int log_bpp, struct urb **urb_ptr, + const char *front, char **urb_buf_ptr, + u32 byte_offset, u32 device_byte_offset, u32 byte_width); + +int udl_drop_usb(struct drm_device *dev); +int udl_select_std_channel(struct udl_device *udl); + +#endif diff --git a/drivers/gpu/drm/udl/udl_main.c b/drivers/gpu/drm/udl/udl_main.c new file mode 100644 index 0000000000..3ebe2ce55d --- /dev/null +++ b/drivers/gpu/drm/udl/udl_main.c @@ -0,0 +1,360 @@ +// 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.h> +#include <drm/drm_print.h> +#include <drm/drm_probe_helper.h> + +#include "udl_drv.h" + +/* -BULK_SIZE as per usb-skeleton. Can we get full page and avoid overhead? */ +#define BULK_SIZE 512 + +#define NR_USB_REQUEST_CHANNEL 0x12 + +#define MAX_TRANSFER (PAGE_SIZE*16 - BULK_SIZE) +#define WRITES_IN_FLIGHT (20) +#define MAX_VENDOR_DESCRIPTOR_SIZE 256 + +static struct urb *udl_get_urb_locked(struct udl_device *udl, long timeout); + +static int udl_parse_vendor_descriptor(struct udl_device *udl) +{ + struct usb_device *udev = udl_to_usb_device(udl); + char *desc; + char *buf; + char *desc_end; + + u8 total_len = 0; + + buf = kzalloc(MAX_VENDOR_DESCRIPTOR_SIZE, GFP_KERNEL); + if (!buf) + return false; + desc = buf; + + total_len = usb_get_descriptor(udev, 0x5f, /* vendor specific */ + 0, desc, MAX_VENDOR_DESCRIPTOR_SIZE); + if (total_len > 5) { + DRM_INFO("vendor descriptor length:%x data:%11ph\n", + total_len, desc); + + if ((desc[0] != total_len) || /* descriptor length */ + (desc[1] != 0x5f) || /* vendor descriptor type */ + (desc[2] != 0x01) || /* version (2 bytes) */ + (desc[3] != 0x00) || + (desc[4] != total_len - 2)) /* length after type */ + goto unrecognized; + + desc_end = desc + total_len; + desc += 5; /* the fixed header we've already parsed */ + + while (desc < desc_end) { + u8 length; + u16 key; + + key = le16_to_cpu(*((u16 *) desc)); + desc += sizeof(u16); + length = *desc; + desc++; + + switch (key) { + case 0x0200: { /* max_area */ + u32 max_area; + max_area = le32_to_cpu(*((u32 *)desc)); + DRM_DEBUG("DL chip limited to %d pixel modes\n", + max_area); + udl->sku_pixel_limit = max_area; + break; + } + default: + break; + } + desc += length; + } + } + + goto success; + +unrecognized: + /* allow udlfb to load for now even if firmware unrecognized */ + DRM_ERROR("Unrecognized vendor firmware descriptor\n"); + +success: + kfree(buf); + return true; +} + +/* + * Need to ensure a channel is selected before submitting URBs + */ +int udl_select_std_channel(struct udl_device *udl) +{ + static const u8 set_def_chn[] = {0x57, 0xCD, 0xDC, 0xA7, + 0x1C, 0x88, 0x5E, 0x15, + 0x60, 0xFE, 0xC6, 0x97, + 0x16, 0x3D, 0x47, 0xF2}; + + void *sendbuf; + int ret; + struct usb_device *udev = udl_to_usb_device(udl); + + sendbuf = kmemdup(set_def_chn, sizeof(set_def_chn), GFP_KERNEL); + if (!sendbuf) + return -ENOMEM; + + ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + NR_USB_REQUEST_CHANNEL, + (USB_DIR_OUT | USB_TYPE_VENDOR), 0, 0, + sendbuf, sizeof(set_def_chn), + USB_CTRL_SET_TIMEOUT); + kfree(sendbuf); + return ret < 0 ? ret : 0; +} + +void udl_urb_completion(struct urb *urb) +{ + struct urb_node *unode = urb->context; + struct udl_device *udl = unode->dev; + unsigned long flags; + + /* sync/async unlink faults aren't errors */ + if (urb->status) { + if (!(urb->status == -ENOENT || + urb->status == -ECONNRESET || + urb->status == -EPROTO || + urb->status == -ESHUTDOWN)) { + DRM_ERROR("%s - nonzero write bulk status received: %d\n", + __func__, urb->status); + } + } + + urb->transfer_buffer_length = udl->urbs.size; /* reset to actual */ + + spin_lock_irqsave(&udl->urbs.lock, flags); + list_add_tail(&unode->entry, &udl->urbs.list); + udl->urbs.available++; + spin_unlock_irqrestore(&udl->urbs.lock, flags); + + wake_up(&udl->urbs.sleep); +} + +static void udl_free_urb_list(struct drm_device *dev) +{ + struct udl_device *udl = to_udl(dev); + struct urb_node *unode; + struct urb *urb; + + DRM_DEBUG("Waiting for completes and freeing all render urbs\n"); + + /* keep waiting and freeing, until we've got 'em all */ + while (udl->urbs.count) { + spin_lock_irq(&udl->urbs.lock); + urb = udl_get_urb_locked(udl, MAX_SCHEDULE_TIMEOUT); + udl->urbs.count--; + spin_unlock_irq(&udl->urbs.lock); + if (WARN_ON(!urb)) + break; + unode = urb->context; + /* Free each separately allocated piece */ + usb_free_coherent(urb->dev, udl->urbs.size, + urb->transfer_buffer, urb->transfer_dma); + usb_free_urb(urb); + kfree(unode); + } + + wake_up_all(&udl->urbs.sleep); +} + +static int udl_alloc_urb_list(struct drm_device *dev, int count, size_t size) +{ + struct udl_device *udl = to_udl(dev); + struct urb *urb; + struct urb_node *unode; + char *buf; + size_t wanted_size = count * size; + struct usb_device *udev = udl_to_usb_device(udl); + + spin_lock_init(&udl->urbs.lock); + INIT_LIST_HEAD(&udl->urbs.list); + init_waitqueue_head(&udl->urbs.sleep); + udl->urbs.count = 0; + udl->urbs.available = 0; + +retry: + udl->urbs.size = size; + + while (udl->urbs.count * size < wanted_size) { + unode = kzalloc(sizeof(struct urb_node), GFP_KERNEL); + if (!unode) + break; + unode->dev = udl; + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + kfree(unode); + break; + } + unode->urb = urb; + + buf = usb_alloc_coherent(udev, size, GFP_KERNEL, + &urb->transfer_dma); + if (!buf) { + kfree(unode); + usb_free_urb(urb); + if (size > PAGE_SIZE) { + size /= 2; + udl_free_urb_list(dev); + goto retry; + } + break; + } + + /* urb->transfer_buffer_length set to actual before submit */ + usb_fill_bulk_urb(urb, udev, usb_sndbulkpipe(udev, 1), + buf, size, udl_urb_completion, unode); + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + list_add_tail(&unode->entry, &udl->urbs.list); + + udl->urbs.count++; + udl->urbs.available++; + } + + DRM_DEBUG("allocated %d %d byte urbs\n", udl->urbs.count, (int) size); + + return udl->urbs.count; +} + +static struct urb *udl_get_urb_locked(struct udl_device *udl, long timeout) +{ + struct urb_node *unode; + + assert_spin_locked(&udl->urbs.lock); + + /* Wait for an in-flight buffer to complete and get re-queued */ + if (!wait_event_lock_irq_timeout(udl->urbs.sleep, + !udl->urbs.count || + !list_empty(&udl->urbs.list), + udl->urbs.lock, timeout)) { + DRM_INFO("wait for urb interrupted: available: %d\n", + udl->urbs.available); + return NULL; + } + + if (!udl->urbs.count) + return NULL; + + unode = list_first_entry(&udl->urbs.list, struct urb_node, entry); + list_del_init(&unode->entry); + udl->urbs.available--; + + return unode->urb; +} + +#define GET_URB_TIMEOUT HZ +struct urb *udl_get_urb(struct drm_device *dev) +{ + struct udl_device *udl = to_udl(dev); + struct urb *urb; + + spin_lock_irq(&udl->urbs.lock); + urb = udl_get_urb_locked(udl, GET_URB_TIMEOUT); + spin_unlock_irq(&udl->urbs.lock); + return urb; +} + +int udl_submit_urb(struct drm_device *dev, struct urb *urb, size_t len) +{ + struct udl_device *udl = to_udl(dev); + int ret; + + if (WARN_ON(len > udl->urbs.size)) { + ret = -EINVAL; + goto error; + } + urb->transfer_buffer_length = len; /* set to actual payload len */ + ret = usb_submit_urb(urb, GFP_ATOMIC); + error: + if (ret) { + udl_urb_completion(urb); /* because no one else will */ + DRM_ERROR("usb_submit_urb error %x\n", ret); + } + return ret; +} + +/* wait until all pending URBs have been processed */ +void udl_sync_pending_urbs(struct drm_device *dev) +{ + struct udl_device *udl = to_udl(dev); + + spin_lock_irq(&udl->urbs.lock); + /* 2 seconds as a sane timeout */ + if (!wait_event_lock_irq_timeout(udl->urbs.sleep, + udl->urbs.available == udl->urbs.count, + udl->urbs.lock, + msecs_to_jiffies(2000))) + drm_err(dev, "Timeout for syncing pending URBs\n"); + spin_unlock_irq(&udl->urbs.lock); +} + +int udl_init(struct udl_device *udl) +{ + struct drm_device *dev = &udl->drm; + int ret = -ENOMEM; + + DRM_DEBUG("\n"); + + udl->dmadev = usb_intf_get_dma_device(to_usb_interface(dev->dev)); + if (!udl->dmadev) + drm_warn(dev, "buffer sharing not supported"); /* not an error */ + + mutex_init(&udl->gem_lock); + + if (!udl_parse_vendor_descriptor(udl)) { + ret = -ENODEV; + DRM_ERROR("firmware not recognized. Assume incompatible device\n"); + goto err; + } + + if (udl_select_std_channel(udl)) + DRM_ERROR("Selecting channel failed\n"); + + if (!udl_alloc_urb_list(dev, WRITES_IN_FLIGHT, MAX_TRANSFER)) { + DRM_ERROR("udl_alloc_urb_list failed\n"); + goto err; + } + + DRM_DEBUG("\n"); + ret = udl_modeset_init(dev); + if (ret) + goto err; + + drm_kms_helper_poll_init(dev); + + return 0; + +err: + if (udl->urbs.count) + udl_free_urb_list(dev); + put_device(udl->dmadev); + DRM_ERROR("%d\n", ret); + return ret; +} + +int udl_drop_usb(struct drm_device *dev) +{ + struct udl_device *udl = to_udl(dev); + + udl_free_urb_list(dev); + put_device(udl->dmadev); + udl->dmadev = NULL; + + return 0; +} diff --git a/drivers/gpu/drm/udl/udl_modeset.c b/drivers/gpu/drm/udl/udl_modeset.c new file mode 100644 index 0000000000..40876bcdd7 --- /dev/null +++ b/drivers/gpu/drm/udl/udl_modeset.c @@ -0,0 +1,605 @@ +// 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 <linux/bitfield.h> + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_damage_helper.h> +#include <drm/drm_drv.h> +#include <drm/drm_edid.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_gem_atomic_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_gem_shmem_helper.h> +#include <drm/drm_modeset_helper_vtables.h> +#include <drm/drm_plane_helper.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_vblank.h> + +#include "udl_drv.h" +#include "udl_proto.h" + +/* + * All DisplayLink bulk operations start with 0xaf (UDL_MSG_BULK), followed by + * a specific command code. All operations are written to a command buffer, which + * the driver sends to the device. + */ +static char *udl_set_register(char *buf, u8 reg, u8 val) +{ + *buf++ = UDL_MSG_BULK; + *buf++ = UDL_CMD_WRITEREG; + *buf++ = reg; + *buf++ = val; + + return buf; +} + +static char *udl_vidreg_lock(char *buf) +{ + return udl_set_register(buf, UDL_REG_VIDREG, UDL_VIDREG_LOCK); +} + +static char *udl_vidreg_unlock(char *buf) +{ + return udl_set_register(buf, UDL_REG_VIDREG, UDL_VIDREG_UNLOCK); +} + +static char *udl_set_blank_mode(char *buf, u8 mode) +{ + return udl_set_register(buf, UDL_REG_BLANKMODE, mode); +} + +static char *udl_set_color_depth(char *buf, u8 selection) +{ + return udl_set_register(buf, UDL_REG_COLORDEPTH, selection); +} + +static char *udl_set_base16bpp(char *buf, u32 base) +{ + /* the base pointer is 24 bits wide, 0x20 is hi byte. */ + u8 reg20 = FIELD_GET(UDL_BASE_ADDR2_MASK, base); + u8 reg21 = FIELD_GET(UDL_BASE_ADDR1_MASK, base); + u8 reg22 = FIELD_GET(UDL_BASE_ADDR0_MASK, base); + + buf = udl_set_register(buf, UDL_REG_BASE16BPP_ADDR2, reg20); + buf = udl_set_register(buf, UDL_REG_BASE16BPP_ADDR1, reg21); + buf = udl_set_register(buf, UDL_REG_BASE16BPP_ADDR0, reg22); + + return buf; +} + +/* + * 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 *buf, u32 base) +{ + /* the base pointer is 24 bits wide, 0x26 is hi byte. */ + u8 reg26 = FIELD_GET(UDL_BASE_ADDR2_MASK, base); + u8 reg27 = FIELD_GET(UDL_BASE_ADDR1_MASK, base); + u8 reg28 = FIELD_GET(UDL_BASE_ADDR0_MASK, base); + + buf = udl_set_register(buf, UDL_REG_BASE8BPP_ADDR2, reg26); + buf = udl_set_register(buf, UDL_REG_BASE8BPP_ADDR1, reg27); + buf = udl_set_register(buf, UDL_REG_BASE8BPP_ADDR0, reg28); + + return buf; +} + +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)); +} + +/* + * Takes a DRM display mode and converts it into the DisplayLink + * equivalent register commands. + */ +static char *udl_set_display_mode(char *buf, struct drm_display_mode *mode) +{ + u16 reg01 = mode->crtc_htotal - mode->crtc_hsync_start; + u16 reg03 = reg01 + mode->crtc_hdisplay; + u16 reg05 = mode->crtc_vtotal - mode->crtc_vsync_start; + u16 reg07 = reg05 + mode->crtc_vdisplay; + u16 reg09 = mode->crtc_htotal - 1; + u16 reg0b = 1; /* libdlo hardcodes hsync start to 1 */ + u16 reg0d = mode->crtc_hsync_end - mode->crtc_hsync_start + 1; + u16 reg0f = mode->hdisplay; + u16 reg11 = mode->crtc_vtotal; + u16 reg13 = 0; /* libdlo hardcodes vsync start to 0 */ + u16 reg15 = mode->crtc_vsync_end - mode->crtc_vsync_start; + u16 reg17 = mode->crtc_vdisplay; + u16 reg1b = mode->clock / 5; + + buf = udl_set_register_lfsr16(buf, UDL_REG_XDISPLAYSTART, reg01); + buf = udl_set_register_lfsr16(buf, UDL_REG_XDISPLAYEND, reg03); + buf = udl_set_register_lfsr16(buf, UDL_REG_YDISPLAYSTART, reg05); + buf = udl_set_register_lfsr16(buf, UDL_REG_YDISPLAYEND, reg07); + buf = udl_set_register_lfsr16(buf, UDL_REG_XENDCOUNT, reg09); + buf = udl_set_register_lfsr16(buf, UDL_REG_HSYNCSTART, reg0b); + buf = udl_set_register_lfsr16(buf, UDL_REG_HSYNCEND, reg0d); + buf = udl_set_register_16(buf, UDL_REG_HPIXELS, reg0f); + buf = udl_set_register_lfsr16(buf, UDL_REG_YENDCOUNT, reg11); + buf = udl_set_register_lfsr16(buf, UDL_REG_VSYNCSTART, reg13); + buf = udl_set_register_lfsr16(buf, UDL_REG_VSYNCEND, reg15); + buf = udl_set_register_16(buf, UDL_REG_VPIXELS, reg17); + buf = udl_set_register_16be(buf, UDL_REG_PIXELCLOCK5KHZ, reg1b); + + return buf; +} + +static char *udl_dummy_render(char *wrptr) +{ + *wrptr++ = UDL_MSG_BULK; + *wrptr++ = UDL_CMD_WRITECOPY16; + *wrptr++ = 0x00; /* from addr */ + *wrptr++ = 0x00; + *wrptr++ = 0x00; + *wrptr++ = 0x01; /* one pixel */ + *wrptr++ = 0x00; /* to address */ + *wrptr++ = 0x00; + *wrptr++ = 0x00; + return wrptr; +} + +static long udl_log_cpp(unsigned int cpp) +{ + if (WARN_ON(!is_power_of_2(cpp))) + return -EINVAL; + return __ffs(cpp); +} + +static int udl_handle_damage(struct drm_framebuffer *fb, + const struct iosys_map *map, + const struct drm_rect *clip) +{ + struct drm_device *dev = fb->dev; + void *vaddr = map->vaddr; /* TODO: Use mapping abstraction properly */ + int i, ret; + char *cmd; + struct urb *urb; + int log_bpp; + + ret = udl_log_cpp(fb->format->cpp[0]); + if (ret < 0) + return ret; + log_bpp = ret; + + urb = udl_get_urb(dev); + if (!urb) + return -ENOMEM; + cmd = urb->transfer_buffer; + + for (i = clip->y1; i < clip->y2; i++) { + const int line_offset = fb->pitches[0] * i; + const int byte_offset = line_offset + (clip->x1 << log_bpp); + const int dev_byte_offset = (fb->width * i + clip->x1) << log_bpp; + const int byte_width = drm_rect_width(clip) << log_bpp; + ret = udl_render_hline(dev, log_bpp, &urb, (char *)vaddr, + &cmd, byte_offset, dev_byte_offset, + byte_width); + if (ret) + return ret; + } + + if (cmd > (char *)urb->transfer_buffer) { + /* Send partial buffer remaining before exiting */ + int len; + if (cmd < (char *)urb->transfer_buffer + urb->transfer_buffer_length) + *cmd++ = UDL_MSG_BULK; + len = cmd - (char *)urb->transfer_buffer; + ret = udl_submit_urb(dev, urb, len); + } else { + udl_urb_completion(urb); + } + + return 0; +} + +/* + * Primary plane + */ + +static const uint32_t udl_primary_plane_formats[] = { + DRM_FORMAT_RGB565, + DRM_FORMAT_XRGB8888, +}; + +static const uint64_t udl_primary_plane_fmtmods[] = { + DRM_FORMAT_MOD_LINEAR, + DRM_FORMAT_MOD_INVALID +}; + +static void udl_primary_plane_helper_atomic_update(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_device *dev = plane->dev; + struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, plane); + struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(plane_state); + struct drm_framebuffer *fb = plane_state->fb; + struct drm_plane_state *old_plane_state = drm_atomic_get_old_plane_state(state, plane); + struct drm_atomic_helper_damage_iter iter; + struct drm_rect damage; + int ret, idx; + + if (!fb) + return; /* no framebuffer; plane is disabled */ + + ret = drm_gem_fb_begin_cpu_access(fb, DMA_FROM_DEVICE); + if (ret) + return; + + if (!drm_dev_enter(dev, &idx)) + goto out_drm_gem_fb_end_cpu_access; + + drm_atomic_helper_damage_iter_init(&iter, old_plane_state, plane_state); + drm_atomic_for_each_plane_damage(&iter, &damage) { + udl_handle_damage(fb, &shadow_plane_state->data[0], &damage); + } + + drm_dev_exit(idx); + +out_drm_gem_fb_end_cpu_access: + drm_gem_fb_end_cpu_access(fb, DMA_FROM_DEVICE); +} + +static const struct drm_plane_helper_funcs udl_primary_plane_helper_funcs = { + DRM_GEM_SHADOW_PLANE_HELPER_FUNCS, + .atomic_check = drm_plane_helper_atomic_check, + .atomic_update = udl_primary_plane_helper_atomic_update, +}; + +static const struct drm_plane_funcs udl_primary_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = drm_plane_cleanup, + DRM_GEM_SHADOW_PLANE_FUNCS, +}; + +/* + * CRTC + */ + +static void udl_crtc_helper_atomic_enable(struct drm_crtc *crtc, struct drm_atomic_state *state) +{ + struct drm_device *dev = crtc->dev; + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + struct drm_display_mode *mode = &crtc_state->mode; + struct urb *urb; + char *buf; + int idx; + + if (!drm_dev_enter(dev, &idx)) + return; + + urb = udl_get_urb(dev); + if (!urb) + goto out; + + buf = (char *)urb->transfer_buffer; + buf = udl_vidreg_lock(buf); + buf = udl_set_color_depth(buf, UDL_COLORDEPTH_16BPP); + /* set base for 16bpp segment to 0 */ + buf = udl_set_base16bpp(buf, 0); + /* set base for 8bpp segment to end of fb */ + buf = udl_set_base8bpp(buf, 2 * mode->vdisplay * mode->hdisplay); + buf = udl_set_display_mode(buf, mode); + buf = udl_set_blank_mode(buf, UDL_BLANKMODE_ON); + buf = udl_vidreg_unlock(buf); + buf = udl_dummy_render(buf); + + udl_submit_urb(dev, urb, buf - (char *)urb->transfer_buffer); + +out: + drm_dev_exit(idx); +} + +static void udl_crtc_helper_atomic_disable(struct drm_crtc *crtc, struct drm_atomic_state *state) +{ + struct drm_device *dev = crtc->dev; + struct urb *urb; + char *buf; + int idx; + + if (!drm_dev_enter(dev, &idx)) + return; + + urb = udl_get_urb(dev); + if (!urb) + goto out; + + buf = (char *)urb->transfer_buffer; + buf = udl_vidreg_lock(buf); + buf = udl_set_blank_mode(buf, UDL_BLANKMODE_POWERDOWN); + buf = udl_vidreg_unlock(buf); + buf = udl_dummy_render(buf); + + udl_submit_urb(dev, urb, buf - (char *)urb->transfer_buffer); + +out: + drm_dev_exit(idx); +} + +static const struct drm_crtc_helper_funcs udl_crtc_helper_funcs = { + .atomic_check = drm_crtc_helper_atomic_check, + .atomic_enable = udl_crtc_helper_atomic_enable, + .atomic_disable = udl_crtc_helper_atomic_disable, +}; + +static const struct drm_crtc_funcs udl_crtc_funcs = { + .reset = drm_atomic_helper_crtc_reset, + .destroy = drm_crtc_cleanup, + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, +}; + +/* + * Encoder + */ + +static const struct drm_encoder_funcs udl_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + +/* + * Connector + */ + +static int udl_connector_helper_get_modes(struct drm_connector *connector) +{ + struct udl_connector *udl_connector = to_udl_connector(connector); + + drm_connector_update_edid_property(connector, udl_connector->edid); + if (udl_connector->edid) + return drm_add_edid_modes(connector, udl_connector->edid); + + return 0; +} + +static const struct drm_connector_helper_funcs udl_connector_helper_funcs = { + .get_modes = udl_connector_helper_get_modes, +}; + +static int udl_get_edid_block(void *data, u8 *buf, unsigned int block, size_t len) +{ + struct udl_device *udl = data; + struct drm_device *dev = &udl->drm; + struct usb_device *udev = udl_to_usb_device(udl); + u8 *read_buff; + int ret; + size_t i; + + read_buff = kmalloc(2, GFP_KERNEL); + if (!read_buff) + return -ENOMEM; + + for (i = 0; i < len; i++) { + int bval = (i + block * EDID_LENGTH) << 8; + + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + 0x02, (0x80 | (0x02 << 5)), bval, + 0xA1, read_buff, 2, USB_CTRL_GET_TIMEOUT); + if (ret < 0) { + drm_err(dev, "Read EDID byte %zu failed err %x\n", i, ret); + goto err_kfree; + } else if (ret < 1) { + ret = -EIO; + drm_err(dev, "Read EDID byte %zu failed\n", i); + goto err_kfree; + } + + buf[i] = read_buff[1]; + } + + kfree(read_buff); + + return 0; + +err_kfree: + kfree(read_buff); + return ret; +} + +static enum drm_connector_status udl_connector_detect(struct drm_connector *connector, bool force) +{ + struct drm_device *dev = connector->dev; + struct udl_device *udl = to_udl(dev); + struct udl_connector *udl_connector = to_udl_connector(connector); + enum drm_connector_status status = connector_status_disconnected; + int idx; + + /* cleanup previous EDID */ + kfree(udl_connector->edid); + udl_connector->edid = NULL; + + if (!drm_dev_enter(dev, &idx)) + return connector_status_disconnected; + + udl_connector->edid = drm_do_get_edid(connector, udl_get_edid_block, udl); + if (udl_connector->edid) + status = connector_status_connected; + + drm_dev_exit(idx); + + return status; +} + +static void udl_connector_destroy(struct drm_connector *connector) +{ + struct udl_connector *udl_connector = to_udl_connector(connector); + + drm_connector_cleanup(connector); + kfree(udl_connector->edid); + kfree(udl_connector); +} + +static const struct drm_connector_funcs udl_connector_funcs = { + .reset = drm_atomic_helper_connector_reset, + .detect = udl_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = udl_connector_destroy, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +struct drm_connector *udl_connector_init(struct drm_device *dev) +{ + struct udl_connector *udl_connector; + struct drm_connector *connector; + int ret; + + udl_connector = kzalloc(sizeof(*udl_connector), GFP_KERNEL); + if (!udl_connector) + return ERR_PTR(-ENOMEM); + + connector = &udl_connector->connector; + ret = drm_connector_init(dev, connector, &udl_connector_funcs, DRM_MODE_CONNECTOR_VGA); + if (ret) + goto err_kfree; + + drm_connector_helper_add(connector, &udl_connector_helper_funcs); + + connector->polled = DRM_CONNECTOR_POLL_HPD | + DRM_CONNECTOR_POLL_CONNECT | + DRM_CONNECTOR_POLL_DISCONNECT; + + return connector; + +err_kfree: + kfree(udl_connector); + return ERR_PTR(ret); +} + +/* + * Modesetting + */ + +static enum drm_mode_status udl_mode_config_mode_valid(struct drm_device *dev, + const struct drm_display_mode *mode) +{ + struct udl_device *udl = to_udl(dev); + + if (udl->sku_pixel_limit) { + if (mode->vdisplay * mode->hdisplay > udl->sku_pixel_limit) + return MODE_MEM; + } + + return MODE_OK; +} + +static const struct drm_mode_config_funcs udl_mode_config_funcs = { + .fb_create = drm_gem_fb_create_with_dirty, + .mode_valid = udl_mode_config_mode_valid, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +int udl_modeset_init(struct drm_device *dev) +{ + struct udl_device *udl = to_udl(dev); + struct drm_plane *primary_plane; + struct drm_crtc *crtc; + struct drm_encoder *encoder; + struct drm_connector *connector; + int ret; + + ret = drmm_mode_config_init(dev); + if (ret) + return ret; + + 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.preferred_depth = 16; + dev->mode_config.funcs = &udl_mode_config_funcs; + + primary_plane = &udl->primary_plane; + ret = drm_universal_plane_init(dev, primary_plane, 0, + &udl_primary_plane_funcs, + udl_primary_plane_formats, + ARRAY_SIZE(udl_primary_plane_formats), + udl_primary_plane_fmtmods, + DRM_PLANE_TYPE_PRIMARY, NULL); + if (ret) + return ret; + drm_plane_helper_add(primary_plane, &udl_primary_plane_helper_funcs); + drm_plane_enable_fb_damage_clips(primary_plane); + + crtc = &udl->crtc; + ret = drm_crtc_init_with_planes(dev, crtc, primary_plane, NULL, + &udl_crtc_funcs, NULL); + if (ret) + return ret; + drm_crtc_helper_add(crtc, &udl_crtc_helper_funcs); + + encoder = &udl->encoder; + ret = drm_encoder_init(dev, encoder, &udl_encoder_funcs, DRM_MODE_ENCODER_DAC, NULL); + if (ret) + return ret; + encoder->possible_crtcs = drm_crtc_mask(crtc); + + connector = udl_connector_init(dev); + if (IS_ERR(connector)) + return PTR_ERR(connector); + ret = drm_connector_attach_encoder(connector, encoder); + if (ret) + return ret; + + drm_mode_config_reset(dev); + + return 0; +} diff --git a/drivers/gpu/drm/udl/udl_proto.h b/drivers/gpu/drm/udl/udl_proto.h new file mode 100644 index 0000000000..c92d210958 --- /dev/null +++ b/drivers/gpu/drm/udl/udl_proto.h @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef UDL_PROTO_H +#define UDL_PROTO_H + +#include <linux/bits.h> + +#define UDL_MSG_BULK 0xaf + +/* Register access */ +#define UDL_CMD_WRITEREG 0x20 /* See register constants below */ + +/* Framebuffer access */ +#define UDL_CMD_WRITERAW8 0x60 /* 8 bit raw write command. */ +#define UDL_CMD_WRITERL8 0x61 /* 8 bit run length command. */ +#define UDL_CMD_WRITECOPY8 0x62 /* 8 bit copy command. */ +#define UDL_CMD_WRITERLX8 0x63 /* 8 bit extended run length command. */ +#define UDL_CMD_WRITERAW16 0x68 /* 16 bit raw write command. */ +#define UDL_CMD_WRITERL16 0x69 /* 16 bit run length command. */ +#define UDL_CMD_WRITECOPY16 0x6a /* 16 bit copy command. */ +#define UDL_CMD_WRITERLX16 0x6b /* 16 bit extended run length command. */ + +/* Color depth */ +#define UDL_REG_COLORDEPTH 0x00 +#define UDL_COLORDEPTH_16BPP 0 +#define UDL_COLORDEPTH_24BPP 1 + +/* Display-mode settings */ +#define UDL_REG_XDISPLAYSTART 0x01 +#define UDL_REG_XDISPLAYEND 0x03 +#define UDL_REG_YDISPLAYSTART 0x05 +#define UDL_REG_YDISPLAYEND 0x07 +#define UDL_REG_XENDCOUNT 0x09 +#define UDL_REG_HSYNCSTART 0x0b +#define UDL_REG_HSYNCEND 0x0d +#define UDL_REG_HPIXELS 0x0f +#define UDL_REG_YENDCOUNT 0x11 +#define UDL_REG_VSYNCSTART 0x13 +#define UDL_REG_VSYNCEND 0x15 +#define UDL_REG_VPIXELS 0x17 +#define UDL_REG_PIXELCLOCK5KHZ 0x1b + +/* On/Off for driving the DisplayLink framebuffer to the display */ +#define UDL_REG_BLANKMODE 0x1f +#define UDL_BLANKMODE_ON 0x00 /* hsync and vsync on, visible */ +#define UDL_BLANKMODE_BLANKED 0x01 /* hsync and vsync on, blanked */ +#define UDL_BLANKMODE_VSYNC_OFF 0x03 /* vsync off, blanked */ +#define UDL_BLANKMODE_HSYNC_OFF 0x05 /* hsync off, blanked */ +#define UDL_BLANKMODE_POWERDOWN 0x07 /* powered off; requires modeset */ + +/* Framebuffer address */ +#define UDL_REG_BASE16BPP_ADDR2 0x20 +#define UDL_REG_BASE16BPP_ADDR1 0x21 +#define UDL_REG_BASE16BPP_ADDR0 0x22 +#define UDL_REG_BASE8BPP_ADDR2 0x26 +#define UDL_REG_BASE8BPP_ADDR1 0x27 +#define UDL_REG_BASE8BPP_ADDR0 0x28 + +#define UDL_BASE_ADDR0_MASK GENMASK(7, 0) +#define UDL_BASE_ADDR1_MASK GENMASK(15, 8) +#define UDL_BASE_ADDR2_MASK GENMASK(23, 16) + +/* Lock/unlock video registers */ +#define UDL_REG_VIDREG 0xff +#define UDL_VIDREG_LOCK 0x00 +#define UDL_VIDREG_UNLOCK 0xff + +#endif diff --git a/drivers/gpu/drm/udl/udl_transfer.c b/drivers/gpu/drm/udl/udl_transfer.c new file mode 100644 index 0000000000..5ff1037a34 --- /dev/null +++ b/drivers/gpu/drm/udl/udl_transfer.c @@ -0,0 +1,217 @@ +// 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 <asm/unaligned.h> + +#include "udl_drv.h" +#include "udl_proto.h" + +#define MAX_CMD_PIXELS 255 + +#define RLX_HEADER_BYTES 7 +#define MIN_RLX_PIX_BYTES 4 +#define MIN_RLX_CMD_BYTES (RLX_HEADER_BYTES + MIN_RLX_PIX_BYTES) + +#define RLE_HEADER_BYTES 6 +#define MIN_RLE_PIX_BYTES 3 +#define MIN_RLE_CMD_BYTES (RLE_HEADER_BYTES + MIN_RLE_PIX_BYTES) + +#define RAW_HEADER_BYTES 6 +#define MIN_RAW_PIX_BYTES 2 +#define MIN_RAW_CMD_BYTES (RAW_HEADER_BYTES + MIN_RAW_PIX_BYTES) + +static inline u16 pixel32_to_be16(const uint32_t pixel) +{ + return (((pixel >> 3) & 0x001f) | + ((pixel >> 5) & 0x07e0) | + ((pixel >> 8) & 0xf800)); +} + +static inline u16 get_pixel_val16(const uint8_t *pixel, int log_bpp) +{ + u16 pixel_val16; + if (log_bpp == 1) + pixel_val16 = *(const uint16_t *)pixel; + else + pixel_val16 = pixel32_to_be16(*(const uint32_t *)pixel); + return pixel_val16; +} + +/* + * Render a command stream for an encoded horizontal line segment of pixels. + * + * A command buffer holds several commands. + * It always begins with a fresh command header + * (the protocol doesn't require this, but we enforce it to allow + * multiple buffers to be potentially encoded and sent in parallel). + * A single command encodes one contiguous horizontal line of pixels + * + * The function relies on the client to do all allocation, so that + * rendering can be done directly to output buffers (e.g. USB URBs). + * The function fills the supplied command buffer, providing information + * on where it left off, so the client may call in again with additional + * buffers if the line will take several buffers to complete. + * + * A single command can transmit a maximum of 256 pixels, + * regardless of the compression ratio (protocol design limit). + * To the hardware, 0 for a size byte means 256 + * + * Rather than 256 pixel commands which are either rl or raw encoded, + * the rlx command simply assumes alternating raw and rl spans within one cmd. + * This has a slightly larger header overhead, but produces more even results. + * It also processes all data (read and write) in a single pass. + * Performance benchmarks of common cases show it having just slightly better + * compression than 256 pixel raw or rle commands, with similar CPU consumpion. + * But for very rl friendly data, will compress not quite as well. + */ +static void udl_compress_hline16( + const u8 **pixel_start_ptr, + const u8 *const pixel_end, + uint32_t *device_address_ptr, + uint8_t **command_buffer_ptr, + const uint8_t *const cmd_buffer_end, int log_bpp) +{ + const int bpp = 1 << log_bpp; + const u8 *pixel = *pixel_start_ptr; + uint32_t dev_addr = *device_address_ptr; + uint8_t *cmd = *command_buffer_ptr; + + while ((pixel_end > pixel) && + (cmd_buffer_end - MIN_RLX_CMD_BYTES > cmd)) { + uint8_t *raw_pixels_count_byte = NULL; + uint8_t *cmd_pixels_count_byte = NULL; + const u8 *raw_pixel_start = NULL; + const u8 *cmd_pixel_start, *cmd_pixel_end = NULL; + uint16_t pixel_val16; + + *cmd++ = UDL_MSG_BULK; + *cmd++ = UDL_CMD_WRITERLX16; + *cmd++ = (uint8_t) ((dev_addr >> 16) & 0xFF); + *cmd++ = (uint8_t) ((dev_addr >> 8) & 0xFF); + *cmd++ = (uint8_t) ((dev_addr) & 0xFF); + + cmd_pixels_count_byte = cmd++; /* we'll know this later */ + cmd_pixel_start = pixel; + + raw_pixels_count_byte = cmd++; /* we'll know this later */ + raw_pixel_start = pixel; + + cmd_pixel_end = pixel + (min3(MAX_CMD_PIXELS + 1UL, + (unsigned long)(pixel_end - pixel) >> log_bpp, + (unsigned long)(cmd_buffer_end - 1 - cmd) / 2) << log_bpp); + + pixel_val16 = get_pixel_val16(pixel, log_bpp); + + while (pixel < cmd_pixel_end) { + const u8 *const start = pixel; + const uint16_t repeating_pixel_val16 = pixel_val16; + + put_unaligned_be16(pixel_val16, cmd); + + cmd += 2; + pixel += bpp; + + while (pixel < cmd_pixel_end) { + pixel_val16 = get_pixel_val16(pixel, log_bpp); + if (pixel_val16 != repeating_pixel_val16) + break; + pixel += bpp; + } + + if (unlikely(pixel > start + bpp)) { + /* go back and fill in raw pixel count */ + *raw_pixels_count_byte = (((start - + raw_pixel_start) >> log_bpp) + 1) & 0xFF; + + /* immediately after raw data is repeat byte */ + *cmd++ = (((pixel - start) >> log_bpp) - 1) & 0xFF; + + /* Then start another raw pixel span */ + raw_pixel_start = pixel; + raw_pixels_count_byte = cmd++; + } + } + + if (pixel > raw_pixel_start) { + /* finalize last RAW span */ + *raw_pixels_count_byte = ((pixel - raw_pixel_start) >> log_bpp) & 0xFF; + } else { + /* undo unused byte */ + cmd--; + } + + *cmd_pixels_count_byte = ((pixel - cmd_pixel_start) >> log_bpp) & 0xFF; + dev_addr += ((pixel - cmd_pixel_start) >> log_bpp) * 2; + } + + if (cmd_buffer_end <= MIN_RLX_CMD_BYTES + cmd) { + /* Fill leftover bytes with no-ops */ + if (cmd_buffer_end > cmd) + memset(cmd, UDL_MSG_BULK, cmd_buffer_end - cmd); + cmd = (uint8_t *) cmd_buffer_end; + } + + *command_buffer_ptr = cmd; + *pixel_start_ptr = pixel; + *device_address_ptr = dev_addr; + + return; +} + +/* + * There are 3 copies of every pixel: The front buffer that the fbdev + * client renders to, the actual framebuffer across the USB bus in hardware + * (that we can only write to, slowly, and can never read), and (optionally) + * our shadow copy that tracks what's been sent to that hardware buffer. + */ +int udl_render_hline(struct drm_device *dev, int log_bpp, struct urb **urb_ptr, + const char *front, char **urb_buf_ptr, + u32 byte_offset, u32 device_byte_offset, + u32 byte_width) +{ + const u8 *line_start, *line_end, *next_pixel; + u32 base16 = 0 + (device_byte_offset >> log_bpp) * 2; + struct urb *urb = *urb_ptr; + u8 *cmd = *urb_buf_ptr; + u8 *cmd_end = (u8 *) urb->transfer_buffer + urb->transfer_buffer_length; + + if (WARN_ON(!(log_bpp == 1 || log_bpp == 2))) { + /* need to finish URB at error from this function */ + udl_urb_completion(urb); + return -EINVAL; + } + + line_start = (u8 *) (front + byte_offset); + next_pixel = line_start; + line_end = next_pixel + byte_width; + + while (next_pixel < line_end) { + + udl_compress_hline16(&next_pixel, + line_end, &base16, + (u8 **) &cmd, (u8 *) cmd_end, log_bpp); + + if (cmd >= cmd_end) { + int len = cmd - (u8 *) urb->transfer_buffer; + int ret = udl_submit_urb(dev, urb, len); + if (ret) + return ret; + urb = udl_get_urb(dev); + if (!urb) + return -EAGAIN; + *urb_ptr = urb; + cmd = urb->transfer_buffer; + cmd_end = &cmd[urb->transfer_buffer_length]; + } + } + + *urb_buf_ptr = cmd; + + return 0; +} |