751 lines
16 KiB
C
751 lines
16 KiB
C
|
/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved.
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License version 2 and
|
||
|
* only version 2 as published by the Free Software Foundation.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
* GNU General Public License for more details.
|
||
|
*/
|
||
|
|
||
|
#include <linux/kernel.h>
|
||
|
#include <linux/errno.h>
|
||
|
#include <linux/init.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/kref.h>
|
||
|
#include <linux/debugfs.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <linux/uaccess.h>
|
||
|
#include <linux/ratelimit.h>
|
||
|
#include <linux/usb/ch9.h>
|
||
|
#include <linux/usb/cdc.h>
|
||
|
#include <linux/termios.h>
|
||
|
#include <asm/unaligned.h>
|
||
|
#include <mach/usb_bridge.h>
|
||
|
|
||
|
static const char *ctrl_bridge_names[] = {
|
||
|
"dun_ctrl_hsic0",
|
||
|
"rmnet_ctrl_hsic0"
|
||
|
};
|
||
|
|
||
|
/* polling interval for Interrupt ep */
|
||
|
#define HS_INTERVAL 7
|
||
|
#define FS_LS_INTERVAL 3
|
||
|
|
||
|
#define ACM_CTRL_DTR (1 << 0)
|
||
|
#define DEFAULT_READ_URB_LENGTH 4096
|
||
|
|
||
|
#define SUSPENDED BIT(0)
|
||
|
|
||
|
struct ctrl_bridge {
|
||
|
struct usb_device *udev;
|
||
|
struct usb_interface *intf;
|
||
|
|
||
|
unsigned int int_pipe;
|
||
|
struct urb *inturb;
|
||
|
void *intbuf;
|
||
|
|
||
|
struct urb *readurb;
|
||
|
void *readbuf;
|
||
|
|
||
|
struct usb_anchor tx_submitted;
|
||
|
struct usb_anchor tx_deferred;
|
||
|
struct usb_ctrlrequest *in_ctlreq;
|
||
|
|
||
|
struct bridge *brdg;
|
||
|
struct platform_device *pdev;
|
||
|
|
||
|
unsigned long flags;
|
||
|
|
||
|
/* input control lines (DSR, CTS, CD, RI) */
|
||
|
unsigned int cbits_tohost;
|
||
|
|
||
|
/* output control lines (DTR, RTS) */
|
||
|
unsigned int cbits_tomdm;
|
||
|
|
||
|
/* counters */
|
||
|
unsigned int snd_encap_cmd;
|
||
|
unsigned int get_encap_res;
|
||
|
unsigned int resp_avail;
|
||
|
unsigned int set_ctrl_line_sts;
|
||
|
unsigned int notify_ser_state;
|
||
|
};
|
||
|
|
||
|
static struct ctrl_bridge *__dev[MAX_BRIDGE_DEVICES];
|
||
|
|
||
|
/* counter used for indexing ctrl bridge devices */
|
||
|
static int ch_id;
|
||
|
|
||
|
unsigned int ctrl_bridge_get_cbits_tohost(unsigned int id)
|
||
|
{
|
||
|
struct ctrl_bridge *dev;
|
||
|
|
||
|
if (id >= MAX_BRIDGE_DEVICES)
|
||
|
return -EINVAL;
|
||
|
|
||
|
dev = __dev[id];
|
||
|
if (!dev)
|
||
|
return -ENODEV;
|
||
|
|
||
|
return dev->cbits_tohost;
|
||
|
}
|
||
|
EXPORT_SYMBOL(ctrl_bridge_get_cbits_tohost);
|
||
|
|
||
|
int ctrl_bridge_set_cbits(unsigned int id, unsigned int cbits)
|
||
|
{
|
||
|
struct ctrl_bridge *dev;
|
||
|
struct bridge *brdg;
|
||
|
int retval;
|
||
|
|
||
|
if (id >= MAX_BRIDGE_DEVICES)
|
||
|
return -EINVAL;
|
||
|
|
||
|
dev = __dev[id];
|
||
|
if (!dev)
|
||
|
return -ENODEV;
|
||
|
|
||
|
pr_debug("%s: dev[id] =%u cbits : %u\n", __func__, id, cbits);
|
||
|
|
||
|
brdg = dev->brdg;
|
||
|
if (!brdg)
|
||
|
return -ENODEV;
|
||
|
|
||
|
dev->cbits_tomdm = cbits;
|
||
|
|
||
|
retval = ctrl_bridge_write(id, NULL, 0);
|
||
|
|
||
|
/* if DTR is high, update latest modem info to host */
|
||
|
if (brdg && (cbits & ACM_CTRL_DTR) && brdg->ops.send_cbits)
|
||
|
brdg->ops.send_cbits(brdg->ctx, dev->cbits_tohost);
|
||
|
|
||
|
return retval;
|
||
|
}
|
||
|
EXPORT_SYMBOL(ctrl_bridge_set_cbits);
|
||
|
|
||
|
static void resp_avail_cb(struct urb *urb)
|
||
|
{
|
||
|
struct ctrl_bridge *dev = urb->context;
|
||
|
int status = 0;
|
||
|
int resubmit_urb = 1;
|
||
|
struct bridge *brdg = dev->brdg;
|
||
|
|
||
|
switch (urb->status) {
|
||
|
case 0:
|
||
|
/*success*/
|
||
|
dev->get_encap_res++;
|
||
|
if (brdg && brdg->ops.send_pkt)
|
||
|
brdg->ops.send_pkt(brdg->ctx, urb->transfer_buffer,
|
||
|
urb->actual_length);
|
||
|
break;
|
||
|
|
||
|
/*do not resubmit*/
|
||
|
case -ESHUTDOWN:
|
||
|
case -ENOENT:
|
||
|
case -ECONNRESET:
|
||
|
/* unplug */
|
||
|
case -EPROTO:
|
||
|
/*babble error*/
|
||
|
resubmit_urb = 0;
|
||
|
/*resubmit*/
|
||
|
case -EOVERFLOW:
|
||
|
default:
|
||
|
dev_dbg(&dev->intf->dev, "%s: non zero urb status = %d\n",
|
||
|
__func__, urb->status);
|
||
|
}
|
||
|
|
||
|
if (resubmit_urb) {
|
||
|
/*re- submit int urb to check response available*/
|
||
|
usb_anchor_urb(dev->inturb, &dev->tx_submitted);
|
||
|
status = usb_submit_urb(dev->inturb, GFP_ATOMIC);
|
||
|
if (status) {
|
||
|
dev_err(&dev->intf->dev,
|
||
|
"%s: Error re-submitting Int URB %d\n",
|
||
|
__func__, status);
|
||
|
usb_unanchor_urb(dev->inturb);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void notification_available_cb(struct urb *urb)
|
||
|
{
|
||
|
int status;
|
||
|
struct usb_cdc_notification *ctrl;
|
||
|
struct ctrl_bridge *dev = urb->context;
|
||
|
struct bridge *brdg = dev->brdg;
|
||
|
unsigned int ctrl_bits;
|
||
|
unsigned char *data;
|
||
|
|
||
|
switch (urb->status) {
|
||
|
case 0:
|
||
|
/*success*/
|
||
|
break;
|
||
|
case -ESHUTDOWN:
|
||
|
case -ENOENT:
|
||
|
case -ECONNRESET:
|
||
|
case -EPROTO:
|
||
|
/* unplug */
|
||
|
return;
|
||
|
case -EPIPE:
|
||
|
dev_err(&dev->intf->dev,
|
||
|
"%s: stall on int endpoint\n", __func__);
|
||
|
/* TBD : halt to be cleared in work */
|
||
|
case -EOVERFLOW:
|
||
|
default:
|
||
|
pr_debug_ratelimited("%s: non zero urb status = %d\n",
|
||
|
__func__, urb->status);
|
||
|
goto resubmit_int_urb;
|
||
|
}
|
||
|
|
||
|
ctrl = (struct usb_cdc_notification *)urb->transfer_buffer;
|
||
|
data = (unsigned char *)(ctrl + 1);
|
||
|
|
||
|
switch (ctrl->bNotificationType) {
|
||
|
case USB_CDC_NOTIFY_RESPONSE_AVAILABLE:
|
||
|
dev->resp_avail++;
|
||
|
usb_fill_control_urb(dev->readurb, dev->udev,
|
||
|
usb_rcvctrlpipe(dev->udev, 0),
|
||
|
(unsigned char *)dev->in_ctlreq,
|
||
|
dev->readbuf,
|
||
|
DEFAULT_READ_URB_LENGTH,
|
||
|
resp_avail_cb, dev);
|
||
|
|
||
|
usb_anchor_urb(dev->readurb, &dev->tx_submitted);
|
||
|
status = usb_submit_urb(dev->readurb, GFP_ATOMIC);
|
||
|
if (status) {
|
||
|
dev_err(&dev->intf->dev,
|
||
|
"%s: Error submitting Read URB %d\n",
|
||
|
__func__, status);
|
||
|
usb_unanchor_urb(dev->readurb);
|
||
|
goto resubmit_int_urb;
|
||
|
}
|
||
|
return;
|
||
|
case USB_CDC_NOTIFY_NETWORK_CONNECTION:
|
||
|
dev_dbg(&dev->intf->dev, "%s network\n", ctrl->wValue ?
|
||
|
"connected to" : "disconnected from");
|
||
|
break;
|
||
|
case USB_CDC_NOTIFY_SERIAL_STATE:
|
||
|
dev->notify_ser_state++;
|
||
|
ctrl_bits = get_unaligned_le16(data);
|
||
|
dev_dbg(&dev->intf->dev, "serial state: %d\n", ctrl_bits);
|
||
|
dev->cbits_tohost = ctrl_bits;
|
||
|
if (brdg && brdg->ops.send_cbits)
|
||
|
brdg->ops.send_cbits(brdg->ctx, ctrl_bits);
|
||
|
break;
|
||
|
default:
|
||
|
dev_err(&dev->intf->dev, "%s: unknown notification %d received:"
|
||
|
"index %d len %d data0 %d data1 %d",
|
||
|
__func__, ctrl->bNotificationType, ctrl->wIndex,
|
||
|
ctrl->wLength, data[0], data[1]);
|
||
|
}
|
||
|
|
||
|
resubmit_int_urb:
|
||
|
usb_anchor_urb(urb, &dev->tx_submitted);
|
||
|
status = usb_submit_urb(urb, GFP_ATOMIC);
|
||
|
if (status) {
|
||
|
dev_err(&dev->intf->dev, "%s: Error re-submitting Int URB %d\n",
|
||
|
__func__, status);
|
||
|
usb_unanchor_urb(urb);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int ctrl_bridge_start_read(struct ctrl_bridge *dev)
|
||
|
{
|
||
|
int retval = 0;
|
||
|
|
||
|
if (!dev->inturb) {
|
||
|
dev_err(&dev->intf->dev, "%s: inturb is NULL\n", __func__);
|
||
|
return -ENODEV;
|
||
|
}
|
||
|
|
||
|
if (!dev->inturb->anchor) {
|
||
|
usb_anchor_urb(dev->inturb, &dev->tx_submitted);
|
||
|
retval = usb_submit_urb(dev->inturb, GFP_KERNEL);
|
||
|
if (retval < 0) {
|
||
|
dev_err(&dev->intf->dev,
|
||
|
"%s error submitting int urb %d\n",
|
||
|
__func__, retval);
|
||
|
usb_unanchor_urb(dev->inturb);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
int ctrl_bridge_open(struct bridge *brdg)
|
||
|
{
|
||
|
struct ctrl_bridge *dev;
|
||
|
|
||
|
if (!brdg) {
|
||
|
err("bridge is null\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (brdg->ch_id >= MAX_BRIDGE_DEVICES)
|
||
|
return -EINVAL;
|
||
|
|
||
|
dev = __dev[brdg->ch_id];
|
||
|
if (!dev) {
|
||
|
err("dev is null\n");
|
||
|
return -ENODEV;
|
||
|
}
|
||
|
|
||
|
dev->brdg = brdg;
|
||
|
dev->snd_encap_cmd = 0;
|
||
|
dev->get_encap_res = 0;
|
||
|
dev->resp_avail = 0;
|
||
|
dev->set_ctrl_line_sts = 0;
|
||
|
dev->notify_ser_state = 0;
|
||
|
|
||
|
if (brdg->ops.send_cbits)
|
||
|
brdg->ops.send_cbits(brdg->ctx, dev->cbits_tohost);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL(ctrl_bridge_open);
|
||
|
|
||
|
void ctrl_bridge_close(unsigned int id)
|
||
|
{
|
||
|
struct ctrl_bridge *dev;
|
||
|
|
||
|
if (id >= MAX_BRIDGE_DEVICES)
|
||
|
return;
|
||
|
|
||
|
dev = __dev[id];
|
||
|
if (!dev || !dev->brdg)
|
||
|
return;
|
||
|
|
||
|
dev_dbg(&dev->intf->dev, "%s:\n", __func__);
|
||
|
|
||
|
ctrl_bridge_set_cbits(dev->brdg->ch_id, 0);
|
||
|
|
||
|
dev->brdg = NULL;
|
||
|
}
|
||
|
EXPORT_SYMBOL(ctrl_bridge_close);
|
||
|
|
||
|
static void ctrl_write_callback(struct urb *urb)
|
||
|
{
|
||
|
struct ctrl_bridge *dev = urb->context;
|
||
|
|
||
|
if (urb->status) {
|
||
|
pr_debug("Write status/size %d/%d\n",
|
||
|
urb->status, urb->actual_length);
|
||
|
}
|
||
|
|
||
|
kfree(urb->transfer_buffer);
|
||
|
kfree(urb->setup_packet);
|
||
|
usb_free_urb(urb);
|
||
|
usb_autopm_put_interface_async(dev->intf);
|
||
|
}
|
||
|
|
||
|
int ctrl_bridge_write(unsigned int id, char *data, size_t size)
|
||
|
{
|
||
|
int result;
|
||
|
struct urb *writeurb;
|
||
|
struct usb_ctrlrequest *out_ctlreq;
|
||
|
struct ctrl_bridge *dev;
|
||
|
|
||
|
if (id >= MAX_BRIDGE_DEVICES) {
|
||
|
result = -EINVAL;
|
||
|
goto free_data;
|
||
|
}
|
||
|
|
||
|
dev = __dev[id];
|
||
|
|
||
|
if (!dev) {
|
||
|
result = -ENODEV;
|
||
|
goto free_data;
|
||
|
}
|
||
|
|
||
|
dev_dbg(&dev->intf->dev, "%s:[id]:%u: write (%d bytes)\n",
|
||
|
__func__, id, size);
|
||
|
|
||
|
writeurb = usb_alloc_urb(0, GFP_ATOMIC);
|
||
|
if (!writeurb) {
|
||
|
dev_err(&dev->intf->dev, "%s: error allocating read urb\n",
|
||
|
__func__);
|
||
|
result = -ENOMEM;
|
||
|
goto free_data;
|
||
|
}
|
||
|
|
||
|
out_ctlreq = kmalloc(sizeof(*out_ctlreq), GFP_ATOMIC);
|
||
|
if (!out_ctlreq) {
|
||
|
dev_err(&dev->intf->dev,
|
||
|
"%s: error allocating setup packet buffer\n",
|
||
|
__func__);
|
||
|
result = -ENOMEM;
|
||
|
goto free_urb;
|
||
|
}
|
||
|
|
||
|
/* CDC Send Encapsulated Request packet */
|
||
|
out_ctlreq->bRequestType = (USB_DIR_OUT | USB_TYPE_CLASS |
|
||
|
USB_RECIP_INTERFACE);
|
||
|
if (!data && !size) {
|
||
|
out_ctlreq->bRequest = USB_CDC_REQ_SET_CONTROL_LINE_STATE;
|
||
|
out_ctlreq->wValue = dev->cbits_tomdm;
|
||
|
dev->set_ctrl_line_sts++;
|
||
|
} else {
|
||
|
out_ctlreq->bRequest = USB_CDC_SEND_ENCAPSULATED_COMMAND;
|
||
|
out_ctlreq->wValue = 0;
|
||
|
dev->snd_encap_cmd++;
|
||
|
}
|
||
|
out_ctlreq->wIndex =
|
||
|
dev->intf->cur_altsetting->desc.bInterfaceNumber;
|
||
|
out_ctlreq->wLength = cpu_to_le16(size);
|
||
|
|
||
|
usb_fill_control_urb(writeurb, dev->udev,
|
||
|
usb_sndctrlpipe(dev->udev, 0),
|
||
|
(unsigned char *)out_ctlreq,
|
||
|
(void *)data, size,
|
||
|
ctrl_write_callback, dev);
|
||
|
|
||
|
result = usb_autopm_get_interface_async(dev->intf);
|
||
|
if (result < 0) {
|
||
|
dev_dbg(&dev->intf->dev, "%s: unable to resume interface: %d\n",
|
||
|
__func__, result);
|
||
|
|
||
|
/*
|
||
|
* Revisit: if (result == -EPERM)
|
||
|
* bridge_suspend(dev->intf, PMSG_SUSPEND);
|
||
|
*/
|
||
|
|
||
|
goto free_ctrlreq;
|
||
|
}
|
||
|
|
||
|
if (test_bit(SUSPENDED, &dev->flags)) {
|
||
|
usb_anchor_urb(writeurb, &dev->tx_deferred);
|
||
|
goto deferred;
|
||
|
}
|
||
|
|
||
|
usb_anchor_urb(writeurb, &dev->tx_submitted);
|
||
|
result = usb_submit_urb(writeurb, GFP_ATOMIC);
|
||
|
if (result < 0) {
|
||
|
dev_err(&dev->intf->dev, "%s: submit URB error %d\n",
|
||
|
__func__, result);
|
||
|
usb_autopm_put_interface_async(dev->intf);
|
||
|
goto unanchor_urb;
|
||
|
}
|
||
|
deferred:
|
||
|
return size;
|
||
|
|
||
|
unanchor_urb:
|
||
|
usb_unanchor_urb(writeurb);
|
||
|
free_ctrlreq:
|
||
|
kfree(out_ctlreq);
|
||
|
free_urb:
|
||
|
usb_free_urb(writeurb);
|
||
|
free_data:
|
||
|
kfree(data);
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
EXPORT_SYMBOL(ctrl_bridge_write);
|
||
|
|
||
|
int ctrl_bridge_suspend(unsigned int id)
|
||
|
{
|
||
|
struct ctrl_bridge *dev;
|
||
|
|
||
|
if (id >= MAX_BRIDGE_DEVICES)
|
||
|
return -EINVAL;
|
||
|
|
||
|
dev = __dev[id];
|
||
|
if (!dev)
|
||
|
return -ENODEV;
|
||
|
|
||
|
set_bit(SUSPENDED, &dev->flags);
|
||
|
usb_kill_anchored_urbs(&dev->tx_submitted);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int ctrl_bridge_resume(unsigned int id)
|
||
|
{
|
||
|
struct ctrl_bridge *dev;
|
||
|
struct urb *urb;
|
||
|
|
||
|
if (id >= MAX_BRIDGE_DEVICES)
|
||
|
return -EINVAL;
|
||
|
|
||
|
dev = __dev[id];
|
||
|
if (!dev)
|
||
|
return -ENODEV;
|
||
|
|
||
|
if (!test_and_clear_bit(SUSPENDED, &dev->flags))
|
||
|
return 0;
|
||
|
|
||
|
/* submit pending write requests */
|
||
|
while ((urb = usb_get_from_anchor(&dev->tx_deferred))) {
|
||
|
int ret;
|
||
|
usb_anchor_urb(urb, &dev->tx_submitted);
|
||
|
ret = usb_submit_urb(urb, GFP_ATOMIC);
|
||
|
if (ret < 0) {
|
||
|
usb_unanchor_urb(urb);
|
||
|
kfree(urb->setup_packet);
|
||
|
kfree(urb->transfer_buffer);
|
||
|
usb_free_urb(urb);
|
||
|
usb_autopm_put_interface_async(dev->intf);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ctrl_bridge_start_read(dev);
|
||
|
}
|
||
|
|
||
|
#if defined(CONFIG_DEBUG_FS)
|
||
|
#define DEBUG_BUF_SIZE 1024
|
||
|
static ssize_t ctrl_bridge_read_stats(struct file *file, char __user *ubuf,
|
||
|
size_t count, loff_t *ppos)
|
||
|
{
|
||
|
struct ctrl_bridge *dev;
|
||
|
char *buf;
|
||
|
int ret;
|
||
|
int i;
|
||
|
int temp = 0;
|
||
|
|
||
|
buf = kzalloc(sizeof(char) * DEBUG_BUF_SIZE, GFP_KERNEL);
|
||
|
if (!buf)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
for (i = 0; i < ch_id; i++) {
|
||
|
dev = __dev[i];
|
||
|
if (!dev)
|
||
|
continue;
|
||
|
|
||
|
temp += scnprintf(buf + temp, DEBUG_BUF_SIZE - temp,
|
||
|
"\nName#%s dev %p\n"
|
||
|
"snd encap cmd cnt: %u\n"
|
||
|
"get encap res cnt: %u\n"
|
||
|
"res available cnt: %u\n"
|
||
|
"set ctrlline sts cnt: %u\n"
|
||
|
"notify ser state cnt: %u\n"
|
||
|
"cbits_tomdm: %d\n"
|
||
|
"cbits_tohost: %d\n"
|
||
|
"suspended: %d\n",
|
||
|
dev->pdev->name, dev,
|
||
|
dev->snd_encap_cmd,
|
||
|
dev->get_encap_res,
|
||
|
dev->resp_avail,
|
||
|
dev->set_ctrl_line_sts,
|
||
|
dev->notify_ser_state,
|
||
|
dev->cbits_tomdm,
|
||
|
dev->cbits_tohost,
|
||
|
test_bit(SUSPENDED, &dev->flags));
|
||
|
}
|
||
|
|
||
|
ret = simple_read_from_buffer(ubuf, count, ppos, buf, temp);
|
||
|
|
||
|
kfree(buf);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static ssize_t ctrl_bridge_reset_stats(struct file *file,
|
||
|
const char __user *buf, size_t count, loff_t *ppos)
|
||
|
{
|
||
|
struct ctrl_bridge *dev;
|
||
|
int i;
|
||
|
|
||
|
for (i = 0; i < ch_id; i++) {
|
||
|
dev = __dev[i];
|
||
|
if (!dev)
|
||
|
continue;
|
||
|
|
||
|
dev->snd_encap_cmd = 0;
|
||
|
dev->get_encap_res = 0;
|
||
|
dev->resp_avail = 0;
|
||
|
dev->set_ctrl_line_sts = 0;
|
||
|
dev->notify_ser_state = 0;
|
||
|
}
|
||
|
return count;
|
||
|
}
|
||
|
|
||
|
const struct file_operations ctrl_stats_ops = {
|
||
|
.read = ctrl_bridge_read_stats,
|
||
|
.write = ctrl_bridge_reset_stats,
|
||
|
};
|
||
|
|
||
|
struct dentry *ctrl_dent;
|
||
|
struct dentry *ctrl_dfile;
|
||
|
static void ctrl_bridge_debugfs_init(void)
|
||
|
{
|
||
|
ctrl_dent = debugfs_create_dir("ctrl_hsic_bridge", 0);
|
||
|
if (IS_ERR(ctrl_dent))
|
||
|
return;
|
||
|
|
||
|
ctrl_dfile =
|
||
|
debugfs_create_file("status", 0644, ctrl_dent, 0,
|
||
|
&ctrl_stats_ops);
|
||
|
if (!ctrl_dfile || IS_ERR(ctrl_dfile))
|
||
|
debugfs_remove(ctrl_dent);
|
||
|
}
|
||
|
|
||
|
static void ctrl_bridge_debugfs_exit(void)
|
||
|
{
|
||
|
debugfs_remove(ctrl_dfile);
|
||
|
debugfs_remove(ctrl_dent);
|
||
|
}
|
||
|
|
||
|
#else
|
||
|
static void ctrl_bridge_debugfs_init(void) { }
|
||
|
static void ctrl_bridge_debugfs_exit(void) { }
|
||
|
#endif
|
||
|
|
||
|
int
|
||
|
ctrl_bridge_probe(struct usb_interface *ifc, struct usb_host_endpoint *int_in,
|
||
|
int id)
|
||
|
{
|
||
|
struct ctrl_bridge *dev;
|
||
|
struct usb_device *udev;
|
||
|
struct usb_endpoint_descriptor *ep;
|
||
|
u16 wMaxPacketSize;
|
||
|
int retval = 0;
|
||
|
int interval;
|
||
|
|
||
|
udev = interface_to_usbdev(ifc);
|
||
|
|
||
|
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
|
||
|
if (!dev) {
|
||
|
dev_err(&ifc->dev, "%s: unable to allocate dev\n",
|
||
|
__func__);
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
dev->pdev = platform_device_alloc(ctrl_bridge_names[id], id);
|
||
|
if (!dev->pdev) {
|
||
|
dev_err(&ifc->dev, "%s: unable to allocate platform device\n",
|
||
|
__func__);
|
||
|
retval = -ENOMEM;
|
||
|
goto nomem;
|
||
|
}
|
||
|
|
||
|
dev->udev = udev;
|
||
|
dev->int_pipe = usb_rcvintpipe(udev,
|
||
|
int_in->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);
|
||
|
dev->intf = ifc;
|
||
|
|
||
|
init_usb_anchor(&dev->tx_submitted);
|
||
|
init_usb_anchor(&dev->tx_deferred);
|
||
|
|
||
|
/*use max pkt size from ep desc*/
|
||
|
ep = &dev->intf->cur_altsetting->endpoint[0].desc;
|
||
|
|
||
|
dev->inturb = usb_alloc_urb(0, GFP_KERNEL);
|
||
|
if (!dev->inturb) {
|
||
|
dev_err(&ifc->dev, "%s: error allocating int urb\n", __func__);
|
||
|
retval = -ENOMEM;
|
||
|
goto pdev_del;
|
||
|
}
|
||
|
|
||
|
wMaxPacketSize = le16_to_cpu(ep->wMaxPacketSize);
|
||
|
|
||
|
dev->intbuf = kmalloc(wMaxPacketSize, GFP_KERNEL);
|
||
|
if (!dev->intbuf) {
|
||
|
dev_err(&ifc->dev, "%s: error allocating int buffer\n",
|
||
|
__func__);
|
||
|
retval = -ENOMEM;
|
||
|
goto free_inturb;
|
||
|
}
|
||
|
|
||
|
interval =
|
||
|
(udev->speed == USB_SPEED_HIGH) ? HS_INTERVAL : FS_LS_INTERVAL;
|
||
|
|
||
|
usb_fill_int_urb(dev->inturb, udev, dev->int_pipe,
|
||
|
dev->intbuf, wMaxPacketSize,
|
||
|
notification_available_cb, dev, interval);
|
||
|
|
||
|
dev->readurb = usb_alloc_urb(0, GFP_KERNEL);
|
||
|
if (!dev->readurb) {
|
||
|
dev_err(&ifc->dev, "%s: error allocating read urb\n",
|
||
|
__func__);
|
||
|
retval = -ENOMEM;
|
||
|
goto free_intbuf;
|
||
|
}
|
||
|
|
||
|
dev->readbuf = kmalloc(DEFAULT_READ_URB_LENGTH, GFP_KERNEL);
|
||
|
if (!dev->readbuf) {
|
||
|
dev_err(&ifc->dev, "%s: error allocating read buffer\n",
|
||
|
__func__);
|
||
|
retval = -ENOMEM;
|
||
|
goto free_rurb;
|
||
|
}
|
||
|
|
||
|
dev->in_ctlreq = kmalloc(sizeof(*dev->in_ctlreq), GFP_KERNEL);
|
||
|
if (!dev->in_ctlreq) {
|
||
|
dev_err(&ifc->dev, "%s:error allocating setup packet buffer\n",
|
||
|
__func__);
|
||
|
retval = -ENOMEM;
|
||
|
goto free_rbuf;
|
||
|
}
|
||
|
|
||
|
dev->in_ctlreq->bRequestType =
|
||
|
(USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE);
|
||
|
dev->in_ctlreq->bRequest = USB_CDC_GET_ENCAPSULATED_RESPONSE;
|
||
|
dev->in_ctlreq->wValue = 0;
|
||
|
dev->in_ctlreq->wIndex =
|
||
|
dev->intf->cur_altsetting->desc.bInterfaceNumber;
|
||
|
dev->in_ctlreq->wLength = cpu_to_le16(DEFAULT_READ_URB_LENGTH);
|
||
|
|
||
|
__dev[id] = dev;
|
||
|
|
||
|
platform_device_add(dev->pdev);
|
||
|
|
||
|
ch_id++;
|
||
|
|
||
|
return ctrl_bridge_start_read(dev);
|
||
|
|
||
|
free_rbuf:
|
||
|
kfree(dev->readbuf);
|
||
|
free_rurb:
|
||
|
usb_free_urb(dev->readurb);
|
||
|
free_intbuf:
|
||
|
kfree(dev->intbuf);
|
||
|
free_inturb:
|
||
|
usb_free_urb(dev->inturb);
|
||
|
pdev_del:
|
||
|
platform_device_unregister(dev->pdev);
|
||
|
nomem:
|
||
|
kfree(dev);
|
||
|
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
void ctrl_bridge_disconnect(unsigned int id)
|
||
|
{
|
||
|
struct ctrl_bridge *dev = __dev[id];
|
||
|
|
||
|
dev_dbg(&dev->intf->dev, "%s:\n", __func__);
|
||
|
|
||
|
platform_device_unregister(dev->pdev);
|
||
|
|
||
|
usb_unlink_anchored_urbs(&dev->tx_submitted);
|
||
|
|
||
|
kfree(dev->in_ctlreq);
|
||
|
kfree(dev->readbuf);
|
||
|
kfree(dev->intbuf);
|
||
|
|
||
|
usb_free_urb(dev->readurb);
|
||
|
usb_free_urb(dev->inturb);
|
||
|
|
||
|
__dev[id] = NULL;
|
||
|
ch_id--;
|
||
|
|
||
|
kfree(dev);
|
||
|
}
|
||
|
|
||
|
static int __init ctrl_bridge_init(void)
|
||
|
{
|
||
|
ctrl_bridge_debugfs_init();
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
module_init(ctrl_bridge_init);
|
||
|
|
||
|
static void __exit ctrl_bridge_exit(void)
|
||
|
{
|
||
|
ctrl_bridge_debugfs_exit();
|
||
|
}
|
||
|
module_exit(ctrl_bridge_exit);
|
||
|
|
||
|
MODULE_DESCRIPTION("Qualcomm modem control bridge driver");
|
||
|
MODULE_LICENSE("GPL v2");
|