Add support for regmap over USB for use with the Multifunction USB Device.
Two endpoints IN/OUT are used. Up to 255 regmaps are supported on one USB
interface. The register index width is always 32-bit, but the register
value can be 8, 16 or 32 bits wide. LZ4 compression is supported on bulk
transfers.

Signed-off-by: Noralf Trønnes <nor...@tronnes.org>
---
 drivers/base/regmap/Kconfig      |    8 +-
 drivers/base/regmap/Makefile     |    1 +
 drivers/base/regmap/regmap-usb.c | 1026 ++++++++++++++++++++++++++++++
 include/linux/regmap.h           |   23 +
 include/linux/regmap_usb.h       |   97 +++
 5 files changed, 1154 insertions(+), 1 deletion(-)
 create mode 100644 drivers/base/regmap/regmap-usb.c
 create mode 100644 include/linux/regmap_usb.h

diff --git a/drivers/base/regmap/Kconfig b/drivers/base/regmap/Kconfig
index 0fd6f97ee523..6c937c196825 100644
--- a/drivers/base/regmap/Kconfig
+++ b/drivers/base/regmap/Kconfig
@@ -4,7 +4,7 @@
 # subsystems should select the appropriate symbols.
 
 config REGMAP
-       default y if (REGMAP_I2C || REGMAP_SPI || REGMAP_SPMI || REGMAP_W1 || 
REGMAP_AC97 || REGMAP_MMIO || REGMAP_IRQ || REGMAP_SCCB || REGMAP_I3C)
+       default y if (REGMAP_I2C || REGMAP_SPI || REGMAP_SPMI || REGMAP_W1 || 
REGMAP_AC97 || REGMAP_MMIO || REGMAP_IRQ || REGMAP_SCCB || REGMAP_I3C || 
REGMAP_USB)
        select IRQ_DOMAIN if REGMAP_IRQ
        bool
 
@@ -53,3 +53,9 @@ config REGMAP_SCCB
 config REGMAP_I3C
        tristate
        depends on I3C
+
+config REGMAP_USB
+       tristate
+       depends on USB
+       select LZ4_COMPRESS
+       select LZ4_DECOMPRESS
diff --git a/drivers/base/regmap/Makefile b/drivers/base/regmap/Makefile
index ff6c7d8ec1cd..7e6932f100ea 100644
--- a/drivers/base/regmap/Makefile
+++ b/drivers/base/regmap/Makefile
@@ -17,3 +17,4 @@ obj-$(CONFIG_REGMAP_W1) += regmap-w1.o
 obj-$(CONFIG_REGMAP_SOUNDWIRE) += regmap-sdw.o
 obj-$(CONFIG_REGMAP_SCCB) += regmap-sccb.o
 obj-$(CONFIG_REGMAP_I3C) += regmap-i3c.o
