[PATCH wireless-next 26/35] wifi: mm81x: add usb.c
From: Lachlan Hodges
Date: Thu Feb 26 2026 - 23:19:19 EST
(Patches split per file for review, see cover letter for more
information)
Signed-off-by: Lachlan Hodges <lachlan.hodges@xxxxxxxxxxxxxx>
---
drivers/net/wireless/morsemicro/mm81x/usb.c | 971 ++++++++++++++++++++
1 file changed, 971 insertions(+)
create mode 100644 drivers/net/wireless/morsemicro/mm81x/usb.c
diff --git a/drivers/net/wireless/morsemicro/mm81x/usb.c b/drivers/net/wireless/morsemicro/mm81x/usb.c
new file mode 100644
index 000000000000..1a08a2eceadf
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/usb.c
@@ -0,0 +1,971 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2017-2026 Morse Micro
+ */
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include "hif.h"
+#include "bus.h"
+#include "debug.h"
+#include "mac.h"
+#include "core.h"
+
+/*
+ * URB timeout in milliseconds. If an URB does not complete within this
+ * time, it will be killed. This timeout needs to account for USB suspendand
+ * resume occurring before the URB can be transferred, and it also needs to
+ * account for transferring USB_MAX_TRANSFER_SIZE bytes over a potentially
+ * slow, congested USB Full Speed link.
+ */
+#define URB_TIMEOUT_MS 250
+
+/* High speed USB 2^(4-1) * 125usec = 1msec */
+#define MM81X_USB_INTERRUPT_INTERVAL 4
+
+/* Max bytes per USB read/write */
+#define USB_MAX_TRANSFER_SIZE (16 * 1024)
+
+/* INT EP buffer size */
+#define MM81X_EP_INT_BUFFER_SIZE 8
+
+/* Morse vendor IDs*/
+#define MM81X_VENDOR_ID 0x325b
+#define MM81X_MM810X_PRODUCT_ID 0x8100
+
+/* Power management runtime auto-suspend delay value in milliseconds */
+#define PM_RUNTIME_AUTOSUSPEND_DELAY_MS 100
+
+enum mm81x_usb_endpoints {
+ MM81X_EP_CMD = 0,
+ MM81X_EP_INT,
+ MM81X_EP_MEM_RD,
+ MM81X_EP_MEM_WR,
+ MM81X_EP_REG_RD,
+ MM81X_EP_REG_WR,
+ MM81X_EP_EP_MAX,
+};
+
+struct mm81x_usb_endpoint {
+ unsigned char *buffer;
+ struct urb *urb;
+ __u8 addr;
+ int size;
+};
+
+enum mm81x_usb_flags { MM81X_USB_FLAG_ATTACHED, MM81X_USB_FLAG_SUSPENDED };
+
+struct mm81x_usb {
+ struct usb_device *udev;
+ struct usb_interface *interface;
+ struct mm81x_usb_endpoint endpoints[MM81X_EP_EP_MAX];
+ int errors;
+
+ /* serialise USB device struct */
+ struct mutex lock;
+
+ /* serialise USB bus access */
+ struct mutex bus_lock;
+
+ bool ongoing_cmd;
+ bool ongoing_rw;
+ wait_queue_head_t rw_in_wait;
+ unsigned long flags;
+};
+
+enum mm81x_usb_command_direction {
+ MM81X_USB_WRITE = 0x00,
+ MM81X_USB_READ = 0x80,
+ MM81X_USB_RESET = 0x02,
+};
+
+struct mm81x_usb_command {
+ __le32 dir; /* Next BULK direction */
+ __le32 address; /* Next BULK address */
+ __le32 length; /* Next BULK size */
+};
+
+static const struct usb_device_id mm81x_usb_table[] = {
+ { USB_DEVICE(MM81X_VENDOR_ID, MM81X_MM810X_PRODUCT_ID) },
+ {} /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, mm81x_usb_table);
+
+static void mm81x_usb_irq_work(struct work_struct *work)
+{
+ struct mm81x *mm = container_of(work, struct mm81x, usb_irq_work);
+
+ mm81x_claim_bus(mm);
+ mm81x_hw_irq_handle(mm);
+ mm81x_release_bus(mm);
+}
+
+/*
+ * See https://www.kernel.org/doc/html/v5.15/driver-api/usb/error-codes.html
+ * Error codes returned by in urb->status which indicate disconnect.
+ */
+static bool mm81x_usb_urb_status_is_disconnect(const struct urb *urb)
+{
+ return ((urb->status == -EPROTO) || (urb->status == -EILSEQ) ||
+ (urb->status == -ETIME) || (urb->status == -EPIPE));
+}
+
+static void mm81x_usb_int_handler(struct urb *urb)
+{
+ int ret;
+ struct mm81x *mm = urb->context;
+ struct mm81x_usb *musb = (struct mm81x_usb *)mm->drv_priv;
+
+ if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags))
+ return;
+
+ if (urb->status) {
+ if (mm81x_usb_urb_status_is_disconnect(urb)) {
+ clear_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags);
+ set_bit(MM81X_STATE_CHIP_UNRESPONSIVE,
+ &mm->state_flags);
+ mm81x_dbg(mm, MM81X_DBG_USB,
+ "USB sudden disconnect detected in %s",
+ __func__);
+ return;
+ }
+
+ if (!(urb->status == -ENOENT || urb->status == -ECONNRESET ||
+ urb->status == -ESHUTDOWN))
+ mm81x_err(mm, "- nonzero read status received: %d",
+ urb->status);
+ }
+
+ ret = usb_submit_urb(urb, GFP_ATOMIC);
+
+ /* usb_kill_urb has been called */
+ if (ret == -EPERM)
+ return;
+ else if (ret)
+ mm81x_err(mm, "error: resubmit urb %p err code %d", urb, ret);
+
+ queue_work(mm->chip_wq, &mm->usb_irq_work);
+}
+
+static int mm81x_usb_int_enable(struct mm81x *mm)
+{
+ int ret = 0;
+ struct mm81x_usb *musb = (struct mm81x_usb *)mm->drv_priv;
+ struct urb *urb;
+
+ if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags))
+ return -ENODEV;
+
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ musb->endpoints[MM81X_EP_INT].urb = urb;
+
+ musb->endpoints[MM81X_EP_INT].buffer =
+ usb_alloc_coherent(musb->udev, MM81X_EP_INT_BUFFER_SIZE,
+ GFP_KERNEL, &urb->transfer_dma);
+ if (!musb->endpoints[MM81X_EP_INT].buffer) {
+ mm81x_err(mm, "couldn't allocate transfer_buffer");
+ ret = -ENOMEM;
+ goto error_set_urb_null;
+ }
+
+ usb_fill_int_urb(
+ musb->endpoints[MM81X_EP_INT].urb, musb->udev,
+ usb_rcvintpipe(musb->udev, musb->endpoints[MM81X_EP_INT].addr),
+ musb->endpoints[MM81X_EP_INT].buffer, MM81X_EP_INT_BUFFER_SIZE,
+ mm81x_usb_int_handler, mm, MM81X_USB_INTERRUPT_INTERVAL);
+ urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+ ret = usb_submit_urb(urb, GFP_KERNEL);
+ if (ret) {
+ mm81x_err(mm, "Couldn't submit urb. Error number %d", ret);
+ goto error;
+ }
+
+ return 0;
+
+error:
+ usb_free_coherent(musb->udev, MM81X_EP_INT_BUFFER_SIZE,
+ musb->endpoints[MM81X_EP_INT].buffer,
+ urb->transfer_dma);
+error_set_urb_null:
+ musb->endpoints[MM81X_EP_INT].urb = NULL;
+ usb_free_urb(urb);
+out:
+ return ret;
+}
+
+static void mm81x_usb_int_stop(struct mm81x *mm)
+{
+ struct mm81x_usb *musb = (struct mm81x_usb *)mm->drv_priv;
+
+ usb_kill_urb(musb->endpoints[MM81X_EP_INT].urb);
+ cancel_work_sync(&mm->usb_irq_work);
+}
+
+static void mm81x_usb_cmd_callback(struct urb *urb)
+{
+ struct mm81x *mm = urb->context;
+ struct mm81x_usb *musb = (struct mm81x_usb *)mm->drv_priv;
+
+ /* sync/async unlink faults aren't errors */
+ if (urb->status) {
+ if (!(urb->status == -ENOENT || urb->status == -ECONNRESET ||
+ urb->status == -ESHUTDOWN))
+ mm81x_err(mm, "nonzero write bulk status received: %d",
+ urb->status);
+
+ musb->errors = urb->status;
+ }
+
+ musb->ongoing_cmd = false;
+ wake_up(&musb->rw_in_wait);
+}
+
+static int mm81x_usb_cmd(struct mm81x_usb *musb,
+ const struct mm81x_usb_command *cmd)
+{
+ int retval = 0;
+ struct mm81x *mm = usb_get_intfdata(musb->interface);
+ struct mm81x_usb_endpoint *ep = &musb->endpoints[MM81X_EP_CMD];
+ size_t writesize = sizeof(*cmd);
+
+ if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags))
+ return -ENODEV;
+
+ memcpy(ep->buffer, cmd, writesize);
+
+ usb_fill_bulk_urb(ep->urb, musb->udev,
+ usb_sndbulkpipe(musb->udev, ep->addr), ep->buffer,
+ writesize, mm81x_usb_cmd_callback, mm);
+ ep->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+ musb->ongoing_cmd = true;
+
+ retval = usb_submit_urb(ep->urb, GFP_KERNEL);
+ if (retval) {
+ mm81x_err(mm, "- failed submitting write urb, error %d",
+ retval);
+
+ goto error;
+ }
+
+ retval = wait_event_interruptible_timeout(
+ musb->rw_in_wait, (!musb->ongoing_cmd),
+ msecs_to_jiffies(URB_TIMEOUT_MS));
+ if (retval < 0) {
+ mm81x_err(mm, "error waiting for urb %d", retval);
+ goto error;
+ } else if (retval == 0) {
+ mm81x_err(mm, "timed out waiting for urb");
+ usb_kill_urb(ep->urb);
+ retval = -ETIMEDOUT;
+ goto error;
+ }
+
+ musb->ongoing_cmd = false;
+ return writesize;
+
+error:
+ musb->ongoing_cmd = false;
+ return retval;
+}
+
+/* Non-destructive USB reset */
+int mm81x_usb_ndr_reset(struct mm81x *mm)
+{
+ int ret;
+ struct mm81x_usb *musb = (struct mm81x_usb *)mm->drv_priv;
+ struct mm81x_usb_command cmd;
+
+ mutex_lock(&musb->lock);
+
+ musb->ongoing_rw = true;
+ musb->errors = 0;
+
+ cmd.dir = cpu_to_le32(MM81X_USB_RESET);
+ cmd.address = cpu_to_le32(0);
+ cmd.length = cpu_to_le32(0);
+
+ ret = mm81x_usb_cmd(musb, &cmd);
+ if (ret < 0)
+ mm81x_err(mm, "mm81x_usb_cmd (MM81X_USB_RESET) error %d\n",
+ ret);
+ else
+ ret = 0;
+
+ musb->ongoing_rw = false;
+ mutex_unlock(&musb->lock);
+ return ret;
+}
+
+static void mm81x_usb_mem_rw_callback(struct urb *urb)
+{
+ struct mm81x *mm = urb->context;
+ struct mm81x_usb *musb = (struct mm81x_usb *)mm->drv_priv;
+
+ /* sync/async unlink faults aren't errors */
+ if (urb->status) {
+ if (!(urb->status == -ENOENT || urb->status == -ECONNRESET ||
+ urb->status == -ESHUTDOWN))
+ mm81x_err(mm, "nonzero write bulk status received: %d",
+ urb->status);
+
+ musb->errors = urb->status;
+ }
+
+ musb->ongoing_rw = false;
+ wake_up(&musb->rw_in_wait);
+}
+
+static int mm81x_usb_mem_read(struct mm81x_usb *musb, u32 address, u8 *data,
+ ssize_t size)
+{
+ int ret;
+ struct mm81x_usb_command cmd;
+ struct mm81x *mm = usb_get_intfdata(musb->interface);
+
+ if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags))
+ return -ENODEV;
+
+ mutex_lock(&musb->lock);
+
+ musb->ongoing_rw = true;
+ musb->errors = 0;
+
+ /* Send command ahead to prepare for Tokens */
+ cmd.dir = cpu_to_le32(MM81X_USB_READ);
+ cmd.address = cpu_to_le32(address);
+ cmd.length = cpu_to_le32(size);
+
+ ret = mm81x_usb_cmd(musb, &cmd);
+ if (ret < 0) {
+ mm81x_err(mm, "mm81x_usb_cmd error %d", ret);
+ goto error;
+ }
+
+ /* Let's be fast push the next URB, don't wait until command is done */
+ usb_fill_bulk_urb(
+ musb->endpoints[MM81X_EP_MEM_RD].urb, musb->udev,
+ usb_rcvbulkpipe(musb->udev,
+ musb->endpoints[MM81X_EP_MEM_RD].addr),
+ musb->endpoints[MM81X_EP_MEM_RD].buffer, size,
+ mm81x_usb_mem_rw_callback, mm);
+
+ ret = usb_submit_urb(musb->endpoints[MM81X_EP_MEM_RD].urb, GFP_ATOMIC);
+ if (ret < 0) {
+ mm81x_err(mm, "failed submitting read urb, error %d", ret);
+ ret = (ret == -ENOMEM) ? ret : -EIO;
+ goto error;
+ }
+
+ ret = wait_event_interruptible_timeout(
+ musb->rw_in_wait, (!musb->ongoing_rw),
+ msecs_to_jiffies(URB_TIMEOUT_MS));
+ if (ret < 0) {
+ mm81x_err(mm, "wait_event_interruptible: error %d", ret);
+ goto error;
+ } else if (ret == 0) {
+ /* Timed out. */
+ usb_kill_urb(musb->endpoints[MM81X_EP_MEM_RD].urb);
+ }
+
+ if (musb->errors) {
+ ret = musb->errors;
+ mm81x_err(mm, "mem read error %d", ret);
+ goto error;
+ }
+
+ memcpy(data, musb->endpoints[MM81X_EP_MEM_RD].buffer, size);
+ ret = size;
+
+error:
+ musb->ongoing_rw = false;
+ mutex_unlock(&musb->lock);
+
+ return ret;
+}
+
+static int mm81x_usb_mem_write(struct mm81x_usb *musb, u32 address, u8 *data,
+ ssize_t size)
+{
+ int ret;
+ struct mm81x_usb_command cmd;
+ struct mm81x *mm = usb_get_intfdata(musb->interface);
+
+ if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags))
+ return -ENODEV;
+
+ mutex_lock(&musb->lock);
+
+ musb->ongoing_rw = true;
+ musb->errors = 0;
+
+ /* Send command ahead to prepare for Tokens */
+ cmd.dir = cpu_to_le32(MM81X_USB_WRITE);
+ cmd.address = cpu_to_le32(address);
+ cmd.length = cpu_to_le32(size);
+ ret = mm81x_usb_cmd(musb, &cmd);
+ if (ret < 0) {
+ mm81x_err(mm, "mm81x_usb_mem_read error %d", ret);
+ goto error;
+ }
+
+ memcpy(musb->endpoints[MM81X_EP_MEM_WR].buffer, data, size);
+
+ /* prepare a read */
+ usb_fill_bulk_urb(
+ musb->endpoints[MM81X_EP_MEM_WR].urb, musb->udev,
+ usb_sndbulkpipe(musb->udev,
+ musb->endpoints[MM81X_EP_MEM_WR].addr),
+ musb->endpoints[MM81X_EP_MEM_WR].buffer, size,
+ mm81x_usb_mem_rw_callback, mm);
+
+ ret = usb_submit_urb(musb->endpoints[MM81X_EP_MEM_WR].urb, GFP_ATOMIC);
+ if (ret < 0) {
+ mm81x_err(mm, "- failed submitting write urb, error %d", ret);
+ ret = (ret == -ENOMEM) ? ret : -EIO;
+ goto error;
+ }
+
+ ret = wait_event_interruptible_timeout(
+ musb->rw_in_wait, (!musb->ongoing_rw),
+ msecs_to_jiffies(URB_TIMEOUT_MS));
+ if (ret < 0) {
+ mm81x_err(mm, "error %d", ret);
+ goto error;
+ } else if (ret == 0) {
+ /* Timed out. */
+ usb_kill_urb(musb->endpoints[MM81X_EP_MEM_WR].urb);
+ }
+
+ if (musb->errors) {
+ ret = musb->errors;
+ mm81x_err(mm, "error %d", ret);
+ goto error;
+ }
+
+ ret = size;
+
+error:
+ musb->ongoing_rw = false;
+ mutex_unlock(&musb->lock);
+ return ret;
+}
+
+static int mm81x_usb_dm_read(struct mm81x *mm, u32 address, u8 *data, int len)
+{
+ ssize_t offset = 0;
+ int ret;
+ struct mm81x_usb *musb = (struct mm81x_usb *)mm->drv_priv;
+
+ if (WARN_ON(len < 0))
+ return -EINVAL;
+
+ while (offset < len) {
+ ret = mm81x_usb_mem_read(musb, address + offset,
+ (u8 *)(data + offset),
+ min((ssize_t)(len - offset),
+ (ssize_t)USB_MAX_TRANSFER_SIZE));
+ if (ret < 0) {
+ mm81x_err(mm, "%s failed (errno=%d)", __func__, ret);
+ return ret;
+ }
+
+ offset += ret;
+ }
+
+ return 0;
+}
+
+static int mm81x_usb_dm_write(struct mm81x *mm, u32 address, const u8 *data,
+ int len)
+{
+ ssize_t offset = 0;
+ int ret;
+ struct mm81x_usb *musb = (struct mm81x_usb *)mm->drv_priv;
+
+ if (WARN_ON(len < 0))
+ return -EINVAL;
+
+ while (offset < len) {
+ ret = mm81x_usb_mem_write(musb, address + offset,
+ (u8 *)(data + offset),
+ min((ssize_t)(len - offset),
+ (ssize_t)USB_MAX_TRANSFER_SIZE));
+ if (ret < 0) {
+ mm81x_err(mm, "%s failed (errno=%d)", __func__, ret);
+ return ret;
+ }
+
+ offset += ret;
+ }
+
+ return 0;
+}
+
+static int mm81x_usb_reg32_read(struct mm81x *mm, u32 address, u32 *val)
+{
+ int ret = 0;
+ struct mm81x_usb *musb = (struct mm81x_usb *)mm->drv_priv;
+
+ ret = mm81x_usb_mem_read(musb, address, (u8 *)val, sizeof(*val));
+ if (ret == sizeof(*val)) {
+ *val = le32_to_cpup((__le32 *)val);
+ return 0;
+ }
+
+ mm81x_err(mm, "usb reg32 read failed %d", ret);
+ return ret;
+}
+
+static int mm81x_usb_reg32_write(struct mm81x *mm, u32 address, u32 val)
+{
+ int ret = 0;
+ struct mm81x_usb *musb = (struct mm81x_usb *)mm->drv_priv;
+ __le32 val_le = cpu_to_le32(val);
+
+ ret = mm81x_usb_mem_write(musb, address, (u8 *)&val_le, sizeof(val_le));
+ if (ret == sizeof(val_le))
+ return 0;
+
+ mm81x_err(mm, "usb reg32 write failed %d", ret);
+ return ret;
+}
+
+static void mm81x_usb_bus_enable(struct mm81x *mm, bool enable)
+{
+ struct mm81x_usb *musb = (struct mm81x_usb *)mm->drv_priv;
+
+ if (enable)
+ usb_autopm_get_interface(musb->interface);
+ else
+ usb_autopm_put_interface(musb->interface);
+}
+
+static void mm81x_usb_claim_bus(struct mm81x *mm)
+{
+ struct mm81x_usb *musb = (struct mm81x_usb *)mm->drv_priv;
+
+ mutex_lock(&musb->bus_lock);
+}
+
+static void mm81x_usb_release_bus(struct mm81x *mm)
+{
+ struct mm81x_usb *musb = (struct mm81x_usb *)mm->drv_priv;
+
+ mutex_unlock(&musb->bus_lock);
+}
+
+static void mm81x_usb_set_irq(struct mm81x *mm, bool enable)
+{
+}
+
+static const struct mm81x_bus_ops mm81x_usb_ops = {
+ .dm_read = mm81x_usb_dm_read,
+ .dm_write = mm81x_usb_dm_write,
+ .reg32_read = mm81x_usb_reg32_read,
+ .reg32_write = mm81x_usb_reg32_write,
+ .set_bus_enable = mm81x_usb_bus_enable,
+ .claim = mm81x_usb_claim_bus,
+ .release = mm81x_usb_release_bus,
+ .set_irq = mm81x_usb_set_irq,
+ .bulk_alignment = MM81X_BUS_DEFAULT_BULK_ALIGNMENT,
+};
+
+static int mm81x_usb_detect_endpoints(struct mm81x *mm,
+ const struct usb_interface *intf)
+{
+ int ret;
+ unsigned int i;
+ struct mm81x_usb *musb = (struct mm81x_usb *)mm->drv_priv;
+ struct usb_endpoint_descriptor *ep_desc;
+ struct usb_host_interface *intf_desc = intf->cur_altsetting;
+
+ for (i = 0; i < intf_desc->desc.bNumEndpoints; i++) {
+ ep_desc = &intf_desc->endpoint[i].desc;
+
+ if (usb_endpoint_is_bulk_in(ep_desc)) {
+ if (!musb->endpoints[MM81X_EP_MEM_RD].addr) {
+ musb->endpoints[MM81X_EP_MEM_RD].addr =
+ usb_endpoint_num(ep_desc);
+ musb->endpoints[MM81X_EP_MEM_RD].size =
+ usb_endpoint_maxp(ep_desc);
+ } else if (!musb->endpoints[MM81X_EP_REG_RD].addr) {
+ musb->endpoints[MM81X_EP_REG_RD].addr =
+ usb_endpoint_num(ep_desc);
+ musb->endpoints[MM81X_EP_REG_RD].size =
+ usb_endpoint_maxp(ep_desc);
+ }
+ } else if (usb_endpoint_is_bulk_out(ep_desc)) {
+ if (!musb->endpoints[MM81X_EP_MEM_WR].addr) {
+ musb->endpoints[MM81X_EP_MEM_WR].addr =
+ usb_endpoint_num(ep_desc);
+ musb->endpoints[MM81X_EP_MEM_WR].size =
+ usb_endpoint_maxp(ep_desc);
+ } else if (!musb->endpoints[MM81X_EP_REG_WR].addr) {
+ musb->endpoints[MM81X_EP_REG_WR].addr =
+ usb_endpoint_num(ep_desc);
+ musb->endpoints[MM81X_EP_REG_WR].size =
+ usb_endpoint_maxp(ep_desc);
+ }
+ } else if (usb_endpoint_is_int_in(ep_desc)) {
+ musb->endpoints[MM81X_EP_INT].addr =
+ usb_endpoint_num(ep_desc);
+ musb->endpoints[MM81X_EP_INT].size =
+ usb_endpoint_maxp(ep_desc);
+ }
+ }
+
+ mm81x_dbg(mm, MM81X_DBG_USB,
+ "\tMemory Endpoint IN %s detected: %u size %u",
+ musb->endpoints[MM81X_EP_MEM_RD].addr ? "" : "not",
+ musb->endpoints[MM81X_EP_MEM_RD].addr,
+ musb->endpoints[MM81X_EP_MEM_RD].size);
+ mm81x_dbg(mm, MM81X_DBG_USB,
+ "\tMemory Endpoint OUT %s detected: %u size %u",
+ musb->endpoints[MM81X_EP_MEM_WR].addr ? "" : "not",
+ musb->endpoints[MM81X_EP_MEM_WR].addr,
+ musb->endpoints[MM81X_EP_MEM_WR].size);
+ mm81x_dbg(mm, MM81X_DBG_USB, "\tRegister Endpoint IN %s detected: %u",
+ musb->endpoints[MM81X_EP_REG_RD].addr ? "" : "not",
+ musb->endpoints[MM81X_EP_REG_RD].addr);
+ mm81x_dbg(mm, MM81X_DBG_USB, "\tRegister Endpoint OUT %s detected: %u",
+ musb->endpoints[MM81X_EP_REG_WR].addr ? "" : "not",
+ musb->endpoints[MM81X_EP_REG_WR].addr);
+ mm81x_dbg(mm, MM81X_DBG_USB, "\tStats IN endpoint %s detected: %u",
+ musb->endpoints[MM81X_EP_INT].addr ? "" : "not",
+ musb->endpoints[MM81X_EP_INT].addr);
+
+ /* Verify we have an IN and OUT */
+ if (!(musb->endpoints[MM81X_EP_MEM_RD].addr &&
+ musb->endpoints[MM81X_EP_MEM_WR].addr))
+ return -ENODEV;
+
+ /* Verify the stats MM81X_EP_INT is detected */
+ if (!musb->endpoints[MM81X_EP_INT].addr)
+ return -ENODEV;
+
+ /* Verify minimum interrupt status read */
+ if (musb->endpoints[MM81X_EP_INT].size < 8)
+ return -ENODEV;
+
+ musb->endpoints[MM81X_EP_CMD].urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!musb->endpoints[MM81X_EP_CMD].urb) {
+ ret = -ENOMEM;
+ goto err_ep;
+ }
+
+ musb->endpoints[MM81X_EP_MEM_RD].urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!musb->endpoints[MM81X_EP_MEM_RD].urb) {
+ ret = -ENOMEM;
+ goto err_ep;
+ }
+
+ musb->endpoints[MM81X_EP_MEM_WR].urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!musb->endpoints[MM81X_EP_MEM_WR].urb) {
+ ret = -ENOMEM;
+ goto err_ep;
+ }
+
+ musb->endpoints[MM81X_EP_MEM_RD].buffer =
+ kmalloc(USB_MAX_TRANSFER_SIZE, GFP_KERNEL);
+ if (!musb->endpoints[MM81X_EP_MEM_RD].buffer) {
+ ret = -ENOMEM;
+ goto err_ep;
+ }
+
+ musb->endpoints[MM81X_EP_MEM_WR].buffer =
+ kmalloc(USB_MAX_TRANSFER_SIZE, GFP_KERNEL);
+ if (!musb->endpoints[MM81X_EP_MEM_WR].buffer) {
+ ret = -ENOMEM;
+ goto err_ep;
+ }
+
+ musb->endpoints[MM81X_EP_CMD].buffer = usb_alloc_coherent(
+ musb->udev, sizeof(struct mm81x_usb_command), GFP_KERNEL,
+ &musb->endpoints[MM81X_EP_CMD].urb->transfer_dma);
+
+ if (!musb->endpoints[MM81X_EP_CMD].buffer) {
+ ret = -ENOMEM;
+ goto err_ep;
+ }
+
+ /* Assign command to memory out end point */
+ musb->endpoints[MM81X_EP_CMD].addr =
+ musb->endpoints[MM81X_EP_MEM_WR].addr;
+ musb->endpoints[MM81X_EP_CMD].size =
+ musb->endpoints[MM81X_EP_MEM_WR].size;
+
+ return 0;
+
+err_ep:
+ if (musb->endpoints[MM81X_EP_CMD].urb &&
+ musb->endpoints[MM81X_EP_CMD].buffer)
+ usb_free_coherent(
+ musb->udev, sizeof(struct mm81x_usb_command),
+ musb->endpoints[MM81X_EP_CMD].buffer,
+ musb->endpoints[MM81X_EP_CMD].urb->transfer_dma);
+ usb_free_urb(musb->endpoints[MM81X_EP_MEM_RD].urb);
+ usb_free_urb(musb->endpoints[MM81X_EP_CMD].urb);
+ usb_free_urb(musb->endpoints[MM81X_EP_MEM_WR].urb);
+ kfree(musb->endpoints[MM81X_EP_MEM_RD].buffer);
+ kfree(musb->endpoints[MM81X_EP_MEM_WR].buffer);
+
+ return ret;
+}
+
+static void mm81x_urb_cleanup(struct mm81x *mm)
+{
+ struct mm81x_usb *musb = (struct mm81x_usb *)mm->drv_priv;
+ struct mm81x_usb_endpoint *int_ep = &musb->endpoints[MM81X_EP_INT];
+ struct mm81x_usb_endpoint *rd_ep = &musb->endpoints[MM81X_EP_MEM_RD];
+ struct mm81x_usb_endpoint *wr_ep = &musb->endpoints[MM81X_EP_MEM_WR];
+ struct mm81x_usb_endpoint *cmd_ep = &musb->endpoints[MM81X_EP_CMD];
+
+ usb_kill_urb(rd_ep->urb);
+ usb_kill_urb(wr_ep->urb);
+ usb_kill_urb(cmd_ep->urb);
+
+ if (int_ep->urb)
+ usb_free_coherent(musb->udev, MM81X_EP_INT_BUFFER_SIZE,
+ int_ep->buffer, int_ep->urb->transfer_dma);
+
+ if (cmd_ep->urb)
+ usb_free_coherent(musb->udev, sizeof(struct mm81x_usb_command),
+ cmd_ep->buffer, cmd_ep->urb->transfer_dma);
+
+ kfree(wr_ep->buffer);
+ kfree(rd_ep->buffer);
+
+ usb_free_urb(int_ep->urb);
+ usb_free_urb(wr_ep->urb);
+ usb_free_urb(rd_ep->urb);
+ usb_free_urb(cmd_ep->urb);
+}
+
+static int mm81x_usb_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{
+ int ret;
+ struct mm81x *mm;
+ struct mm81x_usb *musb;
+
+ mm = mm81x_mac_create(sizeof(*musb), &interface->dev);
+ if (!mm) {
+ dev_err(&interface->dev, "mm81x_mac_create failed\n");
+ return -ENOMEM;
+ }
+
+ mm->bus_ops = &mm81x_usb_ops;
+ mm->bus_type = MM81X_BUS_TYPE_USB;
+
+ musb = (struct mm81x_usb *)mm->drv_priv;
+ musb->udev = usb_get_dev(interface_to_usbdev(interface));
+ musb->interface = usb_get_intf(interface);
+
+ mutex_init(&musb->lock);
+ mutex_init(&musb->bus_lock);
+ init_waitqueue_head(&musb->rw_in_wait);
+ usb_set_intfdata(interface, mm);
+
+ ret = mm81x_usb_detect_endpoints(mm, interface);
+ if (ret < 0) {
+ mm81x_err(mm, "mm81x_usb_detect_endpoints failed (%d)\n", ret);
+ goto err_destroy_mac;
+ }
+
+ set_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags);
+
+ ret = mm81x_core_attach_regs(mm);
+ if (ret < 0) {
+ mm81x_err(mm, "mm81x_core_attach_regs failed: %d", ret);
+ goto err_destroy_mac;
+ }
+
+ mm->ps.gpios_supported = false;
+
+ mm81x_dbg(mm, MM81X_DBG_USB, "CHIP ID 0x%08x:0x%04x",
+ MM81X_REG_CHIP_ID(mm), mm->chip_id);
+
+ mm81x_core_init_mac_addr(mm);
+
+ ret = mm81x_core_create(mm);
+ if (ret)
+ goto err_destroy_mac;
+
+ INIT_WORK(&mm->usb_irq_work, mm81x_usb_irq_work);
+ mm81x_usb_int_enable(mm);
+
+ ret = mm81x_mac_register(mm);
+ if (ret) {
+ mm81x_err(mm, "mm81x_mac_register failed: %d", ret);
+ goto err_core_destroy;
+ }
+
+ /* USB requires remote wakeup functionality for suspend */
+ clear_bit(MM81X_USB_FLAG_SUSPENDED, &musb->flags);
+ musb->interface->needs_remote_wakeup = 1;
+ usb_enable_autosuspend(musb->udev);
+ pm_runtime_set_autosuspend_delay(&musb->udev->dev,
+ PM_RUNTIME_AUTOSUSPEND_DELAY_MS);
+
+ usb_autopm_get_interface(interface);
+ return 0;
+
+err_core_destroy:
+ mm81x_usb_int_stop(mm);
+ mm81x_core_destroy(mm);
+err_destroy_mac:
+ mm81x_mac_destroy(mm);
+ return ret;
+}
+
+static void mm81x_usb_disconnect(struct usb_interface *interface)
+{
+ struct mm81x *mm = usb_get_intfdata(interface);
+ struct mm81x_usb *musb = (struct mm81x_usb *)mm->drv_priv;
+ int minor = interface->minor;
+ struct usb_device *udev = interface_to_usbdev(interface);
+
+ if (udev->state == USB_STATE_NOTATTACHED) {
+ clear_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags);
+ set_bit(MM81X_STATE_CHIP_UNRESPONSIVE, &mm->state_flags);
+ mm81x_dbg(mm, MM81X_DBG_USB, "USB suddenly unplugged");
+ }
+
+ usb_disable_autosuspend(usb_get_dev(udev));
+
+ if (test_bit(MM81X_USB_FLAG_SUSPENDED, &musb->flags)) {
+ mm81x_dbg(mm, MM81X_DBG_USB,
+ "USB was suspended: release locks");
+ mm81x_usb_release_bus(mm);
+ mutex_unlock(&musb->lock);
+ }
+
+ clear_bit(MM81X_USB_FLAG_SUSPENDED, &musb->flags);
+
+ mm81x_mac_unregister(mm);
+ mm81x_usb_int_stop(mm);
+ mm81x_core_destroy(mm);
+ mm81x_urb_cleanup(mm);
+ mm81x_mac_destroy(mm);
+
+ usb_autopm_put_interface(interface);
+ usb_set_intfdata(interface, NULL);
+ dev_info(&interface->dev, "USB Morse #%d now disconnected", minor);
+ usb_put_dev(udev);
+}
+
+static int mm81x_usb_suspend(struct usb_interface *intf, pm_message_t message)
+{
+ struct mm81x *mm = usb_get_intfdata(intf);
+ struct mm81x_usb *musb = (struct mm81x_usb *)mm->drv_priv;
+ struct mm81x_usb_endpoint *int_ep = &musb->endpoints[MM81X_EP_INT];
+ struct mm81x_usb_endpoint *rd_ep = &musb->endpoints[MM81X_EP_MEM_RD];
+ struct mm81x_usb_endpoint *wr_ep = &musb->endpoints[MM81X_EP_MEM_WR];
+ struct mm81x_usb_endpoint *cmd_ep = &musb->endpoints[MM81X_EP_CMD];
+
+ if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags))
+ return -ENODEV;
+
+ usb_kill_urb(int_ep->urb);
+ usb_kill_urb(rd_ep->urb);
+ usb_kill_urb(wr_ep->urb);
+ usb_kill_urb(cmd_ep->urb);
+
+ /* Locking the bus. No USB communication after this point */
+ mm81x_usb_claim_bus(mm);
+ mutex_lock(&musb->lock);
+
+ set_bit(MM81X_USB_FLAG_SUSPENDED, &musb->flags);
+ return 0;
+}
+
+static int mm81x_usb_resume(struct usb_interface *intf)
+{
+ struct mm81x *mm = usb_get_intfdata(intf);
+ struct mm81x_usb *musb = (struct mm81x_usb *)mm->drv_priv;
+ int ret;
+ struct mm81x_usb_endpoint *int_ep = &musb->endpoints[MM81X_EP_INT];
+
+ if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags))
+ return -ENODEV;
+
+ ret = usb_submit_urb(int_ep->urb, GFP_KERNEL);
+ if (ret)
+ mm81x_err(mm, "Couldn't submit urb. Error number %d", ret);
+
+ mm81x_usb_release_bus(mm);
+ mutex_unlock(&musb->lock);
+
+ clear_bit(MM81X_USB_FLAG_SUSPENDED, &musb->flags);
+ return 0;
+}
+
+static int mm81x_usb_reset_resume(struct usb_interface *intf)
+{
+ struct mm81x *mm = usb_get_intfdata(intf);
+ struct mm81x_usb *musb = (struct mm81x_usb *)mm->drv_priv;
+ int ret;
+ struct mm81x_usb_endpoint *int_ep = &musb->endpoints[MM81X_EP_INT];
+
+ if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags))
+ return -ENODEV;
+
+ ret = usb_submit_urb(int_ep->urb, GFP_KERNEL);
+ if (ret)
+ mm81x_err(mm, "Couldn't submit urb. Error number %d", ret);
+
+ mm81x_usb_release_bus(mm);
+ mutex_unlock(&musb->lock);
+
+ clear_bit(MM81X_USB_FLAG_SUSPENDED, &musb->flags);
+
+ return 0;
+}
+
+static int mm81x_usb_pre_reset(struct usb_interface *intf)
+{
+ return 0;
+}
+
+static int mm81x_usb_post_reset(struct usb_interface *intf)
+{
+ return 0;
+}
+
+static struct usb_driver mm81x_usb_driver = {
+ .name = "mm81x_usb",
+ .probe = mm81x_usb_probe,
+ .disconnect = mm81x_usb_disconnect,
+ .suspend = mm81x_usb_suspend,
+ .resume = mm81x_usb_resume,
+ .reset_resume = mm81x_usb_reset_resume,
+ .pre_reset = mm81x_usb_pre_reset,
+ .post_reset = mm81x_usb_post_reset,
+ .id_table = mm81x_usb_table,
+ .supports_autosuspend = 1,
+ .soft_unbind = 1,
+};
+
+int __init mm81x_usb_init(void)
+{
+ int ret;
+
+ ret = usb_register(&mm81x_usb_driver);
+ if (ret)
+ pr_err("failed to register mm81x usb driver: %d\n", ret);
+
+ return ret;
+}
+
+void __exit mm81x_usb_exit(void)
+{
+ usb_deregister(&mm81x_usb_driver);
+}
--
2.43.0