usb: gadget: Vote for SWFI when USB cable is connected

To enable better power management and entering CPU idle states while
USB cable is connected and no USB SW involvement is required
(MSM BAM2BAM mode) a new SysFS was added.
This SysFS allow controlling USB voting and gives the ability
to save power consumption while USB is active.
default behavior is high QoS. to change the new SysFS
echo high/low to /sys/class/android_usb/android0/pm_qos

Change-Id: Ia73491cfddc3968e1d04423a1750c89fab5fefeb
Signed-off-by: Ofir Cohen <ofirc@codeaurora.org>
This commit is contained in:
Ofir Cohen 2012-05-03 14:26:32 +03:00 committed by Stephen Boyd
parent ab8d924e62
commit 521828cd2c
2 changed files with 95 additions and 1 deletions

View file

@ -23,10 +23,12 @@
#include <linux/kernel.h>
#include <linux/utsname.h>
#include <linux/platform_device.h>
#include <linux/pm_qos.h>
#include <linux/usb/ch9.h>
#include <linux/usb/composite.h>
#include <linux/usb/gadget.h>
#include <linux/usb/android.h>
#include "gadget_chips.h"
@ -106,8 +108,12 @@ struct android_dev {
bool enabled;
int disable_depth;
struct mutex mutex;
struct android_usb_platform_data *pdata;
bool connected;
bool sw_connected;
char pm_qos[5];
struct pm_qos_request pm_qos_req_dma;
struct work_struct work;
};
@ -162,6 +168,26 @@ static struct usb_configuration android_config_driver = {
.bMaxPower = 0xFA, /* 500ma */
};
static void android_pm_qos_update_latency(struct android_dev *dev, int vote)
{
struct android_usb_platform_data *pdata = dev->pdata;
u32 swfi_latency = 0;
static int last_vote = -1;
if (!pdata || vote == last_vote
|| !pdata->swfi_latency)
return;
swfi_latency = pdata->swfi_latency + 1;
if (vote)
pm_qos_update_request(&dev->pm_qos_req_dma,
swfi_latency);
else
pm_qos_update_request(&dev->pm_qos_req_dma,
PM_QOS_DEFAULT_VALUE);
last_vote = vote;
}
static void android_work(struct work_struct *data)
{
struct android_dev *dev = container_of(data, struct android_dev, work);
@ -171,15 +197,24 @@ static void android_work(struct work_struct *data)
char *configured[2] = { "USB_STATE=CONFIGURED", NULL };
char **uevent_envp = NULL;
unsigned long flags;
int pm_qos_vote = -1;
spin_lock_irqsave(&cdev->lock, flags);
if (cdev->config)
uevent_envp = configured;
else if (dev->connected != dev->sw_connected)
else if (dev->connected != dev->sw_connected) {
uevent_envp = dev->connected ? connected : disconnected;
if (dev->connected && strncmp(dev->pm_qos, "low", 3))
pm_qos_vote = 1;
else if (!dev->connected || !strncmp(dev->pm_qos, "low", 3))
pm_qos_vote = 0;
}
dev->sw_connected = dev->connected;
spin_unlock_irqrestore(&cdev->lock, flags);
if (pm_qos_vote != -1)
android_pm_qos_update_latency(dev, pm_qos_vote);
if (uevent_envp) {
kobject_uevent_env(&dev->dev->kobj, KOBJ_CHANGE, uevent_envp);
pr_info("%s: sent uevent %s\n", __func__, uevent_envp[0]);
@ -1017,6 +1052,26 @@ static ssize_t enable_store(struct device *pdev, struct device_attribute *attr,
}
mutex_unlock(&dev->mutex);
return size;
}
static ssize_t pm_qos_show(struct device *pdev,
struct device_attribute *attr, char *buf)
{
struct android_dev *dev = dev_get_drvdata(pdev);
return snprintf(buf, PAGE_SIZE, "%s\n", dev->pm_qos);
}
static ssize_t pm_qos_store(struct device *pdev,
struct device_attribute *attr,
const char *buff, size_t size)
{
struct android_dev *dev = dev_get_drvdata(pdev);
strlcpy(dev->pm_qos, buff, sizeof(dev->pm_qos));
return size;
}
@ -1092,6 +1147,8 @@ DESCRIPTOR_STRING_ATTR(iSerial, serial_string)
static DEVICE_ATTR(functions, S_IRUGO | S_IWUSR, functions_show,
functions_store);
static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR, enable_show, enable_store);
static DEVICE_ATTR(pm_qos, S_IRUGO | S_IWUSR,
pm_qos_show, pm_qos_store);
static DEVICE_ATTR(state, S_IRUGO, state_show, NULL);
static struct device_attribute *android_usb_attributes[] = {
@ -1106,6 +1163,7 @@ static struct device_attribute *android_usb_attributes[] = {
&dev_attr_iSerial,
&dev_attr_functions,
&dev_attr_enable,
&dev_attr_pm_qos,
&dev_attr_state,
NULL
};
@ -1303,9 +1361,12 @@ static void android_destroy_device(struct android_dev *dev)
static int __devinit android_probe(struct platform_device *pdev)
{
struct android_usb_platform_data *pdata = pdev->dev.platform_data;
struct android_dev *dev = _android_dev;
int ret = 0;
dev->pdata = pdata;
android_class = class_create(THIS_MODULE, "android_usb");
if (IS_ERR(android_class))
return PTR_ERR(android_class);
@ -1323,6 +1384,12 @@ static int __devinit android_probe(struct platform_device *pdev)
goto err_probe;
}
/* pm qos request to prevent apps idle power collapse */
if (pdata && pdata->swfi_latency)
pm_qos_add_request(&dev->pm_qos_req_dma,
PM_QOS_CPU_DMA_LATENCY, PM_QOS_DEFAULT_VALUE);
strlcpy(dev->pm_qos, "high", sizeof(dev->pm_qos));
return ret;
err_probe:
android_destroy_device(dev);
@ -1334,10 +1401,13 @@ err_dev:
static int android_remove(struct platform_device *pdev)
{
struct android_dev *dev = _android_dev;
struct android_usb_platform_data *pdata = pdev->dev.platform_data;
android_destroy_device(dev);
class_destroy(android_class);
usb_composite_unregister(&android_usb_driver);
if (pdata && pdata->swfi_latency)
pm_qos_remove_request(&dev->pm_qos_req_dma);
return 0;
}

View file

@ -0,0 +1,24 @@
/*
* Platform data for Android USB
*
* Copyright (C) 2008 Google, Inc.
* Author: Mike Lockwood <lockwood@android.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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.
*
*/
#ifndef __LINUX_USB_ANDROID_H
#define __LINUX_USB_ANDROID_H
struct android_usb_platform_data {
u32 swfi_latency;
};
#endif /* __LINUX_USB_ANDROID_H */