usb: diag_bridge: support autosuspend and autoresume
Allow automatic power management by the USB core by enabling
autosuspend in the DIAG Bridge driver. This allows the MDM device
to be suspended and draw minimal power when idle.
In the diagfwd_hsic driver, register suspend and resume callbacks
to know when to temporarily halt queuing read or write requests to
the bridge which would otherwise prevent the HSIC device from
triggering an autosuspend.
Similarly, close the bridge when the USB cable is disconnected to
signify that it is done using it, or else there would forever be a
pending read or write on the bridge that would keep the HSIC active.
Revert allocation flags in diag_bridge_read() and diag_bridge_write()
to GFP_KERNEL, as they now can only be called in process context.
Change-Id: I124460bbc90ff10a1a7f8e6ab94dab0fbfc5d0df
Signed-off-by: Jack Pham <jackp@codeaurora.org>
diff --git a/arch/arm/mach-msm/include/mach/diag_bridge.h b/arch/arm/mach-msm/include/mach/diag_bridge.h
index a39ed25..b06f020 100644
--- a/arch/arm/mach-msm/include/mach/diag_bridge.h
+++ b/arch/arm/mach-msm/include/mach/diag_bridge.h
@@ -19,6 +19,8 @@
int buf_size, int actual);
void (*write_complete_cb)(void *ctxt, char *buf,
int buf_size, int actual);
+ int (*suspend)(void *ctxt);
+ void (*resume)(void *ctxt);
};
#if defined(CONFIG_USB_QCOM_DIAG_BRIDGE) \
diff --git a/drivers/char/diag/diagchar.h b/drivers/char/diag/diagchar.h
index 371d319..a701773 100644
--- a/drivers/char/diag/diagchar.h
+++ b/drivers/char/diag/diagchar.h
@@ -228,6 +228,7 @@
int hsic_ch;
int hsic_device_enabled;
int hsic_device_opened;
+ int hsic_suspend;
int read_len_mdm;
int in_busy_hsic_read_on_mdm;
int in_busy_hsic_write_on_mdm;
@@ -238,6 +239,8 @@
struct workqueue_struct *diag_hsic_wq;
struct work_struct diag_read_mdm_work;
struct work_struct diag_read_hsic_work;
+ struct work_struct diag_disconnect_work;
+ struct work_struct diag_usb_read_complete_work;
struct diag_request *usb_read_mdm_ptr;
struct diag_request *write_ptr_mdm;
#endif
diff --git a/drivers/char/diag/diagfwd_hsic.c b/drivers/char/diag/diagfwd_hsic.c
index ac5722f..b2080b3 100644
--- a/drivers/char/diag/diagfwd_hsic.c
+++ b/drivers/char/diag/diagfwd_hsic.c
@@ -102,14 +102,16 @@
driver->write_ptr_mdm);
}
} else {
- pr_err("DIAG in %s: actual_size: %d\n", __func__, actual_size);
+ pr_debug("%s: actual_size: %d\n", __func__, actual_size);
}
/*
* If for some reason there was no hsic data to write to the
* mdm channel, set up another read
*/
- if (!driver->in_busy_hsic_write_on_mdm)
+ if (!driver->in_busy_hsic_write_on_mdm &&
+ driver->usb_mdm_connected &&
+ !driver->hsic_suspend)
queue_work(driver->diag_hsic_wq, &driver->diag_read_hsic_work);
}
@@ -127,13 +129,34 @@
if (actual_size < 0)
pr_err("DIAG in %s: actual_size: %d\n", __func__, actual_size);
- queue_work(driver->diag_hsic_wq, &driver->diag_read_mdm_work);
+ if (driver->usb_mdm_connected)
+ queue_work(driver->diag_hsic_wq, &driver->diag_read_mdm_work);
+}
+
+static int diag_hsic_suspend(void *ctxt)
+{
+ if (driver->in_busy_hsic_write)
+ return -EBUSY;
+
+ driver->hsic_suspend = 1;
+
+ return 0;
+}
+
+static void diag_hsic_resume(void *ctxt)
+{
+ driver->hsic_suspend = 0;
+
+ if (!driver->in_busy_hsic_write_on_mdm && driver->usb_mdm_connected)
+ queue_work(driver->diag_hsic_wq, &driver->diag_read_hsic_work);
}
static struct diag_bridge_ops hsic_diag_bridge_ops = {
.ctxt = NULL,
.read_complete_cb = diag_hsic_read_complete_callback,
.write_complete_cb = diag_hsic_write_complete_callback,
+ .suspend = diag_hsic_suspend,
+ .resume = diag_hsic_resume,
};
static int diag_hsic_close(void)
@@ -177,11 +200,11 @@
pr_err("DIAG: HSIC channel open error: %d\n",
err);
} else {
- pr_info("DIAG: opened HSIC channel\n");
+ pr_debug("DIAG: opened HSIC channel\n");
driver->hsic_device_opened = 1;
}
} else {
- pr_info("DIAG: HSIC channel already open\n");
+ pr_debug("DIAG: HSIC channel already open\n");
}
/*
@@ -220,9 +243,7 @@
driver->in_busy_hsic_read = 1;
/* Turn off communication over usb mdm and hsic */
- driver->hsic_ch = 0;
-
- return 0;
+ return diag_hsic_close();
}
/*
@@ -311,10 +332,11 @@
diagfwd_connect_hsic();
break;
case USB_DIAG_DISCONNECT:
- diagfwd_disconnect_hsic();
+ queue_work(driver->diag_hsic_wq, &driver->diag_disconnect_work);
break;
case USB_DIAG_READ_DONE:
- diagfwd_read_complete_hsic(d_req);
+ queue_work(driver->diag_hsic_wq,
+ &driver->diag_usb_read_complete_work);
break;
case USB_DIAG_WRITE_DONE:
diagfwd_write_complete_hsic();
@@ -326,6 +348,16 @@
}
}
+static void diag_usb_read_complete_fn(struct work_struct *w)
+{
+ diagfwd_read_complete_hsic(driver->usb_read_mdm_ptr);
+}
+
+static void diag_disconnect_work_fn(struct work_struct *w)
+{
+ diagfwd_disconnect_hsic();
+}
+
static void diag_read_mdm_work_fn(struct work_struct *work)
{
if (!driver->hsic_ch) {
@@ -380,7 +412,6 @@
sizeof(struct diag_request), GFP_KERNEL);
if (driver->usb_read_mdm_ptr == NULL)
goto err;
- driver->diag_hsic_wq = create_singlethread_workqueue("diag_hsic_wq");
#ifdef CONFIG_DIAG_OVER_USB
INIT_WORK(&(driver->diag_read_mdm_work), diag_read_mdm_work_fn);
#endif
@@ -413,23 +444,22 @@
}
}
- /* The hsic (diag_bridge) platform device driver is enabled */
- err = diag_bridge_open(&hsic_diag_bridge_ops);
- if (err) {
- pr_err("DIAG could not open HSIC channel, err: %d\n", err);
- driver->hsic_device_opened = 0;
- return err;
- }
-
- pr_info("DIAG opened HSIC channel\n");
- driver->hsic_device_opened = 1;
-
/*
* The probe function was called after the usb was connected
* on the legacy channel. Communication over usb mdm and hsic
* needs to be turned on.
*/
- if (driver->usb_connected) {
+ if (driver->usb_mdm_connected) {
+ /* The hsic (diag_bridge) platform device driver is enabled */
+ err = diag_bridge_open(&hsic_diag_bridge_ops);
+ if (err) {
+ pr_err("DIAG could not open HSIC, err: %d\n", err);
+ driver->hsic_device_opened = 0;
+ return err;
+ }
+
+ pr_debug("DIAG opened HSIC channel\n");
+ driver->hsic_device_opened = 1;
driver->hsic_ch = 1;
driver->in_busy_hsic_write_on_mdm = 0;
driver->in_busy_hsic_read_on_mdm = 0;
@@ -448,7 +478,7 @@
static int diag_hsic_remove(struct platform_device *pdev)
{
- pr_info("DIAG: %s called\n", __func__);
+ pr_debug("DIAG: %s called\n", __func__);
diag_hsic_close();
return 0;
}
@@ -487,6 +517,11 @@
pr_debug("DIAG in %s\n", __func__);
+ driver->diag_hsic_wq = create_singlethread_workqueue("diag_hsic_wq");
+ INIT_WORK(&(driver->diag_disconnect_work), diag_disconnect_work_fn);
+ INIT_WORK(&(driver->diag_usb_read_complete_work),
+ diag_usb_read_complete_fn);
+
#ifdef CONFIG_DIAG_OVER_USB
driver->mdm_ch = usb_diag_open(DIAG_MDM, driver, diagfwd_hsic_notifier);
if (IS_ERR(driver->mdm_ch)) {
diff --git a/drivers/usb/misc/diag_bridge.c b/drivers/usb/misc/diag_bridge.c
index 5ab987e..9794918 100644
--- a/drivers/usb/misc/diag_bridge.c
+++ b/drivers/usb/misc/diag_bridge.c
@@ -82,6 +82,7 @@
urb->status, urb->actual_length);
if (urb->status == -EPROTO) {
+ dev_err(&dev->udev->dev, "%s: proto error\n", __func__);
/* save error so that subsequent read/write returns ESHUTDOWN */
dev->err = urb->status;
return;
@@ -119,27 +120,36 @@
if (dev->err)
return -ESHUTDOWN;
- urb = usb_alloc_urb(0, GFP_ATOMIC);
+ urb = usb_alloc_urb(0, GFP_KERNEL);
if (!urb) {
dev_err(&dev->udev->dev, "unable to allocate urb\n");
return -ENOMEM;
}
+ ret = usb_autopm_get_interface(dev->ifc);
+ if (ret < 0) {
+ dev_err(&dev->udev->dev, "autopm_get failed:%d\n", ret);
+ usb_free_urb(urb);
+ return ret;
+ }
+
pipe = usb_rcvbulkpipe(dev->udev, dev->in_epAddr);
usb_fill_bulk_urb(urb, dev->udev, pipe, data, size,
diag_bridge_read_cb, dev);
usb_anchor_urb(urb, &dev->submitted);
dev->pending_reads++;
- ret = usb_submit_urb(urb, GFP_ATOMIC);
+ ret = usb_submit_urb(urb, GFP_KERNEL);
if (ret) {
dev_err(&dev->udev->dev, "submitting urb failed err:%d\n", ret);
dev->pending_reads--;
usb_unanchor_urb(urb);
usb_free_urb(urb);
+ usb_autopm_put_interface(dev->ifc);
return ret;
}
+ usb_autopm_put_interface(dev->ifc);
usb_free_urb(urb);
return 0;
@@ -153,7 +163,10 @@
dev_dbg(&dev->udev->dev, "%s:\n", __func__);
+ usb_autopm_put_interface_async(dev->ifc);
+
if (urb->status == -EPROTO) {
+ dev_err(&dev->udev->dev, "%s: proto error\n", __func__);
/* save error so that subsequent read/write returns ESHUTDOWN */
dev->err = urb->status;
return;
@@ -191,24 +204,32 @@
if (dev->err)
return -ESHUTDOWN;
- urb = usb_alloc_urb(0, GFP_ATOMIC);
+ urb = usb_alloc_urb(0, GFP_KERNEL);
if (!urb) {
err("unable to allocate urb");
return -ENOMEM;
}
+ ret = usb_autopm_get_interface(dev->ifc);
+ if (ret < 0) {
+ dev_err(&dev->udev->dev, "autopm_get failed:%d\n", ret);
+ usb_free_urb(urb);
+ return ret;
+ }
+
pipe = usb_sndbulkpipe(dev->udev, dev->out_epAddr);
usb_fill_bulk_urb(urb, dev->udev, pipe, data, size,
diag_bridge_write_cb, dev);
usb_anchor_urb(urb, &dev->submitted);
dev->pending_writes++;
- ret = usb_submit_urb(urb, GFP_ATOMIC);
+ ret = usb_submit_urb(urb, GFP_KERNEL);
if (ret) {
dev_err(&dev->udev->dev, "submitting urb failed err:%d\n", ret);
dev->pending_writes--;
usb_unanchor_urb(urb);
usb_free_urb(urb);
+ usb_autopm_put_interface(dev->ifc);
return ret;
}
@@ -381,6 +402,37 @@
usb_set_intfdata(ifc, NULL);
}
+static int diag_bridge_suspend(struct usb_interface *ifc, pm_message_t message)
+{
+ struct diag_bridge *dev = usb_get_intfdata(ifc);
+ struct diag_bridge_ops *cbs = dev->ops;
+ int ret = 0;
+
+ if (cbs && cbs->suspend) {
+ ret = cbs->suspend(cbs->ctxt);
+ if (ret) {
+ dev_dbg(&dev->udev->dev,
+ "%s: diag veto'd suspend\n", __func__);
+ return ret;
+ }
+
+ usb_kill_anchored_urbs(&dev->submitted);
+ }
+
+ return ret;
+}
+
+static int diag_bridge_resume(struct usb_interface *ifc)
+{
+ struct diag_bridge *dev = usb_get_intfdata(ifc);
+ struct diag_bridge_ops *cbs = dev->ops;
+
+
+ if (cbs && cbs->resume)
+ cbs->resume(cbs->ctxt);
+
+ return 0;
+}
#define VALID_INTERFACE_NUM 0
static const struct usb_device_id diag_bridge_ids[] = {
@@ -401,7 +453,10 @@
.name = "diag_bridge",
.probe = diag_bridge_probe,
.disconnect = diag_bridge_disconnect,
+ .suspend = diag_bridge_suspend,
+ .resume = diag_bridge_resume,
.id_table = diag_bridge_ids,
+ .supports_autosuspend = 1,
};
static int __init diag_bridge_init(void)
@@ -410,8 +465,7 @@
ret = usb_register(&diag_bridge_driver);
if (ret) {
- err("%s: unable to register diag driver",
- __func__);
+ err("%s: unable to register diag driver", __func__);
return ret;
}