// 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 "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 *mors = container_of(work, struct mm81x, usb_irq_work);
mm81x_claim_bus(mors);
mm81x_hw_irq_handle(mors);
mm81x_release_bus(mors);
}
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 *mors = urb->context;
struct mm81x_usb *musb = (struct mm81x_usb *)mors->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,
&mors->state_flags);
dev_dbg(mors->dev,
"USB sudden disconnect detected in %s",
__func__);
return;
}
if (!(urb->status == -ENOENT || urb->status == -ECONNRESET ||
urb->status == -ESHUTDOWN))
dev_err(mors->dev, "- 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)
dev_err(mors->dev, "error: resubmit urb %p err code %d", urb,
ret);
queue_work(mors->chip_wq, &mors->usb_irq_work);
}
static int mm81x_usb_int_enable(struct mm81x *mors)
{
int ret = 0;
struct mm81x_usb *musb = (struct mm81x_usb *)mors->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) {
dev_err(mors->dev, "couldn't allocate transfer_buffer");
ret = -ENOMEM;
goto error_set_urb_null;
}