Add optional display functionality to the Multifunction USB Device.
The bulk of the code is placed in the drm subsystem since it's reaching
into the drm internals.

Signed-off-by: Noralf Trønnes <nor...@tronnes.org>
---
 drivers/gpu/drm/mud/Kconfig             |   3 +
 drivers/gpu/drm/mud/Makefile            |   1 +
 drivers/gpu/drm/mud/mud_drm_gadget.c    | 889 ++++++++++++++++++++++++
 drivers/usb/gadget/Kconfig              |  12 +
 drivers/usb/gadget/function/Makefile    |   2 +
 drivers/usb/gadget/function/f_mud_drm.c | 181 +++++
 6 files changed, 1088 insertions(+)
 create mode 100644 drivers/gpu/drm/mud/mud_drm_gadget.c
 create mode 100644 drivers/usb/gadget/function/f_mud_drm.c

diff --git a/drivers/gpu/drm/mud/Kconfig b/drivers/gpu/drm/mud/Kconfig
index 440e994ca0a2..b3c6d073cc9c 100644
--- a/drivers/gpu/drm/mud/Kconfig
+++ b/drivers/gpu/drm/mud/Kconfig
@@ -13,3 +13,6 @@ config DRM_MUD
        help
         This is a KMS driver for Multifunction USB Device displays or display
         adapters.
+
+config DRM_MUD_GADGET
+       tristate
diff --git a/drivers/gpu/drm/mud/Makefile b/drivers/gpu/drm/mud/Makefile
index d5941d33bcd9..56d2c39ac0eb 100644
--- a/drivers/gpu/drm/mud/Makefile
+++ b/drivers/gpu/drm/mud/Makefile
@@ -1,3 +1,4 @@
 # SPDX-License-Identifier: GPL-2.0-or-later
 
 obj-$(CONFIG_DRM_MUD)                  += mud_drm.o
