linux/drivers/net/wireless/morsemicro/mm81x/usb.c
Lachlan Hodges b1906cea00 wifi: mm81x: add mm81x Wi-Fi HaLow driver
mm81x is the first Wi-Fi HaLow driver to support the Morse Micro mm81x
chip family via USB and SDIO.

S1G support in the kernel is only new, and as a result this driver has
been scoped to be simple and only support station and AP interface.
The Wi-Fi specific features only cover the minimum required for basic
use such as powersave, aggregation, rate control and so on. The driver
will be extended into the future as S1G support for operations such as
ACS, channel switching and so on are added into the wireless stack.

The driver has been build tested on a long list of architectures and
compilers via Intels LKP.

The driver currently supports IEEE80211-2024 US channels only, with
AU 2020 also available. In order for this to be expanded additional
non-trivial kernel work is required which will begin once the
driver is upstream.

The driver has had many authors who are listed below in
alphabetical order:

Co-developed-by: Andrew Pope <andrew.pope@morsemicro.com>
Signed-off-by: Andrew Pope <andrew.pope@morsemicro.com>
Co-developed-by: Arien Judge <arien.judge@morsemicro.com>
Signed-off-by: Arien Judge <arien.judge@morsemicro.com>
Co-developed-by: Ayman Grais <ayman.grais@morsemicro.com>
Signed-off-by: Ayman Grais <ayman.grais@morsemicro.com>
Co-developed-by: Bassem Dawood <bassem@morsemicro.com>
Signed-off-by: Bassem Dawood <bassem@morsemicro.com>
Co-developed-by: Chetan Mistry <chetan.mistry@morsemicro.com>
Signed-off-by: Chetan Mistry <chetan.mistry@morsemicro.com>
Co-developed-by: Dan Callaghan <dan.callaghan@morsemicro.com>
Signed-off-by: Dan Callaghan <dan.callaghan@morsemicro.com>
Co-developed-by: James Herbert <james.herbert@morsemicro.com>
Signed-off-by: James Herbert <james.herbert@morsemicro.com>
Co-developed-by: Sahand Maleki <sahand.maleki@morsemicro.com>
Signed-off-by: Sahand Maleki <sahand.maleki@morsemicro.com>
Co-developed-by: Simon Wadsworth <simon@morsemicro.com>
Signed-off-by: Simon Wadsworth <simon@morsemicro.com>
Signed-off-by: Lachlan Hodges <lachlan.hodges@morsemicro.com>
2026-07-08 12:03:50 +10:00

944 lines
24 KiB
C