+obj-$(CONFIG_REGMAP_USB) += regmap-usb.o
diff --git a/drivers/base/regmap/regmap-usb.c b/drivers/base/regmap/regmap-usb.c
new file mode 100644
index 000000000000..bb4f0df44d1d
--- /dev/null
+++ b/drivers/base/regmap/regmap-usb.c
@@ -0,0 +1,1026 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Register map access API - USB support
+ *
+ * Copyright 2020 Noralf Trønnes
+ */
+
+#include <linux/debugfs.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/list.h>
+#include <linux/lz4.h>
+#include <linux/regmap.h>
+#include <linux/regmap_usb.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/usb.h>
+
+#include "internal.h"
+
+/**
+ * DOC: overview
+ *
+ * This regmap over USB supports multiple regmaps over a single USB interface.
+ * Two endpoints are needed and the first IN and OUT endpoints are used.
+ * A REGMAP_USB_DT_INTERFACE descriptor request is issued to get the number of
+ * regmaps supported on the interface. A REGMAP_USB_DT_MAP descriptor request 
is
+ * issued to get details about a specific regmap. This is done when
+ * devm_regmap_init_usb() is called to get access to a regmap.
+ *
+ * A regmap transfer begins with the host sending OUT a &regmap_usb_header 
which
+ * contains info about the index of the regmap, the register address etc. Next
+ * it does an IN or OUT transfer of the register value(s) depending on if it's 
a
+ * read or write. This transfer can be compressed using lz4 if the device
+ * supports it. Finally a &regmap_usb_status IN request is issued to receive 
the
+ * status of the transfer.
+ *
+ * If a transfer fails with the error code -EPIPE, a reset control request
+ * (REGMAP_USB_REQ_PROTOCOL_RESET) is issued. The device should reset it's 
state
+ * machine and return its previous error code if any. The device can halt its
+ * IN/OUT endpoints to force the host to perform a reset if it fails to
+ * understand a transfer.
+ */
+
+/* Provides exclusive interface access */
+struct regmap_usb_interface {
+       struct usb_interface *interface;
+       struct mutex lock; /* Ensures exclusive interface access */
+       unsigned int refcount;
+       struct list_head link;
+
+       u32 tag;
+};
+
+struct regmap_usb_context;
+
+struct regmap_usb_transfer {
+       struct regmap_usb_context *ctx;
+       struct usb_anchor anchor;
+       struct urb *header_urb;
+       struct urb *buf_out_urb;
+       struct urb *buf_in_urb;
+       void *buf;
+       size_t bufsize;
+       struct urb *status_urb;
+       spinlock_t lock; /* Protect dynamic values */
+       u32 tag;
+       int status;
+
+       u8 compression;
+       void *buf_in_dest;
+       unsigned int length;
+       unsigned int actual_length;
+
+       ktime_t start; /* FIXME: Temporary debug/perf aid */
+};
+
+struct regmap_usb_context {
+       struct usb_device *usb;
+       struct regmap_usb_interface *ruif;
+       u8 ifnum;
+       unsigned int in_pipe;
+       unsigned int out_pipe;
+       u16 index;
+       unsigned int val_bytes;
+       void *lz4_comp_mem;
+       u8 compression;
+       unsigned int max_transfer_size;
+       struct regmap_usb_transfer *transfers[2];
+#ifdef CONFIG_DEBUG_FS
+       u64 stats_length;
+       u64 stats_actual_length;
+       unsigned int num_resets;
+       unsigned int num_errors;
+#endif
+};
+
+/* FIXME: Temporary debugging aid */
+static unsigned int debug = 8;
+
+#define udebug(level, fmt, ...)                                \
+do {                                                   \
+       if ((level) <= debug)                           \
+               pr_debug(fmt, ##__VA_ARGS__);           \
+} while (0)
+
+static LIST_HEAD(regmap_usb_interfaces);
+static DEFINE_MUTEX(regmap_usb_interfaces_lock);
+
+static struct regmap_usb_interface *regmap_usb_interface_get(struct 
usb_interface *interface)
+{
+       struct regmap_usb_interface *ruif, *entry;
+
+       mutex_lock(&regmap_usb_interfaces_lock);
+       list_for_each_entry(entry, &regmap_usb_interfaces, link)
+               if (entry->interface == interface) {
+                       ruif = entry;
+                       ruif->refcount++;
+                       goto out_unlock;
+               }
+
+       ruif = kzalloc(sizeof(*ruif), GFP_KERNEL);
+       if (!ruif) {
+               ruif = ERR_PTR(-ENOMEM);
+               goto out_unlock;
+       }
+
+       mutex_init(&ruif->lock);
+       ruif->interface = interface;
+       ruif->refcount++;
+       list_add(&ruif->link, &regmap_usb_interfaces);
+out_unlock:
+       mutex_unlock(&regmap_usb_interfaces_lock);
+
+       return ruif;
+}
+
+static void regmap_usb_interface_put(struct regmap_usb_interface *ruif)
+{
+       mutex_lock(&regmap_usb_interfaces_lock);
+       if (--ruif->refcount)
+               goto out_unlock;
+
+       list_del(&ruif->link);
+       mutex_destroy(&ruif->lock);
+       kfree(ruif);
+out_unlock:
+       mutex_unlock(&regmap_usb_interfaces_lock);
+}
+
+#ifdef CONFIG_DEBUG_FS
+static void regmap_usb_stats_add_length(struct regmap_usb_context *ctx, 
unsigned int len)
+{
+       ctx->stats_length += len;
+       /* Did it wrap around? */
+       if (ctx->stats_length <= len && ctx->stats_actual_length) {
+               ctx->stats_length = len;
+               ctx->stats_actual_length = 0;
+       }
+}
+
+#define regmap_usb_stats_add(c, v) \
+       (c) += v
+#else
+static void regmap_usb_stats_add_length(struct regmap_usb_context *ctx, 
unsigned int len)
+{
+}
+
+#define regmap_usb_stats_add(c, v)
+#endif
+
+static int regmap_usb_protocol_reset(struct regmap_usb_context *ctx)
+{
+       u8 *prev_errno;
+       int ret;
+
+       regmap_usb_stats_add(ctx->num_resets, 1);
+
+       prev_errno = kmalloc(1, GFP_ATOMIC);
+       if (!prev_errno)
+               return -ENOMEM;
+
+       usb_clear_halt(ctx->usb, ctx->out_pipe);
+       usb_clear_halt(ctx->usb, ctx->in_pipe);
+
+       ret = usb_control_msg(ctx->usb, usb_rcvctrlpipe(ctx->usb, 0),
+                             REGMAP_USB_REQ_PROTOCOL_RESET,
+                             USB_DIR_IN | USB_TYPE_VENDOR | 
USB_RECIP_INTERFACE,
+                             0, ctx->ifnum, prev_errno, 1,
+                             USB_CTRL_SET_TIMEOUT);
+       udebug(0, "%s: ret=%d, prev_errno=%u\n", __func__, ret, *prev_errno);
+       if (ret < 0 || ret != 1) {
+               /* FIXME: Try a USB port reset? */
+               ret = -EPIPE;
+               goto free;
+       }
+
+       ret = *prev_errno;
+free:
+       kfree(prev_errno);
+
+       return ret ? -ret : -EPIPE;
+}
+
+static void regmap_usb_header_urb_completion(struct urb *urb)
+{
+       struct regmap_usb_transfer *transfer = urb->context;
+       unsigned long flags;
+
+       spin_lock_irqsave(&transfer->lock, flags);
+       if (urb->status)
+               transfer->status = urb->status;
+       else if (urb->actual_length != urb->transfer_buffer_length)
+               transfer->status = -EREMOTEIO;
+       transfer->start = ktime_get();
+       spin_unlock_irqrestore(&transfer->lock, flags);
+
+       udebug(4, "%s: transfer: status=%d (%d), tag=%u\n",
+              __func__, urb->status, transfer->status, transfer->tag);
+}
+
+static void regmap_usb_status_urb_completion(struct urb *urb)
+{
+       struct regmap_usb_status *status = urb->transfer_buffer;
+       struct regmap_usb_transfer *transfer = urb->context;
+       unsigned long flags;
+       int stat;
+
+       udebug(4, "%s: urb->status=%d, signature=0x%x, tag=%u (expected %u)\n",
+              __func__, urb->status, le32_to_cpu(status->signature),
+              le16_to_cpu(status->tag), transfer->tag);
+
+       if (urb->status)
+               stat = urb->status;
+       else if (urb->actual_length != urb->transfer_buffer_length)
+               stat = -EREMOTEIO;
+       else if (le32_to_cpu(status->signature) != REGMAP_USB_STATUS_SIGNATURE 
||
+                le16_to_cpu(status->tag) != transfer->tag)
+               stat = -EBADMSG;
+       else
+               stat = -status->status;
+
+       spin_lock_irqsave(&transfer->lock, flags);
+       if (!transfer->status)
+               transfer->status = stat;
+       spin_unlock_irqrestore(&transfer->lock, flags);
+}
+
+static long mud_drm_throughput(ktime_t begin, ktime_t end, size_t len)
+{
+       long throughput;
+
+       throughput = ktime_us_delta(end, begin);
+       throughput = throughput ? (len * 1000) / throughput : 0;
+       throughput = throughput * 1000 / 1024;
+
+       return throughput;
+}
+
+static void regmap_usb_buf_in_urb_completion(struct urb *urb)
+{
+       struct regmap_usb_transfer *transfer = urb->context;
+       unsigned long flags;
+       ktime_t start, end;
+
+       spin_lock_irqsave(&transfer->lock, flags);
+       if (urb->status && !transfer->status)
+               transfer->status = urb->status;
+       transfer->actual_length = urb->actual_length;
+       start = transfer->start;
+       spin_unlock_irqrestore(&transfer->lock, flags);
+
+       end = ktime_get();
+
+       udebug(4, "%s: IN: status=%d, tag=%u, %ld kB/s (%lld ms), len=%u\n",
+              __func__, urb->status, transfer->tag,
+              mud_drm_throughput(start, end, urb->actual_length),
+              ktime_ms_delta(end, start), urb->actual_length);
+}
+
+static void regmap_usb_buf_out_urb_completion(struct urb *urb)
+{
+       struct regmap_usb_transfer *transfer = urb->context;
+       unsigned long flags;
+       ktime_t start, end;
+
+       spin_lock_irqsave(&transfer->lock, flags);
+       if (!transfer->status) {
+               if (urb->status)
+                       transfer->status = urb->status;
+               else if (urb->actual_length != urb->transfer_buffer_length)
+                       transfer->status = -EREMOTEIO;
+       }
+       start = transfer->start;
+       spin_unlock_irqrestore(&transfer->lock, flags);
+
+       end = ktime_get();
+
+       udebug(4, "%s: OUT: status=%d, tag=%u, %ld kB/s (%lld ms), len=%u\n",
+              __func__, transfer->status, transfer->tag,
+              mud_drm_throughput(start, end, urb->transfer_buffer_length),
+              ktime_ms_delta(end, start), urb->transfer_buffer_length);
+}
+
+static struct urb *regmap_usb_alloc_urb(struct usb_device *usb, unsigned int 
pipe,
+                                       size_t size, usb_complete_t complete_fn,
+                                       struct regmap_usb_transfer *transfer)
+{
+       void *buf = NULL;
+       struct urb *urb;
+
+       urb = usb_alloc_urb(0, GFP_KERNEL);
+       if (!urb)
+               return NULL;
+
+       if (size) {
+               buf = usb_alloc_coherent(usb, size, GFP_KERNEL, 
&urb->transfer_dma);
+               if (!buf) {
+                       usb_free_urb(urb);
+                       return NULL;
+               }
+       }
+
+       usb_fill_bulk_urb(urb, usb, pipe, buf, size, complete_fn, transfer);
+       if (size)
+               urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+       return urb;
+}
+
+static void regmap_usb_free_transfer(struct regmap_usb_transfer *transfer)
+{
+       struct urb *urb;
+
+       if (!transfer)
+               return;
+
+       urb = transfer->header_urb;
+       if (urb)
+               usb_free_coherent(urb->dev, urb->transfer_buffer_length,
+                                 urb->transfer_buffer, urb->transfer_dma);
+       usb_free_urb(urb);
+
+       urb = transfer->status_urb;
+       if (urb)
+               usb_free_coherent(urb->dev, urb->transfer_buffer_length,
+                                 urb->transfer_buffer, urb->transfer_dma);
+       usb_free_urb(urb);
+
+       usb_free_urb(transfer->buf_in_urb);
+       usb_free_urb(transfer->buf_out_urb);
+       kfree(transfer->buf);
+       kfree(transfer);
+}
+
+static struct regmap_usb_transfer *regmap_usb_alloc_transfer(struct 
regmap_usb_context *ctx)
+{
+       struct regmap_usb_transfer *transfer;
+
+       transfer = kzalloc(sizeof(*transfer), GFP_KERNEL);
+       if (!transfer)
+               return NULL;
+
+       init_usb_anchor(&transfer->anchor);
+       spin_lock_init(&transfer->lock);
+       transfer->ctx = ctx;
+
+       transfer->header_urb = regmap_usb_alloc_urb(ctx->usb, ctx->out_pipe,
+                                                   sizeof(struct 
regmap_usb_header),
+                                                   
regmap_usb_header_urb_completion,
+                                                   transfer);
+       if (!transfer->header_urb)
+               goto error;
+
+       transfer->status_urb = regmap_usb_alloc_urb(ctx->usb, ctx->in_pipe,
+                                                   sizeof(struct 
regmap_usb_status),
+                                                   
regmap_usb_status_urb_completion,
+                                                   transfer);
+       if (!transfer->status_urb)
+               goto error;
+
+       transfer->buf_in_urb = regmap_usb_alloc_urb(ctx->usb, ctx->in_pipe, 0,
+                                                   
regmap_usb_buf_in_urb_completion,
+                                                   transfer);
+       if (!transfer->buf_in_urb)
+               goto error;
+
+       transfer->buf_out_urb = regmap_usb_alloc_urb(ctx->usb, ctx->out_pipe, 0,
+                                                    
regmap_usb_buf_out_urb_completion,
+                                                    transfer);
+       if (!transfer->buf_out_urb)
+               goto error;
+
+       transfer->bufsize = ctx->max_transfer_size;
+retry:
+       transfer->buf = kmalloc(transfer->bufsize, GFP_KERNEL);
+       if (!transfer->buf) {
+               if (transfer->bufsize < 32) /* Give up */
+                       goto error;
+
+               transfer->bufsize /= 2;
+               goto retry;
+       }
+
+       return transfer;
+
+error:
+       regmap_usb_free_transfer(transfer);
+
+       return NULL;
+}
+
+static void regmap_usb_free_transfers(struct regmap_usb_context *ctx)
+{
+       regmap_usb_free_transfer(ctx->transfers[0]);
+       regmap_usb_free_transfer(ctx->transfers[1]);
+}
+
+static int regmap_usb_alloc_transfers(struct regmap_usb_context *ctx)
+{
+       ctx->transfers[0] = regmap_usb_alloc_transfer(ctx);
+       ctx->transfers[1] = regmap_usb_alloc_transfer(ctx);
+       if (!ctx->transfers[0] || !ctx->transfers[1]) {
+               regmap_usb_free_transfers(ctx);
+               return -ENOMEM;
+       }
+
+       return 0;
+}
+
+static int regmap_usb_submit_urb(struct urb *urb, struct regmap_usb_transfer 
*transfer)
+{
+       int ret;
+
+       usb_anchor_urb(urb, &transfer->anchor);
+       ret = usb_submit_urb(urb, GFP_KERNEL);
+       if (ret)
+               usb_unanchor_urb(urb);
+
+       return ret;
+}
+
+static void regmap_usb_kill_transfers(struct regmap_usb_context *ctx)
+{
+       usb_kill_anchored_urbs(&ctx->transfers[0]->anchor);
+       usb_kill_anchored_urbs(&ctx->transfers[1]->anchor);
+}
+
+static int regmap_usb_submit_transfer(struct regmap_usb_transfer *transfer,
+                                     unsigned int regnr, u32 flags, void *buf, 
size_t len)
+{
+       struct regmap_usb_context *ctx = transfer->ctx;
+       struct regmap_usb_header *header;
+       struct urb *urb;
+       int ret;
+
+       spin_lock_irq(&transfer->lock);
+       transfer->actual_length = 0;
+       transfer->status = 0;
+       transfer->tag = ++ctx->ruif->tag;
+       spin_unlock_irq(&transfer->lock);
+
+       udebug(3, "%s: regnr=0x%x, in=%u flags=0x%x, len=%zu, transfer->buf=%s, 
tag=%u\n",
+              __func__, regnr, !!(flags & REGMAP_USB_HEADER_FLAG_IN), flags, 
len,
+              buf == transfer->buf ? "yes" : "no", ctx->ruif->tag);
+
+       header = transfer->header_urb->transfer_buffer;
+       header->signature = cpu_to_le32(REGMAP_USB_HEADER_SIGNATURE);
+       header->index = cpu_to_le16(ctx->index);
+       header->tag = cpu_to_le16(ctx->ruif->tag);
+       header->flags = cpu_to_le32(flags);
+       header->regnr = cpu_to_le32(regnr);
+       header->length = cpu_to_le32(len);
+
+       ret = regmap_usb_submit_urb(transfer->header_urb, transfer);
+       if (ret)
+               goto error;
+
+       if (flags & REGMAP_USB_HEADER_FLAG_IN)
+               urb = transfer->buf_in_urb;
+       else
+               urb = transfer->buf_out_urb;
+
+       urb->transfer_buffer = buf;
+       urb->transfer_buffer_length = len;
+
+       ret = regmap_usb_submit_urb(urb, transfer);
+       if (ret)
+               goto error;
+
+       ret = regmap_usb_submit_urb(transfer->status_urb, transfer);
+       if (ret)
+               goto error;
+
+       return 0;
+
+error:
+       regmap_usb_kill_transfers(ctx);
+
+       return ret;
+}
+
+static int regmap_usb_wait_anchor(struct regmap_usb_transfer *transfer)
+{
+       int remain;
+
+       remain = usb_wait_anchor_empty_timeout(&transfer->anchor, 5000);
+       if (!remain) {
+               /* Kill pending first */
+               if (transfer == transfer->ctx->transfers[0])
+                       
usb_kill_anchored_urbs(&transfer->ctx->transfers[1]->anchor);
+               else
+                       
usb_kill_anchored_urbs(&transfer->ctx->transfers[0]->anchor);
+               usb_kill_anchored_urbs(&transfer->anchor);
+
+               return -ETIMEDOUT;
+       }
+
+       return 0;
+}
+
+static int regmap_usb_transfer_decompress(struct regmap_usb_transfer *transfer)
+{
+       unsigned int length, actual_length;
+       u8 compression;
+       void *dest;
+       int ret;
+
+       spin_lock_irq(&transfer->lock);
+       length = transfer->buf_in_urb->transfer_buffer_length;
+       actual_length = transfer->actual_length;
+       compression = transfer->compression;
+       transfer->compression = 0;
+       dest = transfer->buf_in_dest;
+       spin_unlock_irq(&transfer->lock);
+
+       udebug(3, "%s: dest=%px length=%u actual_length=%u\n",
+              __func__, dest, length, actual_length);
+
+       if (!actual_length) /* This transfer has not been used */
+               return 0;
+
+       if (!length) /* FIXME: necessary? */
+               return -EINVAL;
+
+       regmap_usb_stats_add(transfer->ctx->stats_actual_length, actual_length);
+
+       if (!compression) {
+               if (actual_length != length)
+                       return -EREMOTEIO;
+
+               return 0;
+       }
+
+       if (actual_length == length) { /* Device did not compress */
+               memcpy(dest, transfer->buf, length);
+               return 0;
+       }
+
+       if (compression & REGMAP_USB_COMPRESSION_LZ4) {
+               ret = LZ4_decompress_safe(transfer->buf, dest,
+                                         actual_length, transfer->bufsize);
+               udebug(3, "    decompress: ret=%d\n", ret);
+       } else {
+               return -EINVAL;
+       }
+
+       if (ret < 0 || ret != length)
+               return -EREMOTEIO;
+
+       return 0;
+}
+
+static int regmap_usb_transfer(struct regmap_usb_context *ctx, bool in,
+                              const void *reg, void *buf, size_t len)
+{
+       struct regmap_usb_transfer *transfer = NULL;
+       unsigned int i, regnr, actual_length;
+       size_t chunk, trlen, complen = 0;
+       size_t orglen = len;
+       ktime_t start, end;
+       void *trbuf;
+       u32 flags;
+       int ret;
+
+       regnr = *(u32 *)reg;
+
+       for (i = 0; i < 2; i++) {
+               struct regmap_usb_transfer *transfer = ctx->transfers[i];
+
+               spin_lock_irq(&transfer->lock);
+               transfer->actual_length = 0;
+               transfer->compression = 0;
+               transfer->status = 0;
+               spin_unlock_irq(&transfer->lock);
+       }
+
+       /* FIXME: This did not work */
+       /* Use 2 transfers to maximize compressed transfers */
+       if (0 && ctx->compression &&
+           ctx->transfers[0]->bufsize == ctx->transfers[1]->bufsize &&
+           len > 128 && len <= ctx->transfers[0]->bufsize)
+               complen = len / 2;
+
+       mutex_lock(&ctx->ruif->lock);
+
+       udebug(2, "\n%s: regnr=0x%x, in=%u len=%zu, buf=%px, is_vmalloc=%u\n",
+              __func__, regnr, in, len, buf, is_vmalloc_addr(buf));
+
+       start = ktime_get();
+
+       i = 0;
+       while (len) {
+               transfer = ctx->transfers[i];
+               i = !i;
+
+               chunk = min(complen ? complen : transfer->bufsize, len);
+               trlen = chunk;
+               flags = 0;
+
+               regmap_usb_stats_add_length(ctx, chunk);
+
+               ret = regmap_usb_wait_anchor(transfer);
+               if (ret) {
+                       udebug(0, "FAIL first wait %d\n", ret);
+                       goto error;
+               }
+
+               spin_lock_irq(&transfer->lock);
+               ret = transfer->status;
+               actual_length = transfer->actual_length;
+               spin_unlock_irq(&transfer->lock);
+               if (ret) {
+                       udebug(0, "FAIL transfer %d\n", ret);
+                       goto error;
+               }
+
+               if (in && ctx->compression && actual_length) {
+                       ret = regmap_usb_transfer_decompress(transfer);
+                       if (ret)
+                               goto error;
+               }
+
+               trbuf = buf;
+
+               if (!in) {
+                       ret = 0;
+                       /* LZ4_minLength = 13, use the next power of two value 
*/
+                       if (ctx->compression & REGMAP_USB_COMPRESSION_LZ4 && 
chunk >= 16) {
+                               ret = LZ4_compress_default(buf, transfer->buf, 
chunk,
+                                                          chunk, 
ctx->lz4_comp_mem);
+                               udebug(3, "    compress[%u](chunk=%zu): 
ret=%d\n", !i, chunk, ret);
+                       }
+                       if (ret > 0) {
+                               flags |= REGMAP_USB_COMPRESSION_LZ4;
+                               trbuf = transfer->buf;
+                               trlen = ret;
+                       } else if (is_vmalloc_addr(buf)) {
+                               memcpy(transfer->buf, buf, chunk);
+                               trbuf = transfer->buf;
+                       }
+                       regmap_usb_stats_add(ctx->stats_actual_length, trlen);
+               } else {
+                       flags |= REGMAP_USB_HEADER_FLAG_IN;
+                       if (ctx->compression & REGMAP_USB_COMPRESSION_LZ4 && 
trlen >= 16) {
+                               flags |= REGMAP_USB_COMPRESSION_LZ4;
+                               trbuf = transfer->buf;
+
+                               spin_lock_irq(&transfer->lock);
+                               transfer->compression = 
REGMAP_USB_COMPRESSION_LZ4;
+                               transfer->buf_in_dest = buf;
+                               spin_unlock_irq(&transfer->lock);
+                       }
+               }
+
+               ret = regmap_usb_submit_transfer(transfer, regnr, flags, trbuf, 
trlen);
+               if (ret) {
+                       udebug(0, "FAIL submit %d\n", ret);
+                       goto error;
+               }
+
+               len -= chunk;
+               buf += chunk;
+               regnr += chunk / ctx->val_bytes;
+       }
+
+       ret = regmap_usb_wait_anchor(transfer);
+       if (ret) {
+               udebug(0, "FAIL second wait%d\n", ret);
+               goto error;
+       }
+
+       for (i = 0; i < 2; i++) {
+               struct regmap_usb_transfer *transfer = ctx->transfers[i];
+
+               spin_lock_irq(&transfer->lock);
+               ret = transfer->status;
+               spin_unlock_irq(&transfer->lock);
+               if (ret) {
+                       udebug(0, "FAIL transfer2[%u] %d\n", i, ret);
+                       goto error;
+               }
+       }
+
+       if (in && ctx->compression) {
+               ret = regmap_usb_transfer_decompress(ctx->transfers[0]);
+               if (ret)
+                       goto error;
+               ret = regmap_usb_transfer_decompress(ctx->transfers[1]);
+       }
+
+error:
+       /*
+        * FIXME: What errors should warrant a reset?
+        *        Verify that the DOC section is correct.
+        */
+       if (ret == -EPIPE || ret == -ETIMEDOUT)
+               ret = regmap_usb_protocol_reset(ctx);
+
+       if (ret)
+               regmap_usb_stats_add(ctx->num_errors, 1);
+
+       if (debug >= 2) {
+               end = ktime_get();
+               pr_debug("%s: ret=%d %ld kB/s (%lld ms)\n", __func__, ret,
+                        mud_drm_throughput(start, end, orglen),
+                        ktime_ms_delta(end, start));
+       }
+
+       mutex_unlock(&ctx->ruif->lock);
+
+       return ret;
+}
+
+static int regmap_usb_gather_write(void *context,
+                                  const void *reg, size_t reg_len,
+                                  const void *val, size_t val_len)
+{
+       return regmap_usb_transfer(context, false, reg, (void *)val, val_len);
+}
+
+static int regmap_usb_write(void *context, const void *data, size_t count)
+{
+       struct regmap_usb_context *ctx = context;
+       size_t val_len = count - sizeof(u32);
+       void *val;
+       int ret;
+
+       /* buffer needs to be properly aligned for DMA use */
+       val = kmemdup(data + sizeof(u32), val_len, GFP_KERNEL);
+       if (!val)
+               return -ENOMEM;
+
+       ret = regmap_usb_gather_write(ctx, data, sizeof(u32), val, val_len);
+       kfree(val);
+
+       return ret;
+}
+
+static int regmap_usb_read(void *context, const void *reg_buf, size_t reg_size,
+                          void *val_buf, size_t val_size)
+{
+       return regmap_usb_transfer(context, true, reg_buf, val_buf, val_size);
+}
+
+static void regmap_usb_free_context(void *context)
+{
+       struct regmap_usb_context *ctx = context;
+
+       udebug(1, "%s:\n", __func__);
+
+       regmap_usb_interface_put(ctx->ruif);
+       regmap_usb_free_transfers(ctx);
+       kfree(ctx->lz4_comp_mem);
+       kfree(ctx);
+}
+
+static const struct regmap_bus regmap_usb = {
+       .write = regmap_usb_write,
+       .gather_write = regmap_usb_gather_write,
+       .read = regmap_usb_read,
+       .free_context = regmap_usb_free_context,
+       /* regmap_usb_transfer() handles reg_format: */
+       .reg_format_endian_default = REGMAP_ENDIAN_NATIVE,
+       .val_format_endian_default = REGMAP_ENDIAN_LITTLE,
+};
+
+#ifdef CONFIG_DEBUG_FS
+static int regmap_usb_debugfs_usbinfo_show(struct seq_file *s, void *ignored)
+{
+       struct regmap_usb_context *ctx = s->private;
+
+       mutex_lock(&ctx->ruif->lock);
+
+       seq_printf(s, "USB interface:     %s\n", 
dev_name(&ctx->ruif->interface->dev));
+       seq_printf(s, "Regmap index:      %u\n", ctx->index);
+       seq_printf(s, "Max transfer size: %u\n", ctx->max_transfer_size);
+       seq_printf(s, "Tag:               %u\n", ctx->ruif->tag);
+       seq_printf(s, "Number of errors:  %u\n", ctx->num_errors);
+       seq_printf(s, "Number of resets:  %u\n", ctx->num_resets);
+
+       seq_puts(s, "Compression:      ");
+       if (ctx->compression & REGMAP_USB_COMPRESSION_LZ4)
+               seq_puts(s, " lz4");
+       seq_puts(s, "\n");
+
+       if (ctx->compression) {
+               u64 remainder;
+               u64 ratio = div64_u64_rem(ctx->stats_length, 
ctx->stats_actual_length,
+                                         &remainder);
+               u64 ratio_frac = div64_u64(remainder * 10, 
ctx->stats_actual_length);
+
+               seq_printf(s, "Compression ratio: %llu.%llu\n", ratio, 
ratio_frac);
+       }
+
+       mutex_unlock(&ctx->ruif->lock);
+
+       return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(regmap_usb_debugfs_usbinfo);
+
+static void regmap_usb_debugfs_init(struct regmap *map)
+{
+       if (!map->debugfs)
+               return;
+
+       debugfs_create_file("usbinfo", 0400, map->debugfs, map->bus_context,
+                           &regmap_usb_debugfs_usbinfo_fops);
+}
+#else
+static void regmap_usb_debugfs_init(struct regmap *map) {}
+#endif
+
+static struct regmap_usb_context *
+regmap_usb_gen_context(struct usb_interface *interface, unsigned int index,
+                      const struct regmap_config *config)
+{
+       struct usb_host_interface *alt = interface->cur_altsetting;
+       struct usb_device *usb = interface_to_usbdev(interface);
+       struct usb_endpoint_descriptor *ep_in, *ep_out;
+       unsigned int num_regmaps, max_transfer_size;
+       struct regmap_usb_map_descriptor map_desc;
+       struct regmap_usb_context *ctx;
+       int ret;
+
+       ret = regmap_usb_get_interface_descriptor(interface, &num_regmaps);
+       if (ret)
+               return ERR_PTR(ret);
+
+       if (!num_regmaps)
+               return ERR_PTR(-EINVAL);
+
+       if (index >= num_regmaps)
+               return ERR_PTR(-ENOENT);
+
+       ret = regmap_usb_get_map_descriptor(interface, index, &map_desc);
+       if (ret)
+               return ERR_PTR(ret);
+
+       if (config->reg_bits != 32 ||
+           config->val_bits != map_desc.bRegisterValueBits)
+               return ERR_PTR(-EINVAL);
+
+       max_transfer_size = 1 << map_desc.bMaxTransferSizeOrder;
+       if (max_transfer_size < (config->val_bits / 8))
+               return ERR_PTR(-EINVAL);
+
+       max_transfer_size = min_t(unsigned long, max_transfer_size, 
KMALLOC_MAX_SIZE);
+
+       ret = usb_find_common_endpoints(alt, &ep_in, &ep_out, NULL, NULL);
+       if (ret)
+               return ERR_PTR(ret);
+
+       ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+       if (!ctx)
+               return ERR_PTR(-ENOMEM);
+
+       ctx->usb = usb;
+       ctx->index = index;
+       ctx->ifnum = alt->desc.bInterfaceNumber;
+       ctx->val_bytes = config->val_bits / 8;
+       ctx->compression = map_desc.bCompression;
+       ctx->max_transfer_size = max_transfer_size;
+       ctx->in_pipe = usb_rcvbulkpipe(usb, usb_endpoint_num(ep_in));
+       ctx->out_pipe = usb_sndbulkpipe(usb, usb_endpoint_num(ep_out));
+
+       if (ctx->compression & REGMAP_USB_COMPRESSION_LZ4) {
+               ctx->lz4_comp_mem = kmalloc(LZ4_MEM_COMPRESS, GFP_KERNEL);
+               if (!ctx->lz4_comp_mem) {
+                       ret = -ENOMEM;
+                       goto err_free;
+               }
+       }
+
+       ret = regmap_usb_alloc_transfers(ctx);
+       if (ret)
+               goto err_free;
+
+       ctx->ruif = regmap_usb_interface_get(interface);
+       if (IS_ERR(ctx->ruif)) {
+               ret = PTR_ERR(ctx->ruif);
+               goto err_free_transfers;
+       }
+
+       return ctx;
+
+err_free_transfers:
+       regmap_usb_free_transfers(ctx);
+err_free:
+       kfree(ctx->lz4_comp_mem);
+       kfree(ctx);
+
+       return ERR_PTR(ret);
+}
+
+struct regmap *__devm_regmap_init_usb(struct device *dev,
+                                     struct usb_interface *interface,
+                                     unsigned int index,
+                                     const struct regmap_config *config,
+                                     struct lock_class_key *lock_key,
+                                     const char *lock_name)
+{
+       struct regmap_usb_context *ctx;
+       struct regmap *map;
+
+       ctx = regmap_usb_gen_context(interface, index, config);
+       if (IS_ERR(ctx))
+               return ERR_CAST(ctx);
+
+       map = __devm_regmap_init(dev, &regmap_usb, ctx, config,
+                                lock_key, lock_name);
+       if (!IS_ERR(map))
+               regmap_usb_debugfs_init(map);
+
+       return map;
+}
+EXPORT_SYMBOL(__devm_regmap_init_usb);
+
+static int regmap_usb_get_descriptor(struct usb_interface *interface, u8 type,
+                                    u8 index, void *desc, size_t size)
+{
+       u8 ifnum = interface->cur_altsetting->desc.bInterfaceNumber;
+       struct usb_device *usb = interface_to_usbdev(interface);
+       u8 *buf;
+       int ret;
+
+       buf = kmalloc(size, GFP_KERNEL);
+       if (!buf)
+               return -ENOMEM;
+
+       ret = usb_control_msg(usb, usb_rcvctrlpipe(usb, 0),
+                             USB_REQ_GET_DESCRIPTOR,
+                             USB_DIR_IN | USB_TYPE_VENDOR | 
USB_RECIP_INTERFACE,
+                             (type << 8) + index, ifnum, buf, size,
+                             USB_CTRL_GET_TIMEOUT);
+       if (ret < 0)
+               goto free;
+
+       if (ret != size || buf[0] != size || buf[1] != type) {
+               ret = -ENODATA;
+               goto free;
+       }
+
+       memcpy(desc, buf, size);
+free:
+       kfree(buf);
+
+       return ret;
+}
+
+/**
+ * regmap_usb_get_interface_descriptor() - Get regmap interface descriptor
+ * @interface: USB interface
+ * @num_regmaps: Returns the number of regmaps supported on this interface
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int regmap_usb_get_interface_descriptor(struct usb_interface *interface,
+                                       unsigned int *num_regmaps)
+{
+       struct regmap_usb_interface_descriptor desc;
+       int ret;
+
+       ret = regmap_usb_get_descriptor(interface, REGMAP_USB_DT_INTERFACE, 0,
+                                       &desc, sizeof(desc));
+       if (ret < 0)
+               return ret;
+
+       *num_regmaps = desc.bNumRegmaps;
+
+       return 0;
+}
+EXPORT_SYMBOL(regmap_usb_get_interface_descriptor);
+
+/**
+ * regmap_usb_get_map_descriptor() - Get regmap descriptor
+ * @interface: USB interface
+ * @index: Index of register
+ * @desc: Returned descriptor (little endian representation)
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int regmap_usb_get_map_descriptor(struct usb_interface *interface,
+                                 unsigned int index,
+                                 struct regmap_usb_map_descriptor *desc)
+{
+       int ret;
+
+       ret = regmap_usb_get_descriptor(interface, REGMAP_USB_DT_MAP, index,
+                                       desc, sizeof(*desc));
+       if (ret < 0)
+               return ret;
+
+       if (desc->name[31] != '\0')
+               return -EINVAL;
+
+       return 0;
+}
+EXPORT_SYMBOL(regmap_usb_get_map_descriptor);
+
+MODULE_LICENSE("GPL");
diff --git a/include/linux/regmap.h b/include/linux/regmap.h
index dfe493ac692d..c25ae1a98538 100644
--- a/include/linux/regmap.h
+++ b/include/linux/regmap.h
@@ -32,6 +32,7 @@ struct regmap_range_cfg;
 struct regmap_field;
 struct snd_ac97;
 struct sdw_slave;
+struct usb_interface;
 
 /* An enum of all the supported cache types */
 enum regcache_type {
@@ -618,6 +619,12 @@ struct regmap *__devm_regmap_init_sdw(struct sdw_slave 
*sdw,
                                 const struct regmap_config *config,
                                 struct lock_class_key *lock_key,
                                 const char *lock_name);
+struct regmap *__devm_regmap_init_usb(struct device *dev,
+                                     struct usb_interface *interface,
+                                     unsigned int index,
+                                     const struct regmap_config *config,
+                                     struct lock_class_key *lock_key,
+                                     const char *lock_name);
 struct regmap *__devm_regmap_init_slimbus(struct slim_device *slimbus,
                                 const struct regmap_config *config,
                                 struct lock_class_key *lock_key,
@@ -971,6 +978,22 @@ bool regmap_ac97_default_volatile(struct device *dev, 
unsigned int reg);
        __regmap_lockdep_wrapper(__devm_regmap_init_sdw, #config,       \
                                sdw, config)
 
+/**
+ * devm_regmap_init_usb() - Initialise managed register map
+ *
+ * @dev: Parent device
+ * @interface: USB interface
+ * @index: Index of register
+ * @config: Configuration for register map
+ *
+ * The return value will be an ERR_PTR() on error or a valid pointer
+ * to a struct regmap. The regmap will be automatically freed by the
+ * device management code.
+ */
+#define devm_regmap_init_usb(dev, interface, index, config)            \
+       __regmap_lockdep_wrapper(__devm_regmap_init_usb, #config,       \
+                               dev, interface, index, config)
+
 /**
  * devm_regmap_init_slimbus() - Initialise managed register map
  *
diff --git a/include/linux/regmap_usb.h b/include/linux/regmap_usb.h
new file mode 100644
index 000000000000..e28d5139a53c
--- /dev/null
+++ b/include/linux/regmap_usb.h
@@ -0,0 +1,97 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Copyright 2020 Noralf Trønnes
+ */
+
+#ifndef __LINUX_REGMAP_USB_H
+#define __LINUX_REGMAP_USB_H
+
+#include <linux/types.h>
+#include <uapi/linux/usb/ch9.h>
+
+struct usb_interface;
+
+#define REGMAP_USB_MAX_MAPS    255
+
+#define REGMAP_USB_DT_INTERFACE                (USB_TYPE_VENDOR | 0x01)
+#define REGMAP_USB_DT_MAP              (USB_TYPE_VENDOR | 0x02)
+
+/**
+ * struct regmap_usb_interface_descriptor - Regmap interface descriptor
+ * @bLength: Size of descriptor in bytes
+ * @bDescriptorType: DescriptorType (REGMAP_USB_DT_INTERFACE)
+ * @wNumRegmaps: Number of regmaps on this interface
+ */
+struct regmap_usb_interface_descriptor {
+       __u8 bLength;
+       __u8 bDescriptorType;
+
+       __u8 bNumRegmaps;
+} __packed;
+
+/**
+ * struct regmap_usb_map_descriptor - Regmap descriptor
+ * @bLength: Size of descriptor in bytes
+ * @bDescriptorType: DescriptorType (REGMAP_USB_DT_MAP)
+ * @name: Register name (NUL terminated)
+ * @bRegisterValueBits: Number of bits in the register value
+ * @bCompression: Supported compression types
+ * @bMaxTransferSizeOrder: Maximum transfer size the device can handle as log2.
+ */
+struct regmap_usb_map_descriptor {
+       __u8 bLength;
+       __u8 bDescriptorType;
+
+       __u8 name[32];
+       __u8 bRegisterValueBits;
+       __u8 bCompression;
+#define REGMAP_USB_COMPRESSION_LZ4     BIT(0)
+       __u8 bMaxTransferSizeOrder;
+} __packed;
+
+#define REGMAP_USB_REQ_PROTOCOL_RESET  0xff    /* Returns previous error code 
as u8 */
+
+/**
+ * struct regmap_usb_header - Transfer header
+ * @signature: Magic value (0x2389abc2)
+ * @index: Index of adressed register
+ * @tag: Sequential transfer number
+ * @flags: Transfer flags
+ * @regnr: Register number
+ * @length: Transfer length
+ */
+struct regmap_usb_header {
+#define REGMAP_USB_HEADER_SIGNATURE    0x2389abc2
+       __le32 signature;
+       __le16 index;
+       __le16 tag;
+       __le32 flags;
+#define REGMAP_USB_HEADER_FLAG_IN      BIT(31)
+/* First 8 bits are the same as the descriptor compression bits */
+#define REGMAP_USB_HEADER_FLAG_COMPRESSION_MASK        0xff
+       __le32 regnr;
+       __le32 length;
+} __packed;
+
+/**
+ * struct regmap_usb_status - Transfer status
+ * @signature: Magic value (0x83e7b803)
+ * @index: Index of adressed register
+ * @tag: Sequential transfer number (the same as the one received in the 
header)
+ * @status: Status value of the transfer (Zero on success or a Linux errno on 
failure)
+ */
+struct regmap_usb_status {
+#define REGMAP_USB_STATUS_SIGNATURE    0x83e7b803
+       __le32 signature;
+       __le16 index;
+       __le16 tag;
+       __u8 status;
+} __packed;
+
+int regmap_usb_get_interface_descriptor(struct usb_interface *interface,
+                                       unsigned int *num_regmaps);
+int regmap_usb_get_map_descriptor(struct usb_interface *interface,
+                                 unsigned int index,
+                                 struct regmap_usb_map_descriptor *desc);
+
+#endif
-- 
2.23.0

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

Reply via email to