+obj-$(CONFIG_DRM_MUD_GADGET)           += mud_drm_gadget.o
diff --git a/drivers/gpu/drm/mud/mud_drm_gadget.c 
b/drivers/gpu/drm/mud/mud_drm_gadget.c
new file mode 100644
index 000000000000..9395d8b7cefe
--- /dev/null
+++ b/drivers/gpu/drm/mud/mud_drm_gadget.c
@@ -0,0 +1,889 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2020 Noralf Trønnes
+ */
+
+#include <linux/lz4.h>
+#include <linux/module.h>
+#include <linux/regmap_usb.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#include <drm/drm_client.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_mode_object.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_rect.h>
+
+#include "mud_drm.h"
+
+/* Temporary debugging aid */
+static unsigned int debug = 8;
+
+#define pdebug(level, fmt, ...)                                \
+       if (level <= debug)                             \
+               printk(KERN_DEBUG fmt, ##__VA_ARGS__)
+
+struct mud_drm_gadget_connector {
+       struct drm_connector *connector;
+       u32 capabilities;
+       enum drm_connector_status status;
+       unsigned int width_mm;
+       unsigned int height_mm;
+       void *edid;
+       size_t edid_len;
+       struct mud_drm_display_mode *modes;
+       unsigned int num_modes;
+};
+
+struct mud_drm_gadget {
+       struct drm_client_dev client;
+
+       struct mud_drm_gadget_connector *connectors;
+       unsigned int connector_count;
+
+       const u32 *formats;
+       unsigned int format_count;
+
+       struct drm_connector *current_connector;
+       struct mud_drm_display_mode current_mode;
+       u32 current_format;
+
+       unsigned int rect_x;
+       unsigned int rect_y;
+       unsigned int rect_width;
+       unsigned int rect_height;
+
+       struct drm_client_buffer *buffer;
+       struct drm_client_buffer *buffer_check;
+       bool check_ok;
+
+       size_t max_transfer_size;
+       void *work_buf;
+};
+
+static int mud_drm_gadget_probe_connector(struct mud_drm_gadget_connector 
*mconn)
+{
+       struct drm_connector *connector = mconn->connector;
+       struct drm_device *dev = connector->dev;
+       struct drm_display_mode *mode;
+       unsigned int i = 0;
+       int ret = 0;
+       void *edid;
+
+       pdebug(2, "%s:\n", __func__);
+
+       mutex_lock(&dev->mode_config.mutex);
+
+       connector->funcs->fill_modes(connector,
+                                    dev->mode_config.max_width,
+                                    dev->mode_config.max_height);
+
+       mconn->width_mm = connector->display_info.width_mm;
+       mconn->height_mm = connector->display_info.height_mm;
+       mconn->status = connector->status;
+
+       mconn->num_modes = 0;
+       list_for_each_entry(mode, &connector->modes, head)
+               mconn->num_modes++;
+
+       pdebug(2, "    num_modes=%u\n", mconn->num_modes);
+
+       if (!mconn->num_modes)
+               goto unlock;
+
+       // FIXME: Checkpatch complains: Reusing the krealloc arg is almost 
always a bug
+       mconn->modes = krealloc(mconn->modes, mconn->num_modes * 
sizeof(*mconn->modes), GFP_KERNEL);
+       if (!mconn->modes) {
+               ret = -ENOMEM;
+               mconn->num_modes = 0;
+               goto unlock;
+       }
+
+       list_for_each_entry(mode, &connector->modes, head) {
+               pdebug(2, "    Modeline " DRM_MODE_FMT "\n", 
DRM_MODE_ARG(mode));
+               mud_drm_from_display_mode(&mconn->modes[i++], mode);
+       }
+
+       if (!connector->edid_blob_ptr)
+               goto unlock;
+
+       edid = connector->edid_blob_ptr->data;
+       mconn->edid_len = connector->edid_blob_ptr->length;
+       pdebug(2, "    edid_len=%zu\n", mconn->edid_len);
+       if (!mconn->edid_len || !edid) {
+               mconn->edid_len = 0;
+               goto unlock;
+       }
+
+       kfree(mconn->edid);
+       mconn->edid = kmemdup(edid, mconn->edid_len, GFP_KERNEL);
+       if (!mconn->edid) {
+               ret = -ENOMEM;
+               mconn->edid_len = 0;
+               goto unlock;
+       }
+
+unlock:
+       mutex_unlock(&dev->mode_config.mutex);
+
+       return ret;
+}
+
+static void mud_drm_gadget_probe_connectors(struct mud_drm_gadget *mdg)
+{
+       unsigned int i;
+
+       for (i = 0; i < mdg->connector_count; i++)
+               mud_drm_gadget_probe_connector(&mdg->connectors[i]);
+}
+
+static int mud_drm_gadget_read_connector(struct mud_drm_gadget *mdg, unsigned 
int regnr,
+                                        void *buf, size_t len)
+{
+       struct mud_drm_gadget_connector *mconn;
+       size_t count = len / sizeof(u32);
+       struct drm_connector *connector;
+       unsigned int index, basereg, i;
+       __le32 *buf32 = buf;
+       size_t offset;
+       u32 val;
+
+       if (regnr < MUD_DRM_REG_CONNECTOR_EDID) {
+               index = (regnr - MUD_DRM_REG_CONNECTOR_TYPE) % 
MUD_DRM_MAX_CONNECTORS;
+               basereg = regnr - index;
+       } else if (regnr <= MUD_DRM_REG_CONNECTOR_EDID_MAX) {
+               index = (regnr - MUD_DRM_REG_CONNECTOR_EDID) % 
MUD_DRM_CONNECTOR_EDID_BLOCK_MAX;
+               basereg = MUD_DRM_REG_CONNECTOR_EDID;
+       } else if (regnr <= MUD_DRM_REG_CONNECTOR_MODES_MAX) {
+               index = (regnr - MUD_DRM_REG_CONNECTOR_MODES) % 
MUD_DRM_CONNECTOR_MODES_BLOCK_MAX;
+               basereg = MUD_DRM_REG_CONNECTOR_MODES;
+       } else {
+               return -EINVAL;
+       }
+
+       pdebug(3, "%s: connector index=%u\n", __func__, index);
+
+       if (index >= mdg->connector_count)
+               return -EINVAL;
+
+       mconn = &mdg->connectors[index];
+       connector = mconn->connector;
+
+       switch (basereg) {
+       case MUD_DRM_REG_CONNECTOR_TV_MODE:
+               // drm_atomic_connector_get_property()
+               drm_modeset_lock(&connector->dev->mode_config.connection_mutex, 
NULL);
+               val = connector->state ? connector->state->tv.mode : 0;
+               if (!connector->state)
+                       pr_err("No Connector state!!!\n");
+               pdebug(4, "    MUD_DRM_REG_CONNECTOR_TV_MODE=%u\n", 
connector->state->tv.mode);
+               
drm_modeset_unlock(&connector->dev->mode_config.connection_mutex);
+               break;
+       case MUD_DRM_REG_CONNECTOR_MODES_COUNT:
+               val = mconn->num_modes;
+               break;
+       case MUD_DRM_REG_CONNECTOR_EDID_LEN:
+               val = mconn->edid_len;
+               break;
+       case MUD_DRM_REG_CONNECTOR_STATUS:
+               val = mconn->status;
+               break;
+       case MUD_DRM_REG_CONNECTOR_CAPS:
+               val = mconn->capabilities;
+               break;
+       case MUD_DRM_REG_CONNECTOR_TYPE:
+               val = mconn->connector->connector_type;
+               break;
+
+       case MUD_DRM_REG_CONNECTOR_EDID:
+               offset = regnr - MUD_DRM_REG_CONNECTOR_EDID - (index * 
MUD_DRM_CONNECTOR_EDID_BLOCK_MAX);
+
+               pdebug(4, "    MUD_DRM_REG_CONNECTOR_EDID: offset=%zu\n", 
offset);
+
+               if ((offset + len) > mconn->edid_len)
+                       return -EINVAL;
+               memcpy(buf, mconn->edid + offset, len);
+               return 0;
+
+       case MUD_DRM_REG_CONNECTOR_MODES:
+               offset = regnr - MUD_DRM_REG_CONNECTOR_MODES - (index * 
MUD_DRM_CONNECTOR_MODES_BLOCK_MAX);
+
+               pdebug(4, "    MUD_DRM_REG_CONNECTOR_MODES: offset=%zu, 
count=%zu\n", offset, count);
+
+               if (offset + count > mconn->num_modes * 
MUD_DRM_DISPLAY_MODE_FIELDS)
+                       return -EINVAL;
+
+               for (i = 0; i < count; i++)
+                       buf32[i] = cpu_to_le32(((u32 *)mconn->modes)[i]);
+               return 0;
+
+       default:
+               return -EINVAL;
+       }
+
+       if (count != 1)
+               return -EINVAL;
+
+       *buf32 = cpu_to_le32(val);
+
+       return 0;
+}
+
+static int drm_mud_gadget_tv_mode_property(struct drm_device *drm, u32 
*num_modes,
+                                          const char **names)
+{
+       struct drm_property *property = drm->mode_config.tv_mode_property;
+       struct drm_property_enum *prop_enum;
+       char *buf;
+
+       if (!property)
+               return -EINVAL;
+
+       *num_modes = 0;
+       list_for_each_entry(prop_enum, &property->enum_list, head)
+               (*num_modes)++;
+
+       pdebug(3, "%s: *num_modes=%u\n", __func__, *num_modes);
+
+       if (!names)
+               return 0;
+
+       buf = kcalloc(*num_modes, DRM_PROP_NAME_LEN, GFP_KERNEL);
+       if (!buf)
+               return -ENOMEM;
+
+       *names = buf;
+
+       list_for_each_entry(prop_enum, &property->enum_list, head) {
+               strncpy(buf, prop_enum->name, DRM_PROP_NAME_LEN);
+               buf += 32;
+       }
+
+       return 0;
+}
+
+static bool mud_drm_gadget_check_buffer(struct mud_drm_gadget *mdg,
+                                       struct drm_client_buffer *buffer,
+                                       struct drm_display_mode *mode)
+{
+       struct drm_framebuffer *fb;
+
+       if (!buffer)
+               return false;
+
+       fb = buffer->fb;
+
+       return fb->format->format == mdg->current_format &&
+              fb->width == mode->hdisplay && fb->height == mode->vdisplay;
+}
+
+static int mud_drm_gadget_check(struct mud_drm_gadget *mdg)
+{
+       struct drm_format_name_buf format_name;
+       struct drm_client_buffer *buffer;
+       struct drm_display_mode mode;
+       void *vaddr;
+       int ret;
+
+       memset(&mode, 0, sizeof(mode));
+       mud_drm_to_display_mode(&mode, &mdg->current_mode);
+
+       pdebug(3, "%s:\n", __func__);
+       pdebug(3, "        Format %s\n", 
drm_get_format_name(mdg->current_format, &format_name));
+       pdebug(3, "        Modeline " DRM_MODE_FMT "\n", DRM_MODE_ARG(&mode));
+
+       mdg->check_ok = false;
+
+       if (!mode.hdisplay || !mdg->current_format)
+               return -EINVAL;
+
+       if (mdg->buffer_check) {
+               drm_client_framebuffer_delete(mdg->buffer_check);
+               mdg->buffer_check = NULL;
+       }
+
+       if (!mud_drm_gadget_check_buffer(mdg, mdg->buffer, &mode)) {
+               buffer = drm_client_framebuffer_create(&mdg->client, 
mode.hdisplay, mode.vdisplay,
+                                                      mdg->current_format);
+               if (IS_ERR(buffer))
+                       return PTR_ERR(buffer);
+
+               vaddr = drm_client_buffer_vmap(buffer);
+               if (IS_ERR(vaddr)) {
+                       drm_client_framebuffer_delete(buffer);
+                       return PTR_ERR(vaddr);
+               }
+
+               mdg->buffer_check = buffer;
+       } else {
+               buffer = mdg->buffer;
+       }
+
+       ret = drm_client_modeset_set(&mdg->client, mdg->current_connector, 
&mode, buffer->fb);
+       if (ret)
+               return ret;
+
+       //ret = drm_client_modeset_check(&mdg->client);
+
+       mdg->check_ok = true;
+
+       return 0;
+}
+
+static int mud_drm_gadget_commit(struct mud_drm_gadget *mdg)
+{
+       int ret;
+
+       pdebug(3, "%s: check_ok=%s\n", __func__, mdg->check_ok ? "true" : 
"false");
+
+       if (!mdg->check_ok)
+               return -EINVAL;
+
+       ret = drm_client_modeset_commit(&mdg->client);
+       if (ret)
+               return ret;
+
+       if (mdg->buffer_check) {
+               drm_client_framebuffer_delete(mdg->buffer);
+               mdg->buffer = mdg->buffer_check;
+               mdg->buffer_check = NULL;
+       }
+
+       return 0;
+}
+
+static size_t mud_drm_gadget_write_buffer_rgb565_to_xrgb8888(struct 
drm_client_buffer *buffer,
+                                                            const u16 *src16, 
size_t len,
+                                                            unsigned int x1, 
unsigned int x2,
+                                                            unsigned int y1, 
unsigned int y2)
+{
+       unsigned int dst_width = buffer->fb->width;
+       unsigned int src_width = x2 - x1;
+       unsigned int x, y;
+       u32 *dst32;
+       u16 val16;
+
+       pdebug(4, "%s: %ux%u+%u+%u\n", __func__, x2 - x1, y2 - y1, x1, y1);
+
+       /* Get the address, it's already mapped */
+       dst32 = drm_client_buffer_vmap(buffer);
+
+       for (y = y1; y < y2; y++) {
+               for (x = x1; x < x2; x++) {
+                       val16 = src16[x + (y * src_width)];
+                       dst32[x + (y * dst_width)] = ((val16 & 0xf800) << 8) |
+                                                    ((val16 & 0x07e0) << 5) |
+                                                    ((val16 & 0x001f) << 3);
+                       len -= 2;
+                       if (!len)
+                               return 0;
+               }
+       }
+
+       return len;
+}
+
+static size_t mud_drm_gadget_write_buffer_memcpy(struct drm_client_buffer 
*buffer,
+                                                const void *src, size_t len,
+                                                unsigned int x1, unsigned int 
x2,
+                                                unsigned int y1, unsigned int 
y2)
+{
+       unsigned int cpp = buffer->fb->format->cpp[0];
+       size_t dst_pitch = buffer->fb->pitches[0];
+       size_t src_pitch = (x2 - x1) * cpp;
+       void *dst;
+
+       pdebug(5, "%s: %ux%u+%u+%u\n", __func__, x2 - x1, y2 - y1, x1, y1);
+
+       /* Get the address, it's already mapped */
+       dst = drm_client_buffer_vmap(buffer);
+       dst += y1 * dst_pitch;
+       dst += x1 * cpp;
+
+       for (; y1 < y2 && len; y1++) {
+               src_pitch = min(src_pitch, len);
+               memcpy(dst, src, src_pitch);
+               src += src_pitch;
+               dst += dst_pitch;
+               len -= src_pitch;
+       }
+
+       return len;
+}
+
+static int mud_drm_gadget_write_buffer(struct mud_drm_gadget *mdg, const void 
*buf,
+                                      size_t offset, size_t len, u8 
compression)
+{
+       struct drm_client_buffer *buffer = mdg->buffer ? mdg->buffer : 
mdg->buffer_check;
+       unsigned int x1 = mdg->rect_x;
+       unsigned int x2 = x1 + mdg->rect_width;
+       unsigned int y1 = mdg->rect_y;
+       unsigned int y2 = y1 + mdg->rect_height;
+       struct drm_framebuffer *fb;
+       int ret;
+
+       if (!buffer)
+               return -ENOMEM;
+
+       fb = buffer->fb;
+
+       pdebug(2, "%s: [FB:%d] %ux%u+%u+%u\n", __func__, fb->base.id,
+              mdg->rect_width, mdg->rect_height, mdg->rect_x, mdg->rect_y);
+
+       if (!x2 || !y2 || x2 > fb->width || y2 > fb->height)
+               return -EINVAL;
+
+       if (compression & REGMAP_USB_COMPRESSION_LZ4) {
+               bool direct = !x1 && !y1 && x2 == fb->width && y2 == fb->height 
&&
+                             (x2 * fb->format->cpp[0]) == fb->pitches[0];
+               void *dest = mdg->work_buf;
+
+               /* if src buffer fits dst buffer, decompress directly into dst 
*/
+               if (direct)
+                       dest = drm_client_buffer_vmap(buffer);
+
+               ret = LZ4_decompress_safe(buf, dest, len, 
mdg->max_transfer_size);
+               pdebug(4, "  decompress len=%zu, ret=%d\n", len, ret);
+               if (ret < 0)
+                       return -EIO;
+
+               if (direct)
+                       return 0;
+
+               buf = mdg->work_buf;
+               len = ret;
+       }
+
+       if (offset + len > (mdg->rect_width * mdg->rect_height * 
fb->format->cpp[0])) {
+               pr_err("%s: Buffer doesn't fit rectangle\n", __func__);
+               return -EINVAL;
+       }
+
+       /* offset and len are 32-bit aligned */
+
+       if (offset) {
+               unsigned int buf_cpp = fb->format->cpp[0];
+               unsigned int pix_offset = offset / buf_cpp;
+               unsigned int off_x1 = x1 + (pix_offset % buf_cpp);
+               size_t remain;
+
+               y1 += pix_offset / (x2 - x1);
+               pdebug(6, "    buf_cpp=%u, pix_offset=%u, off_x1=%u, x1=%u, 
y1=%u\n",
+                      buf_cpp, pix_offset, off_x1, x1, y1);
+               if (y1 >= y2)
+                       return -EINVAL;
+
+               if (off_x1 != x1) {
+                       remain = mud_drm_gadget_write_buffer_memcpy(buffer, 
buf, len,
+                                                                   off_x1, x2, 
y1, y1 + 1);
+                       if (!remain)
+                               return 0;
+
+                       buf += len - remain;
+                       len = remain;
+                       if (++y1 >= y2)
+                               return -EINVAL;
+               }
+       }
+
+       len = mud_drm_gadget_write_buffer_memcpy(buffer, buf, len, x1, x2, y1, 
y2);
+       if (len)
+               pr_err("%s: Failed to write it all: %zu\n", __func__, len);
+
+       return len ? -EIO : 0;
+}
+
+static int mud_drm_gadget_disable_pipe(struct mud_drm_gadget *mdg)
+{
+       int ret;
+
+       pdebug(2, "%s: buffer=%px buffer_check=%px\n",
+              __func__, mdg->buffer, mdg->buffer_check);
+
+       drm_client_modeset_set(&mdg->client, NULL, NULL, NULL);
+       ret = drm_client_modeset_commit(&mdg->client);
+       if (ret)
+               return ret;
+
+       drm_client_framebuffer_delete(mdg->buffer_check);
+       drm_client_framebuffer_delete(mdg->buffer);
+       mdg->buffer_check = NULL;
+       mdg->buffer = NULL;
+
+       return 0;
+}
+
+int mud_drm_gadget_writereg(struct mud_drm_gadget *mdg, unsigned int regnr,
+                           const void *buf, size_t len, u8 compression)
+{
+       const __le32 *buf32;
+       size_t count;
+       u32 val;
+       int ret;
+
+       pdebug(2, "%s: regnr=0x%02x, len=%zu\n", __func__, regnr, len);
+
+       if (regnr >= MUD_DRM_REG_BUFFER) {
+               size_t offset = (regnr - MUD_DRM_REG_BUFFER) * sizeof(u32);
+
+               pdebug(3, "    MUD_DRM_REG_BUFFER\n");
+               return mud_drm_gadget_write_buffer(mdg, buf, offset, len, 
compression);
+       }
+
+       if (compression & REGMAP_USB_COMPRESSION_LZ4) {
+               ret = LZ4_decompress_safe(buf, mdg->work_buf, len, 
mdg->max_transfer_size);
+               pdebug(4, "  decompress len=%zu, ret=%d\n", len, ret);
+               if (ret < 0)
+                       return -EIO;
+
+               buf = mdg->work_buf;
+               len = ret;
+       }
+
+       count = len / sizeof(u32);
+       buf32 = buf;
+
+       while (count--) {
+               val = le32_to_cpup(buf32++);
+
+               if (regnr >= MUD_DRM_REG_SET_MODE && regnr <= 
MUD_DRM_REG_SET_MODE_MAX) {
+                       ((u32 *)&mdg->current_mode)[regnr - 
MUD_DRM_REG_SET_MODE] = val;
+               } else {
+                       switch (regnr) {
+                       case MUD_DRM_REG_PIPE_ENABLE:
+                               pdebug(3, "    MUD_DRM_REG_PIPE_ENABLE: %u\n", 
val);
+                               if (val == 0) {
+                                       ret = mud_drm_gadget_disable_pipe(mdg);
+                                       if (ret)
+                                               return ret;
+                               }
+                               break;
+
+                       case MUD_DRM_REG_RECT_X:
+                               mdg->rect_x = val;
+                               break;
+                       case MUD_DRM_REG_RECT_Y:
+                               mdg->rect_y = val;
+                               break;
+                       case MUD_DRM_REG_RECT_WIDTH:
+                               mdg->rect_width = val;
+                               break;
+                       case MUD_DRM_REG_RECT_HEIGHT:
+                               mdg->rect_height = val;
+                               break;
+
+                       case MUD_DRM_REG_SET_CONNECTOR:
+                               if (val >= mdg->connector_count)
+                                       return -EINVAL;
+
+                               pdebug(3, "    MUD_DRM_REG_SET_CONNECTOR: 
%u\n", val);
+                               mdg->current_connector = 
mdg->connectors[val].connector;
+                               break;
+
+                       case MUD_DRM_REG_SET_FORMAT:
+                               pdebug(3, "    MUD_DRM_REG_SET_FORMAT: %u\n", 
val);
+                               mdg->current_format = val;
+                               break;
+
+                       case MUD_DRM_REG_SET_COMMIT:
+                               pdebug(3, "    MUD_DRM_REG_SET_COMMIT: %u\n", 
val);
+
+                               if (val == MUD_DRM_COMMIT_APPLY)
+                                       ret = mud_drm_gadget_commit(mdg);
+                               else
+                                       ret = mud_drm_gadget_check(mdg);
+                               pdebug(3, "        ret=%d\n", ret);
+                               if (ret)
+                                       return ret;
+                               break;
+
+                       default:
+                               pr_err("%s: unknown register: 0x%x\n", 
__func__, regnr);
+                               return -EINVAL;
+                       }
+               }
+
+               regnr++;
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL(mud_drm_gadget_writereg);
+
+int mud_drm_gadget_readreg(struct mud_drm_gadget *mdg, unsigned int regnr,
+                          void *buf, size_t *len, u8 compression)
+{
+       struct drm_device *drm = mdg->client.dev;
+       size_t count = *len / sizeof(u32);
+       __le32 *buf32 = buf;
+       u32 val;
+       int ret;
+
+       /*
+        * FIXME:
+        * Should we bother with compression? The host honours our choice.
+        * EDID and modes are the big ones, only on connector probing.
+        */
+
+       pdebug(2, "%s: regnr=0x%02x, count=%zu\n", __func__, regnr, count);
+
+       if (regnr >= MUD_DRM_REG_CONNECTOR_TYPE && regnr < 
MUD_DRM_REG_CONNECTOR_MAX)
+               return mud_drm_gadget_read_connector(mdg, regnr, buf, *len);
+
+       if (regnr == MUD_DRM_REG_TV_MODES) {
+               const char *names;
+
+               ret = drm_mud_gadget_tv_mode_property(drm, &val, &names);
+               if (ret)
+                       return ret;
+               if (*len != val * DRM_PROP_NAME_LEN)
+                       ret = -EINVAL;
+               else
+                       memcpy(buf, names, *len);
+               kfree(names);
+               return ret;
+       }
+
+       while (count--) {
+               if (regnr >= MUD_DRM_REG_FORMATS && regnr <= 
MUD_DRM_REG_FORMATS_MAX &&
+                   regnr < (MUD_DRM_REG_FORMATS + mdg->format_count)) {
+                       val = mdg->formats[regnr - MUD_DRM_REG_FORMATS];
+               } else {
+                       switch (regnr) {
+                       case MUD_DRM_REG_MIN_WIDTH:
+                               val = drm->mode_config.min_width;
+                               break;
+                       case MUD_DRM_REG_MAX_WIDTH:
+                               val = drm->mode_config.max_width;
+                               break;
+                       case MUD_DRM_REG_MIN_HEIGHT:
+                               val = drm->mode_config.min_height;
+                               break;
+                       case MUD_DRM_REG_MAX_HEIGHT:
+                               val = drm->mode_config.max_height;
+                               break;
+
+                       case MUD_DRM_REG_NUM_CONNECTORS:
+                               val = mdg->connector_count;
+                               break;
+
+                       case MUD_DRM_REG_FORMATS_COUNT:
+                               val = mdg->format_count;
+                               break;
+
+                       case MUD_DRM_REG_TV_MODES_COUNT:
+                               ret = drm_mud_gadget_tv_mode_property(drm, 
&val, NULL);
+                               if (ret)
+                                       return ret;
+                               break;
+                       default:
+                               pr_err("%s: unknown register: 0x%x\n", 
__func__, regnr);
+                               return -EINVAL;
+                       }
+               }
+
+               *(buf32++) = cpu_to_le32(val);
+               regnr++;
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL(mud_drm_gadget_readreg);
+
+void mud_drm_gadget_enable(struct mud_drm_gadget *mdg)
+{
+       pdebug(1, "%s:\n", __func__);
+}
+EXPORT_SYMBOL(mud_drm_gadget_enable);
+
+void mud_drm_gadget_disable(struct mud_drm_gadget *mdg)
+{
+       pdebug(1, "%s:\n", __func__);
+       mud_drm_gadget_disable_pipe(mdg);
+}
+EXPORT_SYMBOL(mud_drm_gadget_disable);
+
+static int drm_mud_gadget_get_formats(struct mud_drm_gadget *mdg)
+{
+       struct drm_device *drm = mdg->client.dev;
+       struct drm_plane *plane;
+       //struct drm_crtc *crtc;
+       unsigned int i;
+       u32 *formats;
+
+       pdebug(1, "%s:\n", __func__);
+       //crtc = drm_crtc_from_index(drm, 0);
+       //plane = crtc->primary;
+
+       drm_for_each_plane(plane, drm) {
+               pdebug(1, "  plane=%px\n", plane);
+               if (plane->type == DRM_PLANE_TYPE_PRIMARY)
+                       break;
+       }
+
+       formats = kcalloc(plane->format_count, sizeof(u32), GFP_KERNEL);
+       if (!formats)
+               return -ENOMEM;
+
+       for (i = 0; i < plane->format_count; i++) {
+               struct drm_format_name_buf format_name;
+               const struct drm_format_info *fmt;
+
+               fmt = drm_format_info(plane->format_types[i]);
+               if (fmt->num_planes != 1)
+                       continue;
+
+               formats[mdg->format_count++] = plane->format_types[i];
+               pdebug(1, "    %s\n", 
drm_get_format_name(plane->format_types[i], &format_name));
+       }
+
+       if (mdg->format_count > (MUD_DRM_REG_FORMATS_MAX - 
MUD_DRM_REG_FORMATS)) {
+               kfree(formats);
+               return -ENOSPC;
+       }
+
+       mdg->formats = formats;
+
+       return 0;
+}
+
+static bool object_has_property(struct drm_mode_object *obj, struct 
drm_property *property)
+{
+       unsigned int i;
+
+       for (i = 0; i < obj->properties->count; i++)
+               if (obj->properties->properties[i] == property)
+                       return true;
+
+       return false;
+}
+
+static void mud_drm_gadget_get_connectors(struct mud_drm_gadget *mdg)
+{
+       struct mud_drm_gadget_connector *connectors = NULL;
+       struct drm_connector_list_iter conn_iter;
+       unsigned int connector_count = 0;
+       struct drm_connector *connector;
+       struct drm_device *drm = mdg->client.dev;
+
+       pdebug(1, "%s:\n", __func__);
+
+       drm_connector_list_iter_begin(drm, &conn_iter);
+       drm_client_for_each_connector_iter(connector, &conn_iter) {
+               struct mud_drm_gadget_connector *tmp, *mconn;
+
+               tmp = krealloc(connectors, (connector_count + 1) * 
sizeof(*connectors),
+                              GFP_KERNEL | __GFP_ZERO);
+               if (!tmp)
+                       break;
+
+               connectors = tmp;
+               drm_connector_get(connector);
+               mconn = &connectors[connector_count++];
+               mconn->connector = connector;
+
+               mconn->capabilities = MUD_DRM_CONNECTOR_CAP_MODE;
+               if (connector->connector_type != DRM_MODE_CONNECTOR_VIRTUAL)
+                       mconn->capabilities |= MUD_DRM_CONNECTOR_CAP_EDID;
+               if (object_has_property(&connector->base, 
drm->mode_config.tv_mode_property))
+                       mconn->capabilities |= MUD_DRM_CONNECTOR_CAP_TV;
+       }
+       drm_connector_list_iter_end(&conn_iter);
+
+       mdg->connectors = connectors;
+       mdg->connector_count = connector_count;
+}
+
+static void mud_drm_gadget_client_unregister(struct drm_client_dev *client)
+{
+       struct mud_drm_gadget *mdg = container_of(client, struct 
mud_drm_gadget, client);
+       unsigned int i;
+
+       pdebug(1, "%s:\n", __func__);
+
+       vfree(mdg->work_buf);
+       kfree(mdg->formats);
+
+       for (i = 0; i < mdg->connector_count; i++) {
+               struct mud_drm_gadget_connector *mconn = &mdg->connectors[i];
+
+               drm_connector_put(mconn->connector);
+               kfree(mconn->modes);
+               kfree(mconn->edid);
+       }
+       kfree(mdg->connectors);
+
+       drm_client_framebuffer_delete(mdg->buffer_check);
+       drm_client_framebuffer_delete(mdg->buffer);
+       drm_client_release(client);
+       kfree(mdg);
+}
+
+static int mud_drm_gadget_client_hotplug(struct drm_client_dev *client)
+{
+       struct mud_drm_gadget *mdg = container_of(client, struct 
mud_drm_gadget, client);
+
+       pdebug(1, "%s:\n", __func__);
+
+       mud_drm_gadget_probe_connectors(mdg);
+
+       return 0;
+}
+
+static const struct drm_client_funcs mdg_client_funcs = {
+       .owner          = THIS_MODULE,
+       .unregister     = mud_drm_gadget_client_unregister,
+       .hotplug        = mud_drm_gadget_client_hotplug,
+};
+
+struct mud_drm_gadget *mud_drm_gadget_init(unsigned int minor_id,
+                                          size_t max_transfer_size)
+{
+       struct mud_drm_gadget *mdg;
+       void *work_buf;
+       int ret;
+
+       pdebug(1, "%s:\n", __func__);
+
+       mdg = kzalloc(sizeof(*mdg), GFP_KERNEL);
+       work_buf = vmalloc(max_transfer_size);
+       if (!mdg || !work_buf) {
+               ret = -ENOMEM;
+               goto error_free;
+       }
+
+       mdg->max_transfer_size = max_transfer_size;
+       mdg->work_buf = work_buf;
+
+       ret = drm_client_init_from_id(minor_id, &mdg->client, "mud-drm-gadget", 
&mdg_client_funcs);
+       if (0 && ret)
+               goto error_free;
+
+       /* From this point on we can't fail since only a drm_dev_unregister() 
can unload us */
+
+       if (!ret) {
+               ret = drm_mud_gadget_get_formats(mdg);
+               if (ret)
+                       pr_err("ERROR: drm_mud_gadget_get_formats=%d\n", ret);
+
+               mud_drm_gadget_get_connectors(mdg);
+               mud_drm_gadget_probe_connectors(mdg);
+       }
+
+       pdebug(1, "%s: connector_count=%u\n", __func__, mdg->connector_count);
+
+       return mdg;
+
+error_free:
+       vfree(work_buf);
+       kfree(mdg);
+
+       return ERR_PTR(ret);
+}
+EXPORT_SYMBOL(mud_drm_gadget_init);
+
+MODULE_AUTHOR("Noralf Trønnes");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
index d6285146ec76..e30cb039f35d 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -219,6 +219,9 @@ config USB_F_TCM
 config USB_F_MUD
        tristate
 
+config USB_F_MUD_DRM
+       tristate
+
 config USB_F_MUD_PINS
        tristate
 
@@ -498,6 +501,15 @@ menuconfig USB_CONFIGFS_F_MUD
 
 if USB_F_MUD
 
+config USB_CONFIGFS_F_MUD_DRM
+       bool "Multifunction USB Device Display"
+       depends on DRM
+       select DRM_MUD_GADGET
+       select USB_F_MUD_DRM
+       select LZ4_DECOMPRESS
+       help
+         Display support for the Multifunction USB Device.
+
 config USB_CONFIGFS_F_MUD_PINS
        bool "Multifunction USB Device GPIO"
        depends on PINCTRL
diff --git a/drivers/usb/gadget/function/Makefile 
b/drivers/usb/gadget/function/Makefile
index 2e24227fcc12..d0f93ce6bbe9 100644
--- a/drivers/usb/gadget/function/Makefile
+++ b/drivers/usb/gadget/function/Makefile
@@ -52,5 +52,7 @@ usb_f_tcm-y                   := f_tcm.o
 obj-$(CONFIG_USB_F_TCM)                += usb_f_tcm.o
 usb_f_mud-y                    := f_mud.o mud_regmap.o
 obj-$(CONFIG_USB_F_MUD)                += usb_f_mud.o
+usb_f_mud_drm-y                        := f_mud_drm.o
+obj-$(CONFIG_USB_F_MUD_DRM)    += usb_f_mud_drm.o
 usb_f_mud_pins-y               := f_mud_pins.o
 obj-$(CONFIG_USB_F_MUD_PINS)   += usb_f_mud_pins.o
diff --git a/drivers/usb/gadget/function/f_mud_drm.c 
b/drivers/usb/gadget/function/f_mud_drm.c
new file mode 100644
index 000000000000..5e7ba71f3389
--- /dev/null
+++ b/drivers/usb/gadget/function/f_mud_drm.c
@@ -0,0 +1,181 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2020 Noralf Trønnes
+ */
+
+#include <linux/configfs.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/device.h>
+#include <linux/regmap_usb.h>
+#include <linux/slab.h>
+
+#include "f_mud.h"
+#include "../../../gpu/drm/mud/mud_drm.h"
+
+struct f_mud_drm_cell {
+       struct f_mud_cell cell;
+
+       struct mutex lock;
+       int refcnt;
+
+       int drm_dev;
+       const char *backlight_dev;
+
+       struct mud_drm_gadget *mdg;
+};
+
+static inline struct f_mud_drm_cell *ci_to_f_mud_drm_cell(struct config_item 
*item)
+{
+       return container_of(to_config_group(item), struct f_mud_drm_cell, 
cell.group);
+}
+
+static inline struct f_mud_drm_cell *cell_to_f_mud_drm_cell(struct f_mud_cell 
*cell)
+{
+       return container_of(cell, struct f_mud_drm_cell, cell);
+}
+
+static void f_mud_drm_enable(struct f_mud_cell *cell)
+{
+       struct f_mud_drm_cell *pcell = cell_to_f_mud_drm_cell(cell);
+
+       mud_drm_gadget_enable(pcell->mdg);
+}
+
+static void f_mud_drm_disable(struct f_mud_cell *cell)
+{
+       struct f_mud_drm_cell *pcell = cell_to_f_mud_drm_cell(cell);
+
+       mud_drm_gadget_disable(pcell->mdg);
+}
+
+static int f_mud_drm_writereg(struct f_mud_cell *cell, unsigned int regnr,
+                             const void *buf, size_t len, u8 compression)
+{
+       struct f_mud_drm_cell *pcell = cell_to_f_mud_drm_cell(cell);
+
+       return mud_drm_gadget_writereg(pcell->mdg, regnr, buf, len, 
compression);
+}
+
+static int f_mud_drm_readreg(struct f_mud_cell *cell, unsigned int regnr,
+                            void *buf, size_t *len, u8 compression)
+{
+       struct f_mud_drm_cell *pcell = cell_to_f_mud_drm_cell(cell);
+
+       return mud_drm_gadget_readreg(pcell->mdg, regnr, buf, len, compression);
+}
+
+static int f_mud_drm_bind(struct f_mud_cell *cell)
+{
+       struct f_mud_drm_cell *pcell = cell_to_f_mud_drm_cell(cell);
+       struct mud_drm_gadget *mdg;
+       int drm_dev, ret = 0;
+
+       printk("%s:\n", __func__);
+
+       mutex_lock(&pcell->lock);
+       drm_dev = pcell->drm_dev;
+       printk("    drm_dev=%d\n", pcell->drm_dev);
+       printk("    backlight_dev='%s'\n", pcell->backlight_dev ? 
pcell->backlight_dev : "");
+
+       mdg = mud_drm_gadget_init(drm_dev, cell->ops->max_transfer_size);
+       if (IS_ERR(mdg)) {
+               ret = PTR_ERR(mdg);
+               goto out;
+       }
+
+       pcell->mdg = mdg;
+       pcell->refcnt++;
+out:
+       mutex_unlock(&pcell->lock);
+
+       return ret;
+}
+
+static void f_mud_drm_unbind(struct f_mud_cell *cell)
+{
+       struct f_mud_drm_cell *pcell = cell_to_f_mud_drm_cell(cell);
+
+       printk("%s:\n", __func__);
+
+       mutex_lock(&pcell->lock);
+       pcell->refcnt--;
+       mutex_unlock(&pcell->lock);
+}
+
+F_MUD_OPT_INT(f_mud_drm_cell, drm_dev, 0, 63);
+F_MUD_OPT_STR(f_mud_drm_cell, backlight_dev);
+
+static struct configfs_attribute *f_mud_drm_attrs[] = {
+       &f_mud_drm_cell_attr_drm_dev,
+       &f_mud_drm_cell_attr_backlight_dev,
+       NULL,
+};
+
+static struct configfs_item_operations f_mud_drm_item_ops = {
+       .release = f_mud_cell_item_release,
+};
+
+static const struct config_item_type f_mud_drm_func_type = {
+       .ct_item_ops    = &f_mud_drm_item_ops,
+       .ct_attrs       = f_mud_drm_attrs,
+       .ct_owner       = THIS_MODULE,
+};
+
+static void f_mud_drm_free(struct f_mud_cell *cell)
+{
+       struct f_mud_drm_cell *pcell = container_of(cell, struct 
f_mud_drm_cell, cell);
+
+       printk("%s:\n", __func__);
+
+       mutex_destroy(&pcell->lock);
+       kfree(pcell->backlight_dev);
+       kfree(pcell);
+}
+
+static struct f_mud_cell *f_mud_drm_alloc(void)
+{
+       struct f_mud_drm_cell *pcell;
+
+       printk("%s:\n", __func__);
+
+       pcell = kzalloc(sizeof(*pcell), GFP_KERNEL);
+       if (!pcell)
+               return ERR_PTR(-ENOMEM);
+
+       mutex_init(&pcell->lock);
+       config_group_init_type_name(&pcell->cell.group, "", 
&f_mud_drm_func_type);
+
+       return &pcell->cell;
+}
+
+static const struct f_mud_cell_ops f_mud_drm_ops = {
+       .name = "mud-drm",
+       .owner = THIS_MODULE,
+
+       /*
+        * FIXME: Support interrupt for connector hotplug event
+        * Polling should be a fallback.
+       .interrupt_interval_ms = 1000,
+        */
+
+       .alloc = f_mud_drm_alloc,
+       .free = f_mud_drm_free,
+       .bind = f_mud_drm_bind,
+       .unbind = f_mud_drm_unbind,
+
+       .regval_bytes = 4,
+       .max_transfer_size = KMALLOC_MAX_SIZE,
+       .compression = REGMAP_USB_COMPRESSION_LZ4,
+
+       .enable = f_mud_drm_enable,
+       .disable = f_mud_drm_disable,
+       .readreg = f_mud_drm_readreg,
+       .writereg = f_mud_drm_writereg,
+};
+
+DECLARE_F_MUD_CELL_INIT(f_mud_drm_ops);
+
+MODULE_AUTHOR("Noralf Trønnes");
+MODULE_LICENSE("GPL");
-- 
2.23.0

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

Reply via email to