// 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;
}
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, mors, MM81X_USB_INTERRUPT_INTERVAL);
urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
ret = usb_submit_urb(urb, GFP_KERNEL);
if (ret) {
dev_err(mors->dev, "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 *mors)
{
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
usb_kill_urb(musb->endpoints[MM81X_EP_INT].urb);
cancel_work_sync(&mors->usb_irq_work);
}
static void mm81x_usb_cmd_callback(struct urb *urb)
{
struct mm81x *mors = urb->context;
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
/* sync/async unlink faults aren't errors */
if (urb->status) {
if (!(urb->status == -ENOENT || urb->status == -ECONNRESET ||
urb->status == -ESHUTDOWN))
dev_err(mors->dev,
"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 *mors = 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, mors);
ep->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
musb->ongoing_cmd = true;
retval = usb_submit_urb(ep->urb, GFP_KERNEL);
if (retval) {
dev_err(mors->dev, "- 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) {
dev_err(mors->dev, "error waiting for urb %d", retval);
goto error;
} else if (retval == 0) {
dev_err(mors->dev, "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;
}
static int mm81x_usb_ndr_reset(struct mm81x *mors)
{
int ret;
struct mm81x_usb *musb = (struct mm81x_usb *)mors->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)
dev_err(mors->dev, "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 *mors = urb->context;
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
/* sync/async unlink faults aren't errors */
if (urb->status) {
if (!(urb->status == -ENOENT || urb->status == -ECONNRESET ||
urb->status == -ESHUTDOWN))
dev_err(mors->dev,
"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 *mors = 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) {
dev_err(mors->dev, "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, mors);
ret = usb_submit_urb(musb->endpoints[MM81X_EP_MEM_RD].urb, GFP_ATOMIC);
if (ret < 0) {
dev_err(mors->dev, "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) {
dev_err(mors->dev, "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;
dev_err(mors->dev, "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 *mors = 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) {
dev_err(mors->dev, "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, mors);
ret = usb_submit_urb(musb->endpoints[MM81X_EP_MEM_WR].urb, GFP_ATOMIC);
if (ret < 0) {
dev_err(mors->dev, "- 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) {
dev_err(mors->dev, "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;
dev_err(mors->dev, "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 *mors, u32 address, u8 *data, int len)
{
ssize_t offset = 0;
int ret;
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
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) {
dev_err(mors->dev, "%s failed (errno=%d)", __func__,
ret);
return ret;
}
offset += ret;
}
return 0;
}
static int mm81x_usb_dm_write(struct mm81x *mors, u32 address, const u8 *data,
int len)
{
ssize_t offset = 0;
int ret;
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
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) {
dev_err(mors->dev, "%s failed (errno=%d)", __func__,
ret);
return ret;
}
offset += ret;
}
return 0;
}
static int mm81x_usb_reg32_read(struct mm81x *mors, u32 address, u32 *val)
{
int ret = 0;
struct mm81x_usb *musb = (struct mm81x_usb *)mors->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;
}
dev_err(mors->dev, "usb reg32 read failed %d", ret);
return ret;
}
static int mm81x_usb_reg32_write(struct mm81x *mors, u32 address, u32 val)
{
int ret = 0;
struct mm81x_usb *musb = (struct mm81x_usb *)mors->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;
dev_err(mors->dev, "usb reg32 write failed %d", ret);
return ret;
}
static void mm81x_usb_bus_enable(struct mm81x *mors, bool enable)
{
struct mm81x_usb *musb = (struct mm81x_usb *)mors->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 *mors)
{
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
mutex_lock(&musb->bus_lock);
}
static void mm81x_usb_release_bus(struct mm81x *mors)
{
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
mutex_unlock(&musb->bus_lock);
}
static void mm81x_usb_set_irq(struct mm81x *mors, 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,
.digital_reset = mm81x_usb_ndr_reset,
.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 *mors,
const struct usb_interface *intf)
{
int ret;
unsigned int i;
struct mm81x_usb *musb = (struct mm81x_usb *)mors->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);
}
}
dev_dbg(mors->dev, "\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);
dev_dbg(mors->dev, "\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);
dev_dbg(mors->dev, "\tRegister Endpoint IN %s detected: %u",
musb->endpoints[MM81X_EP_REG_RD].addr ? "" : "not",
musb->endpoints[MM81X_EP_REG_RD].addr);
dev_dbg(mors->dev, "\tRegister Endpoint OUT %s detected: %u",
musb->endpoints[MM81X_EP_REG_WR].addr ? "" : "not",
musb->endpoints[MM81X_EP_REG_WR].addr);
dev_dbg(mors->dev, "\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 *mors)
{
struct mm81x_usb *musb = (struct mm81x_usb *)mors->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 *mors;
struct mm81x_usb *musb;
mors = mm81x_core_alloc(sizeof(*musb), &interface->dev);
if (!mors)
return -ENOMEM;
mors->bus_ops = &mm81x_usb_ops;
mors->bus_type = MM81X_BUS_TYPE_USB;
musb = (struct mm81x_usb *)mors->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, mors);
ret = mm81x_usb_detect_endpoints(mors, interface);
if (ret < 0)
goto err_core_free;
set_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags);
ret = mm81x_core_init(mors);
if (ret)
goto err_urb_cleanup;
INIT_WORK(&mors->usb_irq_work, mm81x_usb_irq_work);
ret = mm81x_usb_int_enable(mors);
if (ret)
goto err_core_deinit;
ret = mm81x_core_register(mors);
if (ret)
goto err_usb_int_stop;
/* 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_usb_int_stop:
mm81x_usb_int_stop(mors);
err_core_deinit:
mm81x_core_deinit(mors);
err_urb_cleanup:
mm81x_urb_cleanup(mors);
err_core_free:
mm81x_core_free(mors);
usb_put_intf(interface);
usb_put_dev(interface_to_usbdev(interface));
return ret;
}
static void mm81x_usb_disconnect(struct usb_interface *interface)
{
struct mm81x *mors = usb_get_intfdata(interface);
struct mm81x_usb *musb = (struct mm81x_usb *)mors->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, &mors->state_flags);
dev_dbg(mors->dev, "USB suddenly unplugged");
}
usb_disable_autosuspend(udev);
if (test_bit(MM81X_USB_FLAG_SUSPENDED, &musb->flags)) {
dev_dbg(mors->dev, "USB was suspended: release locks");
mm81x_usb_release_bus(mors);
mutex_unlock(&musb->lock);
}
clear_bit(MM81X_USB_FLAG_SUSPENDED, &musb->flags);
mm81x_core_unregister(mors);
mm81x_usb_int_stop(mors);
mm81x_core_deinit(mors);
mm81x_urb_cleanup(mors);
mm81x_core_free(mors);
usb_autopm_put_interface(interface);
usb_set_intfdata(interface, NULL);
dev_info(&interface->dev, "USB Morse #%d now disconnected", minor);
usb_put_intf(interface);
usb_put_dev(udev);
}
static int mm81x_usb_suspend(struct usb_interface *intf, pm_message_t message)
{
struct mm81x *mors = usb_get_intfdata(intf);
struct mm81x_usb *musb = (struct mm81x_usb *)mors->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(mors);
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 *mors = usb_get_intfdata(intf);
struct mm81x_usb *musb = (struct mm81x_usb *)mors->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)
dev_err(mors->dev, "Couldn't submit urb. Error number %d", ret);
mm81x_usb_release_bus(mors);
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 *mors = usb_get_intfdata(intf);
struct mm81x_usb *musb = (struct mm81x_usb *)mors->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)
dev_err(mors->dev, "Couldn't submit urb. Error number %d", ret);
mm81x_usb_release_bus(mors);
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,
};
module_usb_driver(mm81x_usb_driver);
MODULE_AUTHOR("Morse Micro");
MODULE_DESCRIPTION("Driver support for Morse Micro MM81X USB devices");
MODULE_LICENSE("Dual BSD/GPL");