drm/gm12u320: Move driver to drm/tiny
authorNoralf Trønnes <noralf@tronnes.org>
Thu, 25 Jul 2019 10:51:32 +0000 (12:51 +0200)
committerNoralf Trønnes <noralf@tronnes.org>
Wed, 31 Jul 2019 13:17:03 +0000 (15:17 +0200)
Move the driver to the new haven for tiny DRM drivers.

Cc: Hans de Goede <hdegoede@redhat.com>
Suggested-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Signed-off-by: Noralf Trønnes <noralf@tronnes.org>
Acked-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Acked-by: Sam Ravnborg <sam@ravnborg.org>
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20190725105132.22545-4-noralf@tronnes.org
MAINTAINERS
drivers/gpu/drm/Kconfig
drivers/gpu/drm/Makefile
drivers/gpu/drm/gm12u320/Kconfig [deleted file]
drivers/gpu/drm/gm12u320/Makefile [deleted file]
drivers/gpu/drm/gm12u320/gm12u320.c [deleted file]
drivers/gpu/drm/tiny/Kconfig
drivers/gpu/drm/tiny/Makefile
drivers/gpu/drm/tiny/gm12u320.c [new file with mode: 0644]

index 00c7cbc92711064942f81cb4c8382a2d57f7c297..6fe3462a1f7ae19c6db53347117bb7d1a4a277a8 100644 (file)
@@ -5099,7 +5099,7 @@ DRM DRIVER FOR GRAIN MEDIA GM12U320 PROJECTORS
 M:     Hans de Goede <hdegoede@redhat.com>
 T:     git git://anongit.freedesktop.org/drm/drm-misc
 S:     Maintained
-F:     drivers/gpu/drm/gm12u320/
+F:     drivers/gpu/drm/tiny/gm12u320.c
 
 DRM DRIVER FOR ILITEK ILI9225 PANELS
 M:     David Lechner <david@lechnology.com>
index a5ae0d369e88b88c92d291a71d91e1f2532ef345..e6f40fb54c9afe7e911d0f37ada462768515624f 100644 (file)
@@ -358,8 +358,6 @@ source "drivers/gpu/drm/aspeed/Kconfig"
 
 source "drivers/gpu/drm/mcde/Kconfig"
 
-source "drivers/gpu/drm/gm12u320/Kconfig"
-
 # Keep legacy drivers last
 
 menuconfig DRM_LEGACY
index 1d366c4bbd1fb17c4090bdfc7ab58e55316da86d..10f8329a8b71b2cc10deb38ac49f44fa7837b15a 100644 (file)
@@ -121,4 +121,3 @@ obj-$(CONFIG_DRM_LIMA)  += lima/
 obj-$(CONFIG_DRM_PANFROST) += panfrost/
 obj-$(CONFIG_DRM_ASPEED_GFX) += aspeed/
 obj-$(CONFIG_DRM_MCDE) += mcde/
-obj-$(CONFIG_DRM_GM12U320) += gm12u320/
diff --git a/drivers/gpu/drm/gm12u320/Kconfig b/drivers/gpu/drm/gm12u320/Kconfig
deleted file mode 100644 (file)
index 0882a61..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0+
-config DRM_GM12U320
-       tristate "GM12U320 driver for USB projectors"
-       depends on DRM && USB
-       select DRM_KMS_HELPER
-       select DRM_GEM_SHMEM_HELPER
-       help
-        This is a KMS driver for projectors which use the GM12U320 chipset
-        for video transfer over USB2/3, such as the Acer C120 mini projector.
diff --git a/drivers/gpu/drm/gm12u320/Makefile b/drivers/gpu/drm/gm12u320/Makefile
deleted file mode 100644 (file)
index ea51438..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0+
-obj-$(CONFIG_DRM_GM12U320) += gm12u320.o
diff --git a/drivers/gpu/drm/gm12u320/gm12u320.c b/drivers/gpu/drm/gm12u320/gm12u320.c
deleted file mode 100644 (file)
index b6f47b8..0000000
+++ /dev/null
@@ -1,814 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0+
-/*
- * Copyright 2019 Hans de Goede <hdegoede@redhat.com>
- */
-
-#include <linux/dma-buf.h>
-#include <linux/module.h>
-#include <linux/usb.h>
-
-#include <drm/drm_atomic_helper.h>
-#include <drm/drm_atomic_state_helper.h>
-#include <drm/drm_connector.h>
-#include <drm/drm_damage_helper.h>
-#include <drm/drm_drv.h>
-#include <drm/drm_fb_helper.h>
-#include <drm/drm_file.h>
-#include <drm/drm_format_helper.h>
-#include <drm/drm_fourcc.h>
-#include <drm/drm_gem_shmem_helper.h>
-#include <drm/drm_gem_framebuffer_helper.h>
-#include <drm/drm_ioctl.h>
-#include <drm/drm_modeset_helper_vtables.h>
-#include <drm/drm_probe_helper.h>
-#include <drm/drm_simple_kms_helper.h>
-#include <drm/drm_vblank.h>
-
-static bool eco_mode;
-module_param(eco_mode, bool, 0644);
-MODULE_PARM_DESC(eco_mode, "Turn on Eco mode (less bright, more silent)");
-
-#define DRIVER_NAME            "gm12u320"
-#define DRIVER_DESC            "Grain Media GM12U320 USB projector display"
-#define DRIVER_DATE            "2019"
-#define DRIVER_MAJOR           1
-#define DRIVER_MINOR           0
-#define DRIVER_PATCHLEVEL      1
-
-/*
- * The DLP has an actual width of 854 pixels, but that is not a multiple
- * of 8, breaking things left and right, so we export a width of 848.
- */
-#define GM12U320_USER_WIDTH            848
-#define GM12U320_REAL_WIDTH            854
-#define GM12U320_HEIGHT                        480
-
-#define GM12U320_BLOCK_COUNT           20
-
-#define MISC_RCV_EPT                   1
-#define DATA_RCV_EPT                   2
-#define DATA_SND_EPT                   3
-#define MISC_SND_EPT                   4
-
-#define DATA_BLOCK_HEADER_SIZE         84
-#define DATA_BLOCK_CONTENT_SIZE                64512
-#define DATA_BLOCK_FOOTER_SIZE         20
-#define DATA_BLOCK_SIZE                        (DATA_BLOCK_HEADER_SIZE + \
-                                        DATA_BLOCK_CONTENT_SIZE + \
-                                        DATA_BLOCK_FOOTER_SIZE)
-#define DATA_LAST_BLOCK_CONTENT_SIZE   4032
-#define DATA_LAST_BLOCK_SIZE           (DATA_BLOCK_HEADER_SIZE + \
-                                        DATA_LAST_BLOCK_CONTENT_SIZE + \
-                                        DATA_BLOCK_FOOTER_SIZE)
-
-#define CMD_SIZE                       31
-#define READ_STATUS_SIZE               13
-#define MISC_VALUE_SIZE                        4
-
-#define CMD_TIMEOUT                    msecs_to_jiffies(200)
-#define DATA_TIMEOUT                   msecs_to_jiffies(1000)
-#define IDLE_TIMEOUT                   msecs_to_jiffies(2000)
-#define FIRST_FRAME_TIMEOUT            msecs_to_jiffies(2000)
-
-#define MISC_REQ_GET_SET_ECO_A         0xff
-#define MISC_REQ_GET_SET_ECO_B         0x35
-/* Windows driver does once every second, with arg d = 1, other args 0 */
-#define MISC_REQ_UNKNOWN1_A            0xff
-#define MISC_REQ_UNKNOWN1_B            0x38
-/* Windows driver does this on init, with arg a, b = 0, c = 0xa0, d = 4 */
-#define MISC_REQ_UNKNOWN2_A            0xa5
-#define MISC_REQ_UNKNOWN2_B            0x00
-
-struct gm12u320_device {
-       struct drm_device                dev;
-       struct drm_simple_display_pipe   pipe;
-       struct drm_connector             conn;
-       struct usb_device               *udev;
-       unsigned char                   *cmd_buf;
-       unsigned char                   *data_buf[GM12U320_BLOCK_COUNT];
-       bool                             pipe_enabled;
-       struct {
-               bool                     run;
-               struct workqueue_struct *workq;
-               struct work_struct       work;
-               wait_queue_head_t        waitq;
-               struct mutex             lock;
-               struct drm_framebuffer  *fb;
-               struct drm_rect          rect;
-       } fb_update;
-};
-
-static const char cmd_data[CMD_SIZE] = {
-       0x55, 0x53, 0x42, 0x43, 0x00, 0x00, 0x00, 0x00,
-       0x68, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x10, 0xff,
-       0x00, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x80, 0x00,
-       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
-};
-
-static const char cmd_draw[CMD_SIZE] = {
-       0x55, 0x53, 0x42, 0x43, 0x00, 0x00, 0x00, 0x00,
-       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xfe,
-       0x00, 0x00, 0x00, 0xc0, 0xd1, 0x05, 0x00, 0x40,
-       0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00
-};
-
-static const char cmd_misc[CMD_SIZE] = {
-       0x55, 0x53, 0x42, 0x43, 0x00, 0x00, 0x00, 0x00,
-       0x04, 0x00, 0x00, 0x00, 0x80, 0x01, 0x10, 0xfd,
-       0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00,
-       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
-};
-
-static const char data_block_header[DATA_BLOCK_HEADER_SIZE] = {
-       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-       0xfb, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-       0x00, 0x04, 0x15, 0x00, 0x00, 0xfc, 0x00, 0x00,
-       0x01, 0x00, 0x00, 0xdb
-};
-
-static const char data_last_block_header[DATA_BLOCK_HEADER_SIZE] = {
-       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-       0xfb, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-       0x2a, 0x00, 0x20, 0x00, 0xc0, 0x0f, 0x00, 0x00,
-       0x01, 0x00, 0x00, 0xd7
-};
-
-static const char data_block_footer[DATA_BLOCK_FOOTER_SIZE] = {
-       0xfb, 0x14, 0x02, 0x20, 0x00, 0x00, 0x00, 0x00,
-       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-       0x80, 0x00, 0x00, 0x4f
-};
-
-static int gm12u320_usb_alloc(struct gm12u320_device *gm12u320)
-{
-       int i, block_size;
-       const char *hdr;
-
-       gm12u320->cmd_buf = kmalloc(CMD_SIZE, GFP_KERNEL);
-       if (!gm12u320->cmd_buf)
-               return -ENOMEM;
-
-       for (i = 0; i < GM12U320_BLOCK_COUNT; i++) {
-               if (i == GM12U320_BLOCK_COUNT - 1) {
-                       block_size = DATA_LAST_BLOCK_SIZE;
-                       hdr = data_last_block_header;
-               } else {
-                       block_size = DATA_BLOCK_SIZE;
-                       hdr = data_block_header;
-               }
-
-               gm12u320->data_buf[i] = kzalloc(block_size, GFP_KERNEL);
-               if (!gm12u320->data_buf[i])
-                       return -ENOMEM;
-
-               memcpy(gm12u320->data_buf[i], hdr, DATA_BLOCK_HEADER_SIZE);
-               memcpy(gm12u320->data_buf[i] +
-                               (block_size - DATA_BLOCK_FOOTER_SIZE),
-                      data_block_footer, DATA_BLOCK_FOOTER_SIZE);
-       }
-
-       gm12u320->fb_update.workq = create_singlethread_workqueue(DRIVER_NAME);
-       if (!gm12u320->fb_update.workq)
-               return -ENOMEM;
-
-       return 0;
-}
-
-static void gm12u320_usb_free(struct gm12u320_device *gm12u320)
-{
-       int i;
-
-       if (gm12u320->fb_update.workq)
-               destroy_workqueue(gm12u320->fb_update.workq);
-
-       for (i = 0; i < GM12U320_BLOCK_COUNT; i++)
-               kfree(gm12u320->data_buf[i]);
-
-       kfree(gm12u320->cmd_buf);
-}
-
-static int gm12u320_misc_request(struct gm12u320_device *gm12u320,
-                                u8 req_a, u8 req_b,
-                                u8 arg_a, u8 arg_b, u8 arg_c, u8 arg_d)
-{
-       int ret, len;
-
-       memcpy(gm12u320->cmd_buf, &cmd_misc, CMD_SIZE);
-       gm12u320->cmd_buf[20] = req_a;
-       gm12u320->cmd_buf[21] = req_b;
-       gm12u320->cmd_buf[22] = arg_a;
-       gm12u320->cmd_buf[23] = arg_b;
-       gm12u320->cmd_buf[24] = arg_c;
-       gm12u320->cmd_buf[25] = arg_d;
-
-       /* Send request */
-       ret = usb_bulk_msg(gm12u320->udev,
-                          usb_sndbulkpipe(gm12u320->udev, MISC_SND_EPT),
-                          gm12u320->cmd_buf, CMD_SIZE, &len, CMD_TIMEOUT);
-       if (ret || len != CMD_SIZE) {
-               dev_err(&gm12u320->udev->dev, "Misc. req. error %d\n", ret);
-               return -EIO;
-       }
-
-       /* Read value */
-       ret = usb_bulk_msg(gm12u320->udev,
-                          usb_rcvbulkpipe(gm12u320->udev, MISC_RCV_EPT),
-                          gm12u320->cmd_buf, MISC_VALUE_SIZE, &len,
-                          DATA_TIMEOUT);
-       if (ret || len != MISC_VALUE_SIZE) {
-               dev_err(&gm12u320->udev->dev, "Misc. value error %d\n", ret);
-               return -EIO;
-       }
-       /* cmd_buf[0] now contains the read value, which we don't use */
-
-       /* Read status */
-       ret = usb_bulk_msg(gm12u320->udev,
-                          usb_rcvbulkpipe(gm12u320->udev, MISC_RCV_EPT),
-                          gm12u320->cmd_buf, READ_STATUS_SIZE, &len,
-                          CMD_TIMEOUT);
-       if (ret || len != READ_STATUS_SIZE) {
-               dev_err(&gm12u320->udev->dev, "Misc. status error %d\n", ret);
-               return -EIO;
-       }
-
-       return 0;
-}
-
-static void gm12u320_32bpp_to_24bpp_packed(u8 *dst, u8 *src, int len)
-{
-       while (len--) {
-               *dst++ = *src++;
-               *dst++ = *src++;
-               *dst++ = *src++;
-               src++;
-       }
-}
-
-static void gm12u320_copy_fb_to_blocks(struct gm12u320_device *gm12u320)
-{
-       int block, dst_offset, len, remain, ret, x1, x2, y1, y2;
-       struct drm_framebuffer *fb;
-       void *vaddr;
-       u8 *src;
-
-       mutex_lock(&gm12u320->fb_update.lock);
-
-       if (!gm12u320->fb_update.fb)
-               goto unlock;
-
-       fb = gm12u320->fb_update.fb;
-       x1 = gm12u320->fb_update.rect.x1;
-       x2 = gm12u320->fb_update.rect.x2;
-       y1 = gm12u320->fb_update.rect.y1;
-       y2 = gm12u320->fb_update.rect.y2;
-
-       vaddr = drm_gem_shmem_vmap(fb->obj[0]);
-       if (IS_ERR(vaddr)) {
-               DRM_ERROR("failed to vmap fb: %ld\n", PTR_ERR(vaddr));
-               goto put_fb;
-       }
-
-       if (fb->obj[0]->import_attach) {
-               ret = dma_buf_begin_cpu_access(
-                       fb->obj[0]->import_attach->dmabuf, DMA_FROM_DEVICE);
-               if (ret) {
-                       DRM_ERROR("dma_buf_begin_cpu_access err: %d\n", ret);
-                       goto vunmap;
-               }
-       }
-
-       src = vaddr + y1 * fb->pitches[0] + x1 * 4;
-
-       x1 += (GM12U320_REAL_WIDTH - GM12U320_USER_WIDTH) / 2;
-       x2 += (GM12U320_REAL_WIDTH - GM12U320_USER_WIDTH) / 2;
-
-       for (; y1 < y2; y1++) {
-               remain = 0;
-               len = (x2 - x1) * 3;
-               dst_offset = (y1 * GM12U320_REAL_WIDTH + x1) * 3;
-               block = dst_offset / DATA_BLOCK_CONTENT_SIZE;
-               dst_offset %= DATA_BLOCK_CONTENT_SIZE;
-
-               if ((dst_offset + len) > DATA_BLOCK_CONTENT_SIZE) {
-                       remain = dst_offset + len - DATA_BLOCK_CONTENT_SIZE;
-                       len = DATA_BLOCK_CONTENT_SIZE - dst_offset;
-               }
-
-               dst_offset += DATA_BLOCK_HEADER_SIZE;
-               len /= 3;
-
-               gm12u320_32bpp_to_24bpp_packed(
-                       gm12u320->data_buf[block] + dst_offset,
-                       src, len);
-
-               if (remain) {
-                       block++;
-                       dst_offset = DATA_BLOCK_HEADER_SIZE;
-                       gm12u320_32bpp_to_24bpp_packed(
-                               gm12u320->data_buf[block] + dst_offset,
-                               src + len * 4, remain / 3);
-               }
-               src += fb->pitches[0];
-       }
-
-       if (fb->obj[0]->import_attach) {
-               ret = dma_buf_end_cpu_access(fb->obj[0]->import_attach->dmabuf,
-                                            DMA_FROM_DEVICE);
-               if (ret)
-                       DRM_ERROR("dma_buf_end_cpu_access err: %d\n", ret);
-       }
-vunmap:
-       drm_gem_shmem_vunmap(fb->obj[0], vaddr);
-put_fb:
-       drm_framebuffer_put(fb);
-       gm12u320->fb_update.fb = NULL;
-unlock:
-       mutex_unlock(&gm12u320->fb_update.lock);
-}
-
-static int gm12u320_fb_update_ready(struct gm12u320_device *gm12u320)
-{
-       int ret;
-
-       mutex_lock(&gm12u320->fb_update.lock);
-       ret = !gm12u320->fb_update.run || gm12u320->fb_update.fb != NULL;
-       mutex_unlock(&gm12u320->fb_update.lock);
-
-       return ret;
-}
-
-static void gm12u320_fb_update_work(struct work_struct *work)
-{
-       struct gm12u320_device *gm12u320 =
-               container_of(work, struct gm12u320_device, fb_update.work);
-       int draw_status_timeout = FIRST_FRAME_TIMEOUT;
-       int block, block_size, len;
-       int frame = 0;
-       int ret = 0;
-
-       while (gm12u320->fb_update.run) {
-               gm12u320_copy_fb_to_blocks(gm12u320);
-
-               for (block = 0; block < GM12U320_BLOCK_COUNT; block++) {
-                       if (block == GM12U320_BLOCK_COUNT - 1)
-                               block_size = DATA_LAST_BLOCK_SIZE;
-                       else
-                               block_size = DATA_BLOCK_SIZE;
-
-                       /* Send data command to device */
-                       memcpy(gm12u320->cmd_buf, cmd_data, CMD_SIZE);
-                       gm12u320->cmd_buf[8] = block_size & 0xff;
-                       gm12u320->cmd_buf[9] = block_size >> 8;
-                       gm12u320->cmd_buf[20] = 0xfc - block * 4;
-                       gm12u320->cmd_buf[21] = block | (frame << 7);
-
-                       ret = usb_bulk_msg(gm12u320->udev,
-                               usb_sndbulkpipe(gm12u320->udev, DATA_SND_EPT),
-                               gm12u320->cmd_buf, CMD_SIZE, &len,
-                               CMD_TIMEOUT);
-                       if (ret || len != CMD_SIZE)
-                               goto err;
-
-                       /* Send data block to device */
-                       ret = usb_bulk_msg(gm12u320->udev,
-                               usb_sndbulkpipe(gm12u320->udev, DATA_SND_EPT),
-                               gm12u320->data_buf[block], block_size,
-                               &len, DATA_TIMEOUT);
-                       if (ret || len != block_size)
-                               goto err;
-
-                       /* Read status */
-                       ret = usb_bulk_msg(gm12u320->udev,
-                               usb_rcvbulkpipe(gm12u320->udev, DATA_RCV_EPT),
-                               gm12u320->cmd_buf, READ_STATUS_SIZE, &len,
-                               CMD_TIMEOUT);
-                       if (ret || len != READ_STATUS_SIZE)
-                               goto err;
-               }
-
-               /* Send draw command to device */
-               memcpy(gm12u320->cmd_buf, cmd_draw, CMD_SIZE);
-               ret = usb_bulk_msg(gm12u320->udev,
-                       usb_sndbulkpipe(gm12u320->udev, DATA_SND_EPT),
-                       gm12u320->cmd_buf, CMD_SIZE, &len, CMD_TIMEOUT);
-               if (ret || len != CMD_SIZE)
-                       goto err;
-
-               /* Read status */
-               ret = usb_bulk_msg(gm12u320->udev,
-                       usb_rcvbulkpipe(gm12u320->udev, DATA_RCV_EPT),
-                       gm12u320->cmd_buf, READ_STATUS_SIZE, &len,
-                       draw_status_timeout);
-               if (ret || len != READ_STATUS_SIZE)
-                       goto err;
-
-               draw_status_timeout = CMD_TIMEOUT;
-               frame = !frame;
-
-               /*
-                * We must draw a frame every 2s otherwise the projector
-                * switches back to showing its logo.
-                */
-               wait_event_timeout(gm12u320->fb_update.waitq,
-                                  gm12u320_fb_update_ready(gm12u320),
-                                  IDLE_TIMEOUT);
-       }
-       return;
-err:
-       /* Do not log errors caused by module unload or device unplug */
-       if (ret != -ECONNRESET && ret != -ESHUTDOWN)
-               dev_err(&gm12u320->udev->dev, "Frame update error: %d\n", ret);
-}
-
-static void gm12u320_fb_mark_dirty(struct drm_framebuffer *fb,
-                                  struct drm_rect *dirty)
-{
-       struct gm12u320_device *gm12u320 = fb->dev->dev_private;
-       struct drm_framebuffer *old_fb = NULL;
-       bool wakeup = false;
-
-       mutex_lock(&gm12u320->fb_update.lock);
-
-       if (gm12u320->fb_update.fb != fb) {
-               old_fb = gm12u320->fb_update.fb;
-               drm_framebuffer_get(fb);
-               gm12u320->fb_update.fb = fb;
-               gm12u320->fb_update.rect = *dirty;
-               wakeup = true;
-       } else {
-               struct drm_rect *rect = &gm12u320->fb_update.rect;
-
-               rect->x1 = min(rect->x1, dirty->x1);
-               rect->y1 = min(rect->y1, dirty->y1);
-               rect->x2 = max(rect->x2, dirty->x2);
-               rect->y2 = max(rect->y2, dirty->y2);
-       }
-
-       mutex_unlock(&gm12u320->fb_update.lock);
-
-       if (wakeup)
-               wake_up(&gm12u320->fb_update.waitq);
-
-       if (old_fb)
-               drm_framebuffer_put(old_fb);
-}
-
-static void gm12u320_start_fb_update(struct gm12u320_device *gm12u320)
-{
-       mutex_lock(&gm12u320->fb_update.lock);
-       gm12u320->fb_update.run = true;
-       mutex_unlock(&gm12u320->fb_update.lock);
-
-       queue_work(gm12u320->fb_update.workq, &gm12u320->fb_update.work);
-}
-
-static void gm12u320_stop_fb_update(struct gm12u320_device *gm12u320)
-{
-       mutex_lock(&gm12u320->fb_update.lock);
-       gm12u320->fb_update.run = false;
-       mutex_unlock(&gm12u320->fb_update.lock);
-
-       wake_up(&gm12u320->fb_update.waitq);
-       cancel_work_sync(&gm12u320->fb_update.work);
-
-       mutex_lock(&gm12u320->fb_update.lock);
-       if (gm12u320->fb_update.fb) {
-               drm_framebuffer_put(gm12u320->fb_update.fb);
-               gm12u320->fb_update.fb = NULL;
-       }
-       mutex_unlock(&gm12u320->fb_update.lock);
-}
-
-static int gm12u320_set_ecomode(struct gm12u320_device *gm12u320)
-{
-       return gm12u320_misc_request(gm12u320, MISC_REQ_GET_SET_ECO_A,
-                                    MISC_REQ_GET_SET_ECO_B, 0x01 /* set */,
-                                    eco_mode ? 0x01 : 0x00, 0x00, 0x01);
-}
-
-/* ------------------------------------------------------------------ */
-/* gm12u320 connector                                                */
-
-/*
- * We use fake EDID info so that userspace know that it is dealing with
- * an Acer projector, rather then listing this as an "unknown" monitor.
- * Note this assumes this driver is only ever used with the Acer C120, if we
- * add support for other devices the vendor and model should be parameterized.
- */
-static struct edid gm12u320_edid = {
-       .header         = { 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 },
-       .mfg_id         = { 0x04, 0x72 },       /* "ACR" */
-       .prod_code      = { 0x20, 0xc1 },       /* C120h */
-       .serial         = 0xaa55aa55,
-       .mfg_week       = 1,
-       .mfg_year       = 16,
-       .version        = 1,                    /* EDID 1.3 */
-       .revision       = 3,                    /* EDID 1.3 */
-       .input          = 0x08,                 /* Analog input */
-       .features       = 0x0a,                 /* Pref timing in DTD 1 */
-       .standard_timings = { { 1, 1 }, { 1, 1 }, { 1, 1 }, { 1, 1 },
-                             { 1, 1 }, { 1, 1 }, { 1, 1 }, { 1, 1 } },
-       .detailed_timings = { {
-               .pixel_clock = 3383,
-               /* hactive = 848, hblank = 256 */
-               .data.pixel_data.hactive_lo = 0x50,
-               .data.pixel_data.hblank_lo = 0x00,
-               .data.pixel_data.hactive_hblank_hi = 0x31,
-               /* vactive = 480, vblank = 28 */
-               .data.pixel_data.vactive_lo = 0xe0,
-               .data.pixel_data.vblank_lo = 0x1c,
-               .data.pixel_data.vactive_vblank_hi = 0x10,
-               /* hsync offset 40 pw 128, vsync offset 1 pw 4 */
-               .data.pixel_data.hsync_offset_lo = 0x28,
-               .data.pixel_data.hsync_pulse_width_lo = 0x80,
-               .data.pixel_data.vsync_offset_pulse_width_lo = 0x14,
-               .data.pixel_data.hsync_vsync_offset_pulse_width_hi = 0x00,
-               /* Digital separate syncs, hsync+, vsync+ */
-               .data.pixel_data.misc = 0x1e,
-       }, {
-               .pixel_clock = 0,
-               .data.other_data.type = 0xfd, /* Monitor ranges */
-               .data.other_data.data.range.min_vfreq = 59,
-               .data.other_data.data.range.max_vfreq = 61,
-               .data.other_data.data.range.min_hfreq_khz = 29,
-               .data.other_data.data.range.max_hfreq_khz = 32,
-               .data.other_data.data.range.pixel_clock_mhz = 4, /* 40 MHz */
-               .data.other_data.data.range.flags = 0,
-               .data.other_data.data.range.formula.cvt = {
-                       0xa0, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 },
-       }, {
-               .pixel_clock = 0,
-               .data.other_data.type = 0xfc, /* Model string */
-               .data.other_data.data.str.str = {
-                       'P', 'r', 'o', 'j', 'e', 'c', 't', 'o', 'r', '\n',
-                       ' ', ' ',  ' ' },
-       }, {
-               .pixel_clock = 0,
-               .data.other_data.type = 0xfe, /* Unspecified text / padding */
-               .data.other_data.data.str.str = {
-                       '\n', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
-                       ' ', ' ',  ' ' },
-       } },
-       .checksum = 0x13,
-};
-
-static int gm12u320_conn_get_modes(struct drm_connector *connector)
-{
-       drm_connector_update_edid_property(connector, &gm12u320_edid);
-       return drm_add_edid_modes(connector, &gm12u320_edid);
-}
-
-static const struct drm_connector_helper_funcs gm12u320_conn_helper_funcs = {
-       .get_modes = gm12u320_conn_get_modes,
-};
-
-static const struct drm_connector_funcs gm12u320_conn_funcs = {
-       .fill_modes = drm_helper_probe_single_connector_modes,
-       .destroy = drm_connector_cleanup,
-       .reset = drm_atomic_helper_connector_reset,
-       .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
-       .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
-};
-
-static int gm12u320_conn_init(struct gm12u320_device *gm12u320)
-{
-       drm_connector_helper_add(&gm12u320->conn, &gm12u320_conn_helper_funcs);
-       return drm_connector_init(&gm12u320->dev, &gm12u320->conn,
-                                 &gm12u320_conn_funcs, DRM_MODE_CONNECTOR_VGA);
-}
-
-/* ------------------------------------------------------------------ */
-/* gm12u320 (simple) display pipe                                    */
-
-static void gm12u320_pipe_enable(struct drm_simple_display_pipe *pipe,
-                                struct drm_crtc_state *crtc_state,
-                                struct drm_plane_state *plane_state)
-{
-       struct gm12u320_device *gm12u320 = pipe->crtc.dev->dev_private;
-       struct drm_rect rect = { 0, 0, GM12U320_USER_WIDTH, GM12U320_HEIGHT };
-
-       gm12u320_fb_mark_dirty(plane_state->fb, &rect);
-       gm12u320_start_fb_update(gm12u320);
-       gm12u320->pipe_enabled = true;
-}
-
-static void gm12u320_pipe_disable(struct drm_simple_display_pipe *pipe)
-{
-       struct gm12u320_device *gm12u320 = pipe->crtc.dev->dev_private;
-
-       gm12u320_stop_fb_update(gm12u320);
-       gm12u320->pipe_enabled = false;
-}
-
-static void gm12u320_pipe_update(struct drm_simple_display_pipe *pipe,
-                                struct drm_plane_state *old_state)
-{
-       struct drm_plane_state *state = pipe->plane.state;
-       struct drm_crtc *crtc = &pipe->crtc;
-       struct drm_rect rect;
-
-       if (drm_atomic_helper_damage_merged(old_state, state, &rect))
-               gm12u320_fb_mark_dirty(pipe->plane.state->fb, &rect);
-
-       if (crtc->state->event) {
-               spin_lock_irq(&crtc->dev->event_lock);
-               drm_crtc_send_vblank_event(crtc, crtc->state->event);
-               crtc->state->event = NULL;
-               spin_unlock_irq(&crtc->dev->event_lock);
-       }
-}
-
-static const struct drm_simple_display_pipe_funcs gm12u320_pipe_funcs = {
-       .enable     = gm12u320_pipe_enable,
-       .disable    = gm12u320_pipe_disable,
-       .update     = gm12u320_pipe_update,
-};
-
-static const uint32_t gm12u320_pipe_formats[] = {
-       DRM_FORMAT_XRGB8888,
-};
-
-static const uint64_t gm12u320_pipe_modifiers[] = {
-       DRM_FORMAT_MOD_LINEAR,
-       DRM_FORMAT_MOD_INVALID
-};
-
-static void gm12u320_driver_release(struct drm_device *dev)
-{
-       struct gm12u320_device *gm12u320 = dev->dev_private;
-
-       gm12u320_usb_free(gm12u320);
-       drm_mode_config_cleanup(dev);
-       drm_dev_fini(dev);
-       kfree(gm12u320);
-}
-
-DEFINE_DRM_GEM_SHMEM_FOPS(gm12u320_fops);
-
-static struct drm_driver gm12u320_drm_driver = {
-       .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
-
-       .name            = DRIVER_NAME,
-       .desc            = DRIVER_DESC,
-       .date            = DRIVER_DATE,
-       .major           = DRIVER_MAJOR,
-       .minor           = DRIVER_MINOR,
-
-       .release         = gm12u320_driver_release,
-       .fops            = &gm12u320_fops,
-       DRM_GEM_SHMEM_DRIVER_OPS,
-};
-
-static const struct drm_mode_config_funcs gm12u320_mode_config_funcs = {
-       .fb_create = drm_gem_fb_create_with_dirty,
-       .atomic_check = drm_atomic_helper_check,
-       .atomic_commit = drm_atomic_helper_commit,
-};
-
-static int gm12u320_usb_probe(struct usb_interface *interface,
-                             const struct usb_device_id *id)
-{
-       struct gm12u320_device *gm12u320;
-       struct drm_device *dev;
-       int ret;
-
-       /*
-        * The gm12u320 presents itself to the system as 2 usb mass-storage
-        * interfaces, we only care about / need the first one.
-        */
-       if (interface->cur_altsetting->desc.bInterfaceNumber != 0)
-               return -ENODEV;
-
-       gm12u320 = kzalloc(sizeof(*gm12u320), GFP_KERNEL);
-       if (gm12u320 == NULL)
-               return -ENOMEM;
-
-       gm12u320->udev = interface_to_usbdev(interface);
-       INIT_WORK(&gm12u320->fb_update.work, gm12u320_fb_update_work);
-       mutex_init(&gm12u320->fb_update.lock);
-       init_waitqueue_head(&gm12u320->fb_update.waitq);
-
-       dev = &gm12u320->dev;
-       ret = drm_dev_init(dev, &gm12u320_drm_driver, &interface->dev);
-       if (ret) {
-               kfree(gm12u320);
-               return ret;
-       }
-       dev->dev_private = gm12u320;
-
-       drm_mode_config_init(dev);
-       dev->mode_config.min_width = GM12U320_USER_WIDTH;
-       dev->mode_config.max_width = GM12U320_USER_WIDTH;
-       dev->mode_config.min_height = GM12U320_HEIGHT;
-       dev->mode_config.max_height = GM12U320_HEIGHT;
-       dev->mode_config.funcs = &gm12u320_mode_config_funcs;
-
-       ret = gm12u320_usb_alloc(gm12u320);
-       if (ret)
-               goto err_put;
-
-       ret = gm12u320_set_ecomode(gm12u320);
-       if (ret)
-               goto err_put;
-
-       ret = gm12u320_conn_init(gm12u320);
-       if (ret)
-               goto err_put;
-
-       ret = drm_simple_display_pipe_init(&gm12u320->dev,
-                                          &gm12u320->pipe,
-                                          &gm12u320_pipe_funcs,
-                                          gm12u320_pipe_formats,
-                                          ARRAY_SIZE(gm12u320_pipe_formats),
-                                          gm12u320_pipe_modifiers,
-                                          &gm12u320->conn);
-       if (ret)
-               goto err_put;
-
-       drm_mode_config_reset(dev);
-
-       usb_set_intfdata(interface, dev);
-       ret = drm_dev_register(dev, 0);
-       if (ret)
-               goto err_put;
-
-       drm_fbdev_generic_setup(dev, dev->mode_config.preferred_depth);
-
-       return 0;
-
-err_put:
-       drm_dev_put(dev);
-       return ret;
-}
-
-static void gm12u320_usb_disconnect(struct usb_interface *interface)
-{
-       struct drm_device *dev = usb_get_intfdata(interface);
-       struct gm12u320_device *gm12u320 = dev->dev_private;
-
-       gm12u320_stop_fb_update(gm12u320);
-       drm_dev_unplug(dev);
-       drm_dev_put(dev);
-}
-
-#ifdef CONFIG_PM
-static int gm12u320_suspend(struct usb_interface *interface,
-                           pm_message_t message)
-{
-       struct drm_device *dev = usb_get_intfdata(interface);
-       struct gm12u320_device *gm12u320 = dev->dev_private;
-
-       if (gm12u320->pipe_enabled)
-               gm12u320_stop_fb_update(gm12u320);
-
-       return 0;
-}
-
-static int gm12u320_resume(struct usb_interface *interface)
-{
-       struct drm_device *dev = usb_get_intfdata(interface);
-       struct gm12u320_device *gm12u320 = dev->dev_private;
-
-       gm12u320_set_ecomode(gm12u320);
-       if (gm12u320->pipe_enabled)
-               gm12u320_start_fb_update(gm12u320);
-
-       return 0;
-}
-#endif
-
-static const struct usb_device_id id_table[] = {
-       { USB_DEVICE(0x1de1, 0xc102) },
-       {},
-};
-MODULE_DEVICE_TABLE(usb, id_table);
-
-static struct usb_driver gm12u320_usb_driver = {
-       .name = "gm12u320",
-       .probe = gm12u320_usb_probe,
-       .disconnect = gm12u320_usb_disconnect,
-       .id_table = id_table,
-#ifdef CONFIG_PM
-       .suspend = gm12u320_suspend,
-       .resume = gm12u320_resume,
-       .reset_resume = gm12u320_resume,
-#endif
-};
-
-module_usb_driver(gm12u320_usb_driver);
-MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
-MODULE_LICENSE("GPL");
index f8c9a0e71dde889fb1f7337bb74b192cc7ef7475..504763423d463a69122e3fe92870c8bec8b63aad 100644 (file)
@@ -1,4 +1,14 @@
 # SPDX-License-Identifier: GPL-2.0-only
+
+config DRM_GM12U320
+       tristate "GM12U320 driver for USB projectors"
+       depends on DRM && USB
+       select DRM_KMS_HELPER
+       select DRM_GEM_SHMEM_HELPER
+       help
+        This is a KMS driver for projectors which use the GM12U320 chipset
+        for video transfer over USB2/3, such as the Acer C120 mini projector.
+
 config TINYDRM_HX8357D
        tristate "DRM support for HX8357D display panels"
        depends on DRM && SPI
index 6490167a9ad141851da7a5c0f2a28ae1aef95aab..896cf31132d397b6ac80fc2d8f87d355b06bb78a 100644 (file)
@@ -1,5 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0-only
 
+obj-$(CONFIG_DRM_GM12U320)             += gm12u320.o
 obj-$(CONFIG_TINYDRM_HX8357D)          += hx8357d.o
 obj-$(CONFIG_TINYDRM_ILI9225)          += ili9225.o
 obj-$(CONFIG_TINYDRM_ILI9341)          += ili9341.o
diff --git a/drivers/gpu/drm/tiny/gm12u320.c b/drivers/gpu/drm/tiny/gm12u320.c
new file mode 100644 (file)
index 0000000..b6f47b8
--- /dev/null
@@ -0,0 +1,814 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2019 Hans de Goede <hdegoede@redhat.com>
+ */
+
+#include <linux/dma-buf.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_atomic_state_helper.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_damage_helper.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_file.h>
+#include <drm/drm_format_helper.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_gem_shmem_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_ioctl.h>
+#include <drm/drm_modeset_helper_vtables.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+#include <drm/drm_vblank.h>
+
+static bool eco_mode;
+module_param(eco_mode, bool, 0644);
+MODULE_PARM_DESC(eco_mode, "Turn on Eco mode (less bright, more silent)");
+
+#define DRIVER_NAME            "gm12u320"
+#define DRIVER_DESC            "Grain Media GM12U320 USB projector display"
+#define DRIVER_DATE            "2019"
+#define DRIVER_MAJOR           1
+#define DRIVER_MINOR           0
+#define DRIVER_PATCHLEVEL      1
+
+/*
+ * The DLP has an actual width of 854 pixels, but that is not a multiple
+ * of 8, breaking things left and right, so we export a width of 848.
+ */
+#define GM12U320_USER_WIDTH            848
+#define GM12U320_REAL_WIDTH            854
+#define GM12U320_HEIGHT                        480
+
+#define GM12U320_BLOCK_COUNT           20
+
+#define MISC_RCV_EPT                   1
+#define DATA_RCV_EPT                   2
+#define DATA_SND_EPT                   3
+#define MISC_SND_EPT                   4
+
+#define DATA_BLOCK_HEADER_SIZE         84
+#define DATA_BLOCK_CONTENT_SIZE                64512
+#define DATA_BLOCK_FOOTER_SIZE         20
+#define DATA_BLOCK_SIZE                        (DATA_BLOCK_HEADER_SIZE + \
+                                        DATA_BLOCK_CONTENT_SIZE + \
+                                        DATA_BLOCK_FOOTER_SIZE)
+#define DATA_LAST_BLOCK_CONTENT_SIZE   4032
+#define DATA_LAST_BLOCK_SIZE           (DATA_BLOCK_HEADER_SIZE + \
+                                        DATA_LAST_BLOCK_CONTENT_SIZE + \
+                                        DATA_BLOCK_FOOTER_SIZE)
+
+#define CMD_SIZE                       31
+#define READ_STATUS_SIZE               13
+#define MISC_VALUE_SIZE                        4
+
+#define CMD_TIMEOUT                    msecs_to_jiffies(200)
+#define DATA_TIMEOUT                   msecs_to_jiffies(1000)
+#define IDLE_TIMEOUT                   msecs_to_jiffies(2000)
+#define FIRST_FRAME_TIMEOUT            msecs_to_jiffies(2000)
+
+#define MISC_REQ_GET_SET_ECO_A         0xff
+#define MISC_REQ_GET_SET_ECO_B         0x35
+/* Windows driver does once every second, with arg d = 1, other args 0 */
+#define MISC_REQ_UNKNOWN1_A            0xff
+#define MISC_REQ_UNKNOWN1_B            0x38
+/* Windows driver does this on init, with arg a, b = 0, c = 0xa0, d = 4 */
+#define MISC_REQ_UNKNOWN2_A            0xa5
+#define MISC_REQ_UNKNOWN2_B            0x00
+
+struct gm12u320_device {
+       struct drm_device                dev;
+       struct drm_simple_display_pipe   pipe;
+       struct drm_connector             conn;
+       struct usb_device               *udev;
+       unsigned char                   *cmd_buf;
+       unsigned char                   *data_buf[GM12U320_BLOCK_COUNT];
+       bool                             pipe_enabled;
+       struct {
+               bool                     run;
+               struct workqueue_struct *workq;
+               struct work_struct       work;
+               wait_queue_head_t        waitq;
+               struct mutex             lock;
+               struct drm_framebuffer  *fb;
+               struct drm_rect          rect;
+       } fb_update;
+};
+
+static const char cmd_data[CMD_SIZE] = {
+       0x55, 0x53, 0x42, 0x43, 0x00, 0x00, 0x00, 0x00,
+       0x68, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x10, 0xff,
+       0x00, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x80, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+static const char cmd_draw[CMD_SIZE] = {
+       0x55, 0x53, 0x42, 0x43, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xfe,
+       0x00, 0x00, 0x00, 0xc0, 0xd1, 0x05, 0x00, 0x40,
+       0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+static const char cmd_misc[CMD_SIZE] = {
+       0x55, 0x53, 0x42, 0x43, 0x00, 0x00, 0x00, 0x00,
+       0x04, 0x00, 0x00, 0x00, 0x80, 0x01, 0x10, 0xfd,
+       0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+static const char data_block_header[DATA_BLOCK_HEADER_SIZE] = {
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0xfb, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x04, 0x15, 0x00, 0x00, 0xfc, 0x00, 0x00,
+       0x01, 0x00, 0x00, 0xdb
+};
+
+static const char data_last_block_header[DATA_BLOCK_HEADER_SIZE] = {
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0xfb, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x2a, 0x00, 0x20, 0x00, 0xc0, 0x0f, 0x00, 0x00,
+       0x01, 0x00, 0x00, 0xd7
+};
+
+static const char data_block_footer[DATA_BLOCK_FOOTER_SIZE] = {
+       0xfb, 0x14, 0x02, 0x20, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x80, 0x00, 0x00, 0x4f
+};
+
+static int gm12u320_usb_alloc(struct gm12u320_device *gm12u320)
+{
+       int i, block_size;
+       const char *hdr;
+
+       gm12u320->cmd_buf = kmalloc(CMD_SIZE, GFP_KERNEL);
+       if (!gm12u320->cmd_buf)
+               return -ENOMEM;
+
+       for (i = 0; i < GM12U320_BLOCK_COUNT; i++) {
+               if (i == GM12U320_BLOCK_COUNT - 1) {
+                       block_size = DATA_LAST_BLOCK_SIZE;
+                       hdr = data_last_block_header;
+               } else {
+                       block_size = DATA_BLOCK_SIZE;
+                       hdr = data_block_header;
+               }
+
+               gm12u320->data_buf[i] = kzalloc(block_size, GFP_KERNEL);
+               if (!gm12u320->data_buf[i])
+                       return -ENOMEM;
+
+               memcpy(gm12u320->data_buf[i], hdr, DATA_BLOCK_HEADER_SIZE);
+               memcpy(gm12u320->data_buf[i] +
+                               (block_size - DATA_BLOCK_FOOTER_SIZE),
+                      data_block_footer, DATA_BLOCK_FOOTER_SIZE);
+       }
+
+       gm12u320->fb_update.workq = create_singlethread_workqueue(DRIVER_NAME);
+       if (!gm12u320->fb_update.workq)
+               return -ENOMEM;
+
+       return 0;
+}
+
+static void gm12u320_usb_free(struct gm12u320_device *gm12u320)
+{
+       int i;
+
+       if (gm12u320->fb_update.workq)
+               destroy_workqueue(gm12u320->fb_update.workq);
+
+       for (i = 0; i < GM12U320_BLOCK_COUNT; i++)
+               kfree(gm12u320->data_buf[i]);
+
+       kfree(gm12u320->cmd_buf);
+}
+
+static int gm12u320_misc_request(struct gm12u320_device *gm12u320,
+                                u8 req_a, u8 req_b,
+                                u8 arg_a, u8 arg_b, u8 arg_c, u8 arg_d)
+{
+       int ret, len;
+
+       memcpy(gm12u320->cmd_buf, &cmd_misc, CMD_SIZE);
+       gm12u320->cmd_buf[20] = req_a;
+       gm12u320->cmd_buf[21] = req_b;
+       gm12u320->cmd_buf[22] = arg_a;
+       gm12u320->cmd_buf[23] = arg_b;
+       gm12u320->cmd_buf[24] = arg_c;
+       gm12u320->cmd_buf[25] = arg_d;
+
+       /* Send request */
+       ret = usb_bulk_msg(gm12u320->udev,
+                          usb_sndbulkpipe(gm12u320->udev, MISC_SND_EPT),
+                          gm12u320->cmd_buf, CMD_SIZE, &len, CMD_TIMEOUT);
+       if (ret || len != CMD_SIZE) {
+               dev_err(&gm12u320->udev->dev, "Misc. req. error %d\n", ret);
+               return -EIO;
+       }
+
+       /* Read value */
+       ret = usb_bulk_msg(gm12u320->udev,
+                          usb_rcvbulkpipe(gm12u320->udev, MISC_RCV_EPT),
+                          gm12u320->cmd_buf, MISC_VALUE_SIZE, &len,
+                          DATA_TIMEOUT);
+       if (ret || len != MISC_VALUE_SIZE) {
+               dev_err(&gm12u320->udev->dev, "Misc. value error %d\n", ret);
+               return -EIO;
+       }
+       /* cmd_buf[0] now contains the read value, which we don't use */
+
+       /* Read status */
+       ret = usb_bulk_msg(gm12u320->udev,
+                          usb_rcvbulkpipe(gm12u320->udev, MISC_RCV_EPT),
+                          gm12u320->cmd_buf, READ_STATUS_SIZE, &len,
+                          CMD_TIMEOUT);
+       if (ret || len != READ_STATUS_SIZE) {
+               dev_err(&gm12u320->udev->dev, "Misc. status error %d\n", ret);
+               return -EIO;
+       }
+
+       return 0;
+}
+
+static void gm12u320_32bpp_to_24bpp_packed(u8 *dst, u8 *src, int len)
+{
+       while (len--) {
+               *dst++ = *src++;
+               *dst++ = *src++;
+               *dst++ = *src++;
+               src++;
+       }
+}
+
+static void gm12u320_copy_fb_to_blocks(struct gm12u320_device *gm12u320)
+{
+       int block, dst_offset, len, remain, ret, x1, x2, y1, y2;
+       struct drm_framebuffer *fb;
+       void *vaddr;
+       u8 *src;
+
+       mutex_lock(&gm12u320->fb_update.lock);
+
+       if (!gm12u320->fb_update.fb)
+               goto unlock;
+
+       fb = gm12u320->fb_update.fb;
+       x1 = gm12u320->fb_update.rect.x1;
+       x2 = gm12u320->fb_update.rect.x2;
+       y1 = gm12u320->fb_update.rect.y1;
+       y2 = gm12u320->fb_update.rect.y2;
+
+       vaddr = drm_gem_shmem_vmap(fb->obj[0]);
+       if (IS_ERR(vaddr)) {
+               DRM_ERROR("failed to vmap fb: %ld\n", PTR_ERR(vaddr));
+               goto put_fb;
+       }
+
+       if (fb->obj[0]->import_attach) {
+               ret = dma_buf_begin_cpu_access(
+                       fb->obj[0]->import_attach->dmabuf, DMA_FROM_DEVICE);
+               if (ret) {
+                       DRM_ERROR("dma_buf_begin_cpu_access err: %d\n", ret);
+                       goto vunmap;
+               }
+       }
+
+       src = vaddr + y1 * fb->pitches[0] + x1 * 4;
+
+       x1 += (GM12U320_REAL_WIDTH - GM12U320_USER_WIDTH) / 2;
+       x2 += (GM12U320_REAL_WIDTH - GM12U320_USER_WIDTH) / 2;
+
+       for (; y1 < y2; y1++) {
+               remain = 0;
+               len = (x2 - x1) * 3;
+               dst_offset = (y1 * GM12U320_REAL_WIDTH + x1) * 3;
+               block = dst_offset / DATA_BLOCK_CONTENT_SIZE;
+               dst_offset %= DATA_BLOCK_CONTENT_SIZE;
+
+               if ((dst_offset + len) > DATA_BLOCK_CONTENT_SIZE) {
+                       remain = dst_offset + len - DATA_BLOCK_CONTENT_SIZE;
+                       len = DATA_BLOCK_CONTENT_SIZE - dst_offset;
+               }
+
+               dst_offset += DATA_BLOCK_HEADER_SIZE;
+               len /= 3;
+
+               gm12u320_32bpp_to_24bpp_packed(
+                       gm12u320->data_buf[block] + dst_offset,
+                       src, len);
+
+               if (remain) {
+                       block++;
+                       dst_offset = DATA_BLOCK_HEADER_SIZE;
+                       gm12u320_32bpp_to_24bpp_packed(
+                               gm12u320->data_buf[block] + dst_offset,
+                               src + len * 4, remain / 3);
+               }
+               src += fb->pitches[0];
+       }
+
+       if (fb->obj[0]->import_attach) {
+               ret = dma_buf_end_cpu_access(fb->obj[0]->import_attach->dmabuf,
+                                            DMA_FROM_DEVICE);
+               if (ret)
+                       DRM_ERROR("dma_buf_end_cpu_access err: %d\n", ret);
+       }
+vunmap:
+       drm_gem_shmem_vunmap(fb->obj[0], vaddr);
+put_fb:
+       drm_framebuffer_put(fb);
+       gm12u320->fb_update.fb = NULL;
+unlock:
+       mutex_unlock(&gm12u320->fb_update.lock);
+}
+
+static int gm12u320_fb_update_ready(struct gm12u320_device *gm12u320)
+{
+       int ret;
+
+       mutex_lock(&gm12u320->fb_update.lock);
+       ret = !gm12u320->fb_update.run || gm12u320->fb_update.fb != NULL;
+       mutex_unlock(&gm12u320->fb_update.lock);
+
+       return ret;
+}
+
+static void gm12u320_fb_update_work(struct work_struct *work)
+{
+       struct gm12u320_device *gm12u320 =
+               container_of(work, struct gm12u320_device, fb_update.work);
+       int draw_status_timeout = FIRST_FRAME_TIMEOUT;
+       int block, block_size, len;
+       int frame = 0;
+       int ret = 0;
+
+       while (gm12u320->fb_update.run) {
+               gm12u320_copy_fb_to_blocks(gm12u320);
+
+               for (block = 0; block < GM12U320_BLOCK_COUNT; block++) {
+                       if (block == GM12U320_BLOCK_COUNT - 1)
+                               block_size = DATA_LAST_BLOCK_SIZE;
+                       else
+                               block_size = DATA_BLOCK_SIZE;
+
+                       /* Send data command to device */
+                       memcpy(gm12u320->cmd_buf, cmd_data, CMD_SIZE);
+                       gm12u320->cmd_buf[8] = block_size & 0xff;
+                       gm12u320->cmd_buf[9] = block_size >> 8;
+                       gm12u320->cmd_buf[20] = 0xfc - block * 4;
+                       gm12u320->cmd_buf[21] = block | (frame << 7);
+
+                       ret = usb_bulk_msg(gm12u320->udev,
+                               usb_sndbulkpipe(gm12u320->udev, DATA_SND_EPT),
+                               gm12u320->cmd_buf, CMD_SIZE, &len,
+                               CMD_TIMEOUT);
+                       if (ret || len != CMD_SIZE)
+                               goto err;
+
+                       /* Send data block to device */
+                       ret = usb_bulk_msg(gm12u320->udev,
+                               usb_sndbulkpipe(gm12u320->udev, DATA_SND_EPT),
+                               gm12u320->data_buf[block], block_size,
+                               &len, DATA_TIMEOUT);
+                       if (ret || len != block_size)
+                               goto err;
+
+                       /* Read status */
+                       ret = usb_bulk_msg(gm12u320->udev,
+                               usb_rcvbulkpipe(gm12u320->udev, DATA_RCV_EPT),
+                               gm12u320->cmd_buf, READ_STATUS_SIZE, &len,
+                               CMD_TIMEOUT);
+                       if (ret || len != READ_STATUS_SIZE)
+                               goto err;
+               }
+
+               /* Send draw command to device */
+               memcpy(gm12u320->cmd_buf, cmd_draw, CMD_SIZE);
+               ret = usb_bulk_msg(gm12u320->udev,
+                       usb_sndbulkpipe(gm12u320->udev, DATA_SND_EPT),
+                       gm12u320->cmd_buf, CMD_SIZE, &len, CMD_TIMEOUT);
+               if (ret || len != CMD_SIZE)
+                       goto err;
+
+               /* Read status */
+               ret = usb_bulk_msg(gm12u320->udev,
+                       usb_rcvbulkpipe(gm12u320->udev, DATA_RCV_EPT),
+                       gm12u320->cmd_buf, READ_STATUS_SIZE, &len,
+                       draw_status_timeout);
+               if (ret || len != READ_STATUS_SIZE)
+                       goto err;
+
+               draw_status_timeout = CMD_TIMEOUT;
+               frame = !frame;
+
+               /*
+                * We must draw a frame every 2s otherwise the projector
+                * switches back to showing its logo.
+                */
+               wait_event_timeout(gm12u320->fb_update.waitq,
+                                  gm12u320_fb_update_ready(gm12u320),
+                                  IDLE_TIMEOUT);
+       }
+       return;
+err:
+       /* Do not log errors caused by module unload or device unplug */
+       if (ret != -ECONNRESET && ret != -ESHUTDOWN)
+               dev_err(&gm12u320->udev->dev, "Frame update error: %d\n", ret);
+}
+
+static void gm12u320_fb_mark_dirty(struct drm_framebuffer *fb,
+                                  struct drm_rect *dirty)
+{
+       struct gm12u320_device *gm12u320 = fb->dev->dev_private;
+       struct drm_framebuffer *old_fb = NULL;
+       bool wakeup = false;
+
+       mutex_lock(&gm12u320->fb_update.lock);
+
+       if (gm12u320->fb_update.fb != fb) {
+               old_fb = gm12u320->fb_update.fb;
+               drm_framebuffer_get(fb);
+               gm12u320->fb_update.fb = fb;
+               gm12u320->fb_update.rect = *dirty;
+               wakeup = true;
+       } else {
+               struct drm_rect *rect = &gm12u320->fb_update.rect;
+
+               rect->x1 = min(rect->x1, dirty->x1);
+               rect->y1 = min(rect->y1, dirty->y1);
+               rect->x2 = max(rect->x2, dirty->x2);
+               rect->y2 = max(rect->y2, dirty->y2);
+       }
+
+       mutex_unlock(&gm12u320->fb_update.lock);
+
+       if (wakeup)
+               wake_up(&gm12u320->fb_update.waitq);
+
+       if (old_fb)
+               drm_framebuffer_put(old_fb);
+}
+
+static void gm12u320_start_fb_update(struct gm12u320_device *gm12u320)
+{
+       mutex_lock(&gm12u320->fb_update.lock);
+       gm12u320->fb_update.run = true;
+       mutex_unlock(&gm12u320->fb_update.lock);
+
+       queue_work(gm12u320->fb_update.workq, &gm12u320->fb_update.work);
+}
+
+static void gm12u320_stop_fb_update(struct gm12u320_device *gm12u320)
+{
+       mutex_lock(&gm12u320->fb_update.lock);
+       gm12u320->fb_update.run = false;
+       mutex_unlock(&gm12u320->fb_update.lock);
+
+       wake_up(&gm12u320->fb_update.waitq);
+       cancel_work_sync(&gm12u320->fb_update.work);
+
+       mutex_lock(&gm12u320->fb_update.lock);
+       if (gm12u320->fb_update.fb) {
+               drm_framebuffer_put(gm12u320->fb_update.fb);
+               gm12u320->fb_update.fb = NULL;
+       }
+       mutex_unlock(&gm12u320->fb_update.lock);
+}
+
+static int gm12u320_set_ecomode(struct gm12u320_device *gm12u320)
+{
+       return gm12u320_misc_request(gm12u320, MISC_REQ_GET_SET_ECO_A,
+                                    MISC_REQ_GET_SET_ECO_B, 0x01 /* set */,
+                                    eco_mode ? 0x01 : 0x00, 0x00, 0x01);
+}
+
+/* ------------------------------------------------------------------ */
+/* gm12u320 connector                                                */
+
+/*
+ * We use fake EDID info so that userspace know that it is dealing with
+ * an Acer projector, rather then listing this as an "unknown" monitor.
+ * Note this assumes this driver is only ever used with the Acer C120, if we
+ * add support for other devices the vendor and model should be parameterized.
+ */
+static struct edid gm12u320_edid = {
+       .header         = { 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 },
+       .mfg_id         = { 0x04, 0x72 },       /* "ACR" */
+       .prod_code      = { 0x20, 0xc1 },       /* C120h */
+       .serial         = 0xaa55aa55,
+       .mfg_week       = 1,
+       .mfg_year       = 16,
+       .version        = 1,                    /* EDID 1.3 */
+       .revision       = 3,                    /* EDID 1.3 */
+       .input          = 0x08,                 /* Analog input */
+       .features       = 0x0a,                 /* Pref timing in DTD 1 */
+       .standard_timings = { { 1, 1 }, { 1, 1 }, { 1, 1 }, { 1, 1 },
+                             { 1, 1 }, { 1, 1 }, { 1, 1 }, { 1, 1 } },
+       .detailed_timings = { {
+               .pixel_clock = 3383,
+               /* hactive = 848, hblank = 256 */
+               .data.pixel_data.hactive_lo = 0x50,
+               .data.pixel_data.hblank_lo = 0x00,
+               .data.pixel_data.hactive_hblank_hi = 0x31,
+               /* vactive = 480, vblank = 28 */
+               .data.pixel_data.vactive_lo = 0xe0,
+               .data.pixel_data.vblank_lo = 0x1c,
+               .data.pixel_data.vactive_vblank_hi = 0x10,
+               /* hsync offset 40 pw 128, vsync offset 1 pw 4 */
+               .data.pixel_data.hsync_offset_lo = 0x28,
+               .data.pixel_data.hsync_pulse_width_lo = 0x80,
+               .data.pixel_data.vsync_offset_pulse_width_lo = 0x14,
+               .data.pixel_data.hsync_vsync_offset_pulse_width_hi = 0x00,
+               /* Digital separate syncs, hsync+, vsync+ */
+               .data.pixel_data.misc = 0x1e,
+       }, {
+               .pixel_clock = 0,
+               .data.other_data.type = 0xfd, /* Monitor ranges */
+               .data.other_data.data.range.min_vfreq = 59,
+               .data.other_data.data.range.max_vfreq = 61,
+               .data.other_data.data.range.min_hfreq_khz = 29,
+               .data.other_data.data.range.max_hfreq_khz = 32,
+               .data.other_data.data.range.pixel_clock_mhz = 4, /* 40 MHz */
+               .data.other_data.data.range.flags = 0,
+               .data.other_data.data.range.formula.cvt = {
+                       0xa0, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 },
+       }, {
+               .pixel_clock = 0,
+               .data.other_data.type = 0xfc, /* Model string */
+               .data.other_data.data.str.str = {
+                       'P', 'r', 'o', 'j', 'e', 'c', 't', 'o', 'r', '\n',
+                       ' ', ' ',  ' ' },
+       }, {
+               .pixel_clock = 0,
+               .data.other_data.type = 0xfe, /* Unspecified text / padding */
+               .data.other_data.data.str.str = {
+                       '\n', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
+                       ' ', ' ',  ' ' },
+       } },
+       .checksum = 0x13,
+};
+
+static int gm12u320_conn_get_modes(struct drm_connector *connector)
+{
+       drm_connector_update_edid_property(connector, &gm12u320_edid);
+       return drm_add_edid_modes(connector, &gm12u320_edid);
+}
+
+static const struct drm_connector_helper_funcs gm12u320_conn_helper_funcs = {
+       .get_modes = gm12u320_conn_get_modes,
+};
+
+static const struct drm_connector_funcs gm12u320_conn_funcs = {
+       .fill_modes = drm_helper_probe_single_connector_modes,
+       .destroy = drm_connector_cleanup,
+       .reset = drm_atomic_helper_connector_reset,
+       .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+       .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static int gm12u320_conn_init(struct gm12u320_device *gm12u320)
+{
+       drm_connector_helper_add(&gm12u320->conn, &gm12u320_conn_helper_funcs);
+       return drm_connector_init(&gm12u320->dev, &gm12u320->conn,
+                                 &gm12u320_conn_funcs, DRM_MODE_CONNECTOR_VGA);
+}
+
+/* ------------------------------------------------------------------ */
+/* gm12u320 (simple) display pipe                                    */
+
+static void gm12u320_pipe_enable(struct drm_simple_display_pipe *pipe,
+                                struct drm_crtc_state *crtc_state,
+                                struct drm_plane_state *plane_state)
+{
+       struct gm12u320_device *gm12u320 = pipe->crtc.dev->dev_private;
+       struct drm_rect rect = { 0, 0, GM12U320_USER_WIDTH, GM12U320_HEIGHT };
+
+       gm12u320_fb_mark_dirty(plane_state->fb, &rect);
+       gm12u320_start_fb_update(gm12u320);
+       gm12u320->pipe_enabled = true;
+}
+
+static void gm12u320_pipe_disable(struct drm_simple_display_pipe *pipe)
+{
+       struct gm12u320_device *gm12u320 = pipe->crtc.dev->dev_private;
+
+       gm12u320_stop_fb_update(gm12u320);
+       gm12u320->pipe_enabled = false;
+}
+
+static void gm12u320_pipe_update(struct drm_simple_display_pipe *pipe,
+                                struct drm_plane_state *old_state)
+{
+       struct drm_plane_state *state = pipe->plane.state;
+       struct drm_crtc *crtc = &pipe->crtc;
+       struct drm_rect rect;
+
+       if (drm_atomic_helper_damage_merged(old_state, state, &rect))
+               gm12u320_fb_mark_dirty(pipe->plane.state->fb, &rect);
+
+       if (crtc->state->event) {
+               spin_lock_irq(&crtc->dev->event_lock);
+               drm_crtc_send_vblank_event(crtc, crtc->state->event);
+               crtc->state->event = NULL;
+               spin_unlock_irq(&crtc->dev->event_lock);
+       }
+}
+
+static const struct drm_simple_display_pipe_funcs gm12u320_pipe_funcs = {
+       .enable     = gm12u320_pipe_enable,
+       .disable    = gm12u320_pipe_disable,
+       .update     = gm12u320_pipe_update,
+};
+
+static const uint32_t gm12u320_pipe_formats[] = {
+       DRM_FORMAT_XRGB8888,
+};
+
+static const uint64_t gm12u320_pipe_modifiers[] = {
+       DRM_FORMAT_MOD_LINEAR,
+       DRM_FORMAT_MOD_INVALID
+};
+
+static void gm12u320_driver_release(struct drm_device *dev)
+{
+       struct gm12u320_device *gm12u320 = dev->dev_private;
+
+       gm12u320_usb_free(gm12u320);
+       drm_mode_config_cleanup(dev);
+       drm_dev_fini(dev);
+       kfree(gm12u320);
+}
+
+DEFINE_DRM_GEM_SHMEM_FOPS(gm12u320_fops);
+
+static struct drm_driver gm12u320_drm_driver = {
+       .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
+
+       .name            = DRIVER_NAME,
+       .desc            = DRIVER_DESC,
+       .date            = DRIVER_DATE,
+       .major           = DRIVER_MAJOR,
+       .minor           = DRIVER_MINOR,
+
+       .release         = gm12u320_driver_release,
+       .fops            = &gm12u320_fops,
+       DRM_GEM_SHMEM_DRIVER_OPS,
+};
+
+static const struct drm_mode_config_funcs gm12u320_mode_config_funcs = {
+       .fb_create = drm_gem_fb_create_with_dirty,
+       .atomic_check = drm_atomic_helper_check,
+       .atomic_commit = drm_atomic_helper_commit,
+};
+
+static int gm12u320_usb_probe(struct usb_interface *interface,
+                             const struct usb_device_id *id)
+{
+       struct gm12u320_device *gm12u320;
+       struct drm_device *dev;
+       int ret;
+
+       /*
+        * The gm12u320 presents itself to the system as 2 usb mass-storage
+        * interfaces, we only care about / need the first one.
+        */
+       if (interface->cur_altsetting->desc.bInterfaceNumber != 0)
+               return -ENODEV;
+
+       gm12u320 = kzalloc(sizeof(*gm12u320), GFP_KERNEL);
+       if (gm12u320 == NULL)
+               return -ENOMEM;
+
+       gm12u320->udev = interface_to_usbdev(interface);
+       INIT_WORK(&gm12u320->fb_update.work, gm12u320_fb_update_work);
+       mutex_init(&gm12u320->fb_update.lock);
+       init_waitqueue_head(&gm12u320->fb_update.waitq);
+
+       dev = &gm12u320->dev;
+       ret = drm_dev_init(dev, &gm12u320_drm_driver, &interface->dev);
+       if (ret) {
+               kfree(gm12u320);
+               return ret;
+       }
+       dev->dev_private = gm12u320;
+
+       drm_mode_config_init(dev);
+       dev->mode_config.min_width = GM12U320_USER_WIDTH;
+       dev->mode_config.max_width = GM12U320_USER_WIDTH;
+       dev->mode_config.min_height = GM12U320_HEIGHT;
+       dev->mode_config.max_height = GM12U320_HEIGHT;
+       dev->mode_config.funcs = &gm12u320_mode_config_funcs;
+
+       ret = gm12u320_usb_alloc(gm12u320);
+       if (ret)
+               goto err_put;
+
+       ret = gm12u320_set_ecomode(gm12u320);
+       if (ret)
+               goto err_put;
+
+       ret = gm12u320_conn_init(gm12u320);
+       if (ret)
+               goto err_put;
+
+       ret = drm_simple_display_pipe_init(&gm12u320->dev,
+                                          &gm12u320->pipe,
+                                          &gm12u320_pipe_funcs,
+                                          gm12u320_pipe_formats,
+                                          ARRAY_SIZE(gm12u320_pipe_formats),
+                                          gm12u320_pipe_modifiers,
+                                          &gm12u320->conn);
+       if (ret)
+               goto err_put;
+
+       drm_mode_config_reset(dev);
+
+       usb_set_intfdata(interface, dev);
+       ret = drm_dev_register(dev, 0);
+       if (ret)
+               goto err_put;
+
+       drm_fbdev_generic_setup(dev, dev->mode_config.preferred_depth);
+
+       return 0;
+
+err_put:
+       drm_dev_put(dev);
+       return ret;
+}
+
+static void gm12u320_usb_disconnect(struct usb_interface *interface)
+{
+       struct drm_device *dev = usb_get_intfdata(interface);
+       struct gm12u320_device *gm12u320 = dev->dev_private;
+
+       gm12u320_stop_fb_update(gm12u320);
+       drm_dev_unplug(dev);
+       drm_dev_put(dev);
+}
+
+#ifdef CONFIG_PM
+static int gm12u320_suspend(struct usb_interface *interface,
+                           pm_message_t message)
+{
+       struct drm_device *dev = usb_get_intfdata(interface);
+       struct gm12u320_device *gm12u320 = dev->dev_private;
+
+       if (gm12u320->pipe_enabled)
+               gm12u320_stop_fb_update(gm12u320);
+
+       return 0;
+}
+
+static int gm12u320_resume(struct usb_interface *interface)
+{
+       struct drm_device *dev = usb_get_intfdata(interface);
+       struct gm12u320_device *gm12u320 = dev->dev_private;
+
+       gm12u320_set_ecomode(gm12u320);
+       if (gm12u320->pipe_enabled)
+               gm12u320_start_fb_update(gm12u320);
+
+       return 0;
+}
+#endif
+
+static const struct usb_device_id id_table[] = {
+       { USB_DEVICE(0x1de1, 0xc102) },
+       {},
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+static struct usb_driver gm12u320_usb_driver = {
+       .name = "gm12u320",
+       .probe = gm12u320_usb_probe,
+       .disconnect = gm12u320_usb_disconnect,
+       .id_table = id_table,
+#ifdef CONFIG_PM
+       .suspend = gm12u320_suspend,
+       .resume = gm12u320_resume,
+       .reset_resume = gm12u320_resume,
+#endif
+};
+
+module_usb_driver(gm12u320_usb_driver);
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_LICENSE("GPL");