diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:05:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:05:51 +0000 |
commit | 5d1646d90e1f2cceb9f0828f4b28318cd0ec7744 (patch) | |
tree | a94efe259b9009378be6d90eb30d2b019d95c194 /drivers/gpu/drm/udl | |
parent | Initial commit. (diff) | |
download | linux-upstream.tar.xz linux-upstream.zip |
Adding upstream version 5.10.209.upstream/5.10.209upstream
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 | 11 | ||||
-rw-r--r-- | drivers/gpu/drm/udl/Makefile | 4 | ||||
-rw-r--r-- | drivers/gpu/drm/udl/udl_connector.c | 138 | ||||
-rw-r--r-- | drivers/gpu/drm/udl/udl_connector.h | 15 | ||||
-rw-r--r-- | drivers/gpu/drm/udl/udl_drv.c | 151 | ||||
-rw-r--r-- | drivers/gpu/drm/udl/udl_drv.h | 106 | ||||
-rw-r--r-- | drivers/gpu/drm/udl/udl_main.c | 363 | ||||
-rw-r--r-- | drivers/gpu/drm/udl/udl_modeset.c | 501 | ||||
-rw-r--r-- | drivers/gpu/drm/udl/udl_transfer.c | 252 |
9 files changed, 1541 insertions, 0 deletions
diff --git a/drivers/gpu/drm/udl/Kconfig b/drivers/gpu/drm/udl/Kconfig new file mode 100644 index 000000000..1f497d8f1 --- /dev/null +++ b/drivers/gpu/drm/udl/Kconfig @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DRM_UDL + tristate "DisplayLink" + depends on DRM + depends on USB + depends on USB_ARCH_HAS_HCD + 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 000000000..24d61f61d --- /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_connector.o udl_main.o udl_transfer.o + +obj-$(CONFIG_DRM_UDL) := udl.o diff --git a/drivers/gpu/drm/udl/udl_connector.c b/drivers/gpu/drm/udl/udl_connector.c new file mode 100644 index 000000000..aac41a809 --- /dev/null +++ b/drivers/gpu/drm/udl/udl_connector.c @@ -0,0 +1,138 @@ +// 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_state_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_probe_helper.h> + +#include "udl_connector.h" +#include "udl_drv.h" + +static int udl_get_edid_block(void *data, u8 *buf, unsigned int block, + size_t len) +{ + int ret, i; + u8 *read_buff; + struct udl_device *udl = data; + + read_buff = kmalloc(2, GFP_KERNEL); + if (!read_buff) + return -1; + + for (i = 0; i < len; i++) { + int bval = (i + block * EDID_LENGTH) << 8; + ret = usb_control_msg(udl->udev, + usb_rcvctrlpipe(udl->udev, 0), + (0x02), (0x80 | (0x02 << 5)), bval, + 0xA1, read_buff, 2, 1000); + if (ret < 1) { + DRM_ERROR("Read EDID byte %d failed err %x\n", i, ret); + kfree(read_buff); + return -1; + } + buf[i] = read_buff[1]; + } + + kfree(read_buff); + return 0; +} + +static int udl_get_modes(struct drm_connector *connector) +{ + struct udl_drm_connector *udl_connector = + container_of(connector, + struct udl_drm_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 enum drm_mode_status udl_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct udl_device *udl = to_udl(connector->dev); + if (!udl->sku_pixel_limit) + return 0; + + if (mode->vdisplay * mode->hdisplay > udl->sku_pixel_limit) + return MODE_VIRTUAL_Y; + + return 0; +} + +static enum drm_connector_status +udl_detect(struct drm_connector *connector, bool force) +{ + struct udl_device *udl = to_udl(connector->dev); + struct udl_drm_connector *udl_connector = + container_of(connector, + struct udl_drm_connector, + connector); + + /* cleanup previous edid */ + if (udl_connector->edid != NULL) { + kfree(udl_connector->edid); + udl_connector->edid = NULL; + } + + udl_connector->edid = drm_do_get_edid(connector, udl_get_edid_block, udl); + if (!udl_connector->edid) + return connector_status_disconnected; + + return connector_status_connected; +} + +static void udl_connector_destroy(struct drm_connector *connector) +{ + struct udl_drm_connector *udl_connector = + container_of(connector, + struct udl_drm_connector, + connector); + + drm_connector_cleanup(connector); + kfree(udl_connector->edid); + kfree(connector); +} + +static const struct drm_connector_helper_funcs udl_connector_helper_funcs = { + .get_modes = udl_get_modes, + .mode_valid = udl_mode_valid, +}; + +static const struct drm_connector_funcs udl_connector_funcs = { + .reset = drm_atomic_helper_connector_reset, + .detect = udl_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_drm_connector *udl_connector; + struct drm_connector *connector; + + udl_connector = kzalloc(sizeof(struct udl_drm_connector), GFP_KERNEL); + if (!udl_connector) + return ERR_PTR(-ENOMEM); + + connector = &udl_connector->connector; + drm_connector_init(dev, connector, &udl_connector_funcs, + DRM_MODE_CONNECTOR_DVII); + 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; +} diff --git a/drivers/gpu/drm/udl/udl_connector.h b/drivers/gpu/drm/udl/udl_connector.h new file mode 100644 index 000000000..7f2d392df --- /dev/null +++ b/drivers/gpu/drm/udl/udl_connector.h @@ -0,0 +1,15 @@ +#ifndef __UDL_CONNECTOR_H__ +#define __UDL_CONNECTOR_H__ + +#include <drm/drm_crtc.h> + +struct edid; + +struct udl_drm_connector { + struct drm_connector connector; + /* last udl_detect edid */ + struct edid *edid; +}; + + +#endif //__UDL_CONNECTOR_H__ diff --git a/drivers/gpu/drm/udl/udl_drv.c b/drivers/gpu/drm/udl/udl_drv.c new file mode 100644 index 000000000..bcf32d188 --- /dev/null +++ b/drivers/gpu/drm/udl/udl_drv.c @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2012 Red Hat + */ + +#include <linux/module.h> + +#include <drm/drm_crtc_helper.h> +#include <drm/drm_drv.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_file.h> +#include <drm/drm_gem_shmem_helper.h> +#include <drm/drm_managed.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); + + return drm_mode_config_helper_suspend(dev); +} + +static int udl_usb_resume(struct usb_interface *interface) +{ + struct drm_device *dev = usb_get_intfdata(interface); + + 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 struct drm_driver driver = { + .driver_features = DRIVER_ATOMIC | DRIVER_GEM | DRIVER_MODESET, + + /* GEM hooks */ + .gem_create_object = drm_gem_shmem_create_object_cached, + + .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 usb_device *udev = interface_to_usbdev(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; + + udl->udev = udev; + + 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, + .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 000000000..8aab14871 --- /dev/null +++ b/drivers/gpu/drm/udl/udl_drv.h @@ -0,0 +1,106 @@ +/* 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_device.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_gem.h> +#include <drm/drm_simple_kms_helper.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 delayed_work release_urb_work; + struct urb *urb; +}; + +struct urb_list { + struct list_head list; + spinlock_t lock; + struct semaphore limit_sem; + int available; + int count; + size_t size; +}; + +struct udl_device { + struct drm_device drm; + struct device *dev; + struct device *dmadev; + struct usb_device *udev; + + struct drm_simple_display_pipe display_pipe; + + struct mutex gem_lock; + + int sku_pixel_limit; + + struct urb_list urbs; + + char mode_buf[1024]; + uint32_t mode_buf_len; +}; + +#define to_udl(x) container_of(x, struct udl_device, drm) + +/* 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_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); + +#define CMD_WRITE_RAW8 "\xAF\x60" /**< 8 bit raw write command. */ +#define CMD_WRITE_RL8 "\xAF\x61" /**< 8 bit run length command. */ +#define CMD_WRITE_COPY8 "\xAF\x62" /**< 8 bit copy command. */ +#define CMD_WRITE_RLX8 "\xAF\x63" /**< 8 bit extended run length command. */ + +#define CMD_WRITE_RAW16 "\xAF\x68" /**< 16 bit raw write command. */ +#define CMD_WRITE_RL16 "\xAF\x69" /**< 16 bit run length command. */ +#define CMD_WRITE_COPY16 "\xAF\x6A" /**< 16 bit copy command. */ +#define CMD_WRITE_RLX16 "\xAF\x6B" /**< 16 bit extended run length command. */ + +/* On/Off for driving the DisplayLink framebuffer to the display */ +#define UDL_REG_BLANK_MODE 0x1f + +#define UDL_BLANK_MODE_ON 0x00 /* hsync and vsync on, visible */ +#define UDL_BLANK_MODE_BLANKED 0x01 /* hsync and vsync on, blanked */ +#define UDL_BLANK_MODE_VSYNC_OFF 0x03 /* vsync off, blanked */ +#define UDL_BLANK_MODE_HSYNC_OFF 0x05 /* hsync off, blanked */ +#define UDL_BLANK_MODE_POWERDOWN 0x07 /* powered off; requires modeset */ + +#endif diff --git a/drivers/gpu/drm/udl/udl_main.c b/drivers/gpu/drm/udl/udl_main.c new file mode 100644 index 000000000..5f1d3891e --- /dev/null +++ b/drivers/gpu/drm/udl/udl_main.c @@ -0,0 +1,363 @@ +// 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 (4) +#define MAX_VENDOR_DESCRIPTOR_SIZE 256 + +#define GET_URB_TIMEOUT HZ +#define FREE_URB_TIMEOUT (HZ*2) + +static int udl_parse_vendor_descriptor(struct drm_device *dev, + struct usb_device *usbdev) +{ + struct udl_device *udl = to_udl(dev); + 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(usbdev, 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 + */ +static int udl_select_std_channel(struct udl_device *udl) +{ + int ret; + static const u8 set_def_chn[] = {0x57, 0xCD, 0xDC, 0xA7, + 0x1C, 0x88, 0x5E, 0x15, + 0x60, 0xFE, 0xC6, 0x97, + 0x16, 0x3D, 0x47, 0xF2}; + void *sendbuf; + + sendbuf = kmemdup(set_def_chn, sizeof(set_def_chn), GFP_KERNEL); + if (!sendbuf) + return -ENOMEM; + + ret = usb_control_msg(udl->udev, + usb_sndctrlpipe(udl->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; +} + +static void udl_release_urb_work(struct work_struct *work) +{ + struct urb_node *unode = container_of(work, struct urb_node, + release_urb_work.work); + + up(&unode->dev->urbs.limit_sem); +} + +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 == -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); + +#if 0 + /* + * When using fb_defio, we deadlock if up() is called + * while another is waiting. So queue to another process. + */ + if (fb_defio) + schedule_delayed_work(&unode->release_urb_work, 0); + else +#endif + up(&udl->urbs.limit_sem); +} + +static void udl_free_urb_list(struct drm_device *dev) +{ + struct udl_device *udl = to_udl(dev); + int count = udl->urbs.count; + struct list_head *node; + 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 (count--) { + down(&udl->urbs.limit_sem); + + spin_lock_irq(&udl->urbs.lock); + + node = udl->urbs.list.next; /* have reserved one with sem */ + list_del_init(node); + + spin_unlock_irq(&udl->urbs.lock); + + unode = list_entry(node, struct urb_node, entry); + urb = unode->urb; + + /* Free each separately allocated piece */ + usb_free_coherent(urb->dev, udl->urbs.size, + urb->transfer_buffer, urb->transfer_dma); + usb_free_urb(urb); + kfree(node); + } + udl->urbs.count = 0; +} + +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; + + spin_lock_init(&udl->urbs.lock); + +retry: + udl->urbs.size = size; + INIT_LIST_HEAD(&udl->urbs.list); + + sema_init(&udl->urbs.limit_sem, 0); + udl->urbs.count = 0; + udl->urbs.available = 0; + + while (udl->urbs.count * size < wanted_size) { + unode = kzalloc(sizeof(struct urb_node), GFP_KERNEL); + if (!unode) + break; + unode->dev = udl; + + INIT_DELAYED_WORK(&unode->release_urb_work, + udl_release_urb_work); + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + kfree(unode); + break; + } + unode->urb = urb; + + buf = usb_alloc_coherent(udl->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, udl->udev, usb_sndbulkpipe(udl->udev, 1), + buf, size, udl_urb_completion, unode); + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + list_add_tail(&unode->entry, &udl->urbs.list); + + up(&udl->urbs.limit_sem); + udl->urbs.count++; + udl->urbs.available++; + } + + DRM_DEBUG("allocated %d %d byte urbs\n", udl->urbs.count, (int) size); + + return udl->urbs.count; +} + +struct urb *udl_get_urb(struct drm_device *dev) +{ + struct udl_device *udl = to_udl(dev); + int ret = 0; + struct list_head *entry; + struct urb_node *unode; + struct urb *urb = NULL; + + /* Wait for an in-flight buffer to complete and get re-queued */ + ret = down_timeout(&udl->urbs.limit_sem, GET_URB_TIMEOUT); + if (ret) { + DRM_INFO("wait for urb interrupted: %x available: %d\n", + ret, udl->urbs.available); + goto error; + } + + spin_lock_irq(&udl->urbs.lock); + + BUG_ON(list_empty(&udl->urbs.list)); /* reserved one with limit_sem */ + entry = udl->urbs.list.next; + list_del_init(entry); + udl->urbs.available--; + + spin_unlock_irq(&udl->urbs.lock); + + unode = list_entry(entry, struct urb_node, entry); + urb = unode->urb; + +error: + 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; + + BUG_ON(len > udl->urbs.size); + + urb->transfer_buffer_length = len; /* set to actual payload len */ + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret) { + udl_urb_completion(urb); /* because no one else will */ + DRM_ERROR("usb_submit_urb error %x\n", ret); + } + return ret; +} + +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(dev, udl->udev)) { + 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 000000000..209b5ceba --- /dev/null +++ b/drivers/gpu/drm/udl/udl_modeset.c @@ -0,0 +1,501 @@ +// 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/dma-buf.h> + +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_damage_helper.h> +#include <drm/drm_fourcc.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_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); +} + +static char *udl_set_blank_mode(char *buf, u8 mode) +{ + return udl_set_register(buf, UDL_REG_BLANK_MODE, mode); +} + +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 = to_udl(dev); + struct urb *urb; + char *buf; + int retval; + + if (udl->mode_buf_len == 0) { + DRM_ERROR("No mode set\n"); + return -EINVAL; + } + + 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 long udl_log_cpp(unsigned int cpp) +{ + if (WARN_ON(!is_power_of_2(cpp))) + return -EINVAL; + return __ffs(cpp); +} + +static int udl_aligned_damage_clip(struct drm_rect *clip, int x, int y, + int width, int height) +{ + int x1, x2; + + if (WARN_ON_ONCE(x < 0) || + WARN_ON_ONCE(y < 0) || + WARN_ON_ONCE(width < 0) || + WARN_ON_ONCE(height < 0)) + return -EINVAL; + + x1 = ALIGN_DOWN(x, sizeof(unsigned long)); + x2 = ALIGN(width + (x - x1), sizeof(unsigned long)) + x1; + + clip->x1 = x1; + clip->y1 = y; + clip->x2 = x2; + clip->y2 = y + height; + + return 0; +} + +static int udl_handle_damage(struct drm_framebuffer *fb, int x, int y, + int width, int height) +{ + struct drm_device *dev = fb->dev; + struct dma_buf_attachment *import_attach = fb->obj[0]->import_attach; + int i, ret, tmp_ret; + char *cmd; + struct urb *urb; + struct drm_rect clip; + int log_bpp; + void *vaddr; + + ret = udl_log_cpp(fb->format->cpp[0]); + if (ret < 0) + return ret; + log_bpp = ret; + + ret = udl_aligned_damage_clip(&clip, x, y, width, height); + if (ret) + return ret; + else if ((clip.x2 > fb->width) || (clip.y2 > fb->height)) + return -EINVAL; + + if (import_attach) { + ret = dma_buf_begin_cpu_access(import_attach->dmabuf, + DMA_FROM_DEVICE); + if (ret) + return ret; + } + + vaddr = drm_gem_shmem_vmap(fb->obj[0]); + if (IS_ERR(vaddr)) { + DRM_ERROR("failed to vmap fb\n"); + goto out_dma_buf_end_cpu_access; + } + + urb = udl_get_urb(dev); + if (!urb) { + ret = -ENOMEM; + goto out_drm_gem_shmem_vunmap; + } + 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 = (clip.x2 - clip.x1) << log_bpp; + ret = udl_render_hline(dev, log_bpp, &urb, (char *)vaddr, + &cmd, byte_offset, dev_byte_offset, + byte_width); + if (ret) + goto out_drm_gem_shmem_vunmap; + } + + 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++ = 0xAF; + len = cmd - (char *)urb->transfer_buffer; + ret = udl_submit_urb(dev, urb, len); + } else { + udl_urb_completion(urb); + } + + ret = 0; + +out_drm_gem_shmem_vunmap: + drm_gem_shmem_vunmap(fb->obj[0], vaddr); +out_dma_buf_end_cpu_access: + if (import_attach) { + tmp_ret = dma_buf_end_cpu_access(import_attach->dmabuf, + DMA_FROM_DEVICE); + if (tmp_ret && !ret) + ret = tmp_ret; /* only update ret if not set yet */ + } + + return ret; +} + +/* + * 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 = to_udl(dev); + struct drm_display_mode *mode = &crtc_state->mode; + char *buf; + char *wrptr; + int color_depth = UDL_COLOR_DEPTH_16BPP; + + 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_mode(wrptr, UDL_BLANK_MODE_ON); + wrptr = udl_vidreg_unlock(wrptr); + + wrptr = udl_dummy_render(wrptr); + + udl->mode_buf_len = wrptr - buf; + + udl_handle_damage(fb, 0, 0, fb->width, fb->height); + + /* enable display */ + udl_crtc_write_mode_to_hw(crtc); +} + +static void +udl_simple_display_pipe_disable(struct drm_simple_display_pipe *pipe) +{ + struct drm_crtc *crtc = &pipe->crtc; + struct drm_device *dev = crtc->dev; + struct urb *urb; + char *buf; + + urb = udl_get_urb(dev); + if (!urb) + return; + + buf = (char *)urb->transfer_buffer; + buf = udl_vidreg_lock(buf); + buf = udl_set_blank_mode(buf, UDL_BLANK_MODE_POWERDOWN); + buf = udl_vidreg_unlock(buf); + buf = udl_dummy_render(buf); + + udl_submit_urb(dev, urb, buf - (char *)urb->transfer_buffer); +} + +static void +udl_simple_display_pipe_update(struct drm_simple_display_pipe *pipe, + struct drm_plane_state *old_plane_state) +{ + struct drm_plane_state *state = pipe->plane.state; + struct drm_framebuffer *fb = state->fb; + struct drm_rect rect; + + if (!fb) + return; + + if (drm_atomic_helper_damage_merged(old_plane_state, state, &rect)) + udl_handle_damage(fb, rect.x1, rect.y1, rect.x2 - rect.x1, + rect.y2 - rect.y1); +} + +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, + .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 = drm_gem_fb_create_with_dirty, + .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 = to_udl(dev); + 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.prefer_shadow = 0; + dev->mode_config.preferred_depth = 16; + + dev->mode_config.funcs = &udl_mode_funcs; + + connector = udl_connector_init(dev); + if (IS_ERR(connector)) + return PTR_ERR(connector); + + 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) + return ret; + + drm_mode_config_reset(dev); + + return 0; +} diff --git a/drivers/gpu/drm/udl/udl_transfer.c b/drivers/gpu/drm/udl/udl_transfer.c new file mode 100644 index 000000000..971927669 --- /dev/null +++ b/drivers/gpu/drm/udl/udl_transfer.c @@ -0,0 +1,252 @@ +// 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" + +#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) + +/* + * Trims identical data from front and back of line + * Sets new front buffer address and width + * And returns byte count of identical pixels + * Assumes CPU natural alignment (unsigned long) + * for back and front buffer ptrs and width + */ +#if 0 +static int udl_trim_hline(const u8 *bback, const u8 **bfront, int *width_bytes) +{ + int j, k; + const unsigned long *back = (const unsigned long *) bback; + const unsigned long *front = (const unsigned long *) *bfront; + const int width = *width_bytes / sizeof(unsigned long); + int identical = width; + int start = width; + int end = width; + + for (j = 0; j < width; j++) { + if (back[j] != front[j]) { + start = j; + break; + } + } + + for (k = width - 1; k > j; k--) { + if (back[k] != front[k]) { + end = k+1; + break; + } + } + + identical = start + (width - end); + *bfront = (u8 *) &front[start]; + *width_bytes = (end - start) * sizeof(unsigned long); + + return identical * sizeof(unsigned long); +} +#endif + +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++ = 0xaf; + *cmd++ = 0x6b; + *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, 0xAF, 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; + + BUG_ON(!(log_bpp == 1 || log_bpp == 2)); + + 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; +} |