blob: 7c38390ffe94168214d8ed5b1c0d27cf5260f5b0 [file] [log] [blame]
/*
* Copyright (C) 2011 Google, Inc.
*
* Author:
* Colin Cross <ccross@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.
*
*/
#include <linux/kernel.h>
#include <linux/mutex.h>
#include <linux/notifier.h>
#include <linux/usb/otg_id.h>
static DEFINE_MUTEX(otg_id_lock);
static struct plist_head otg_id_plist =
PLIST_HEAD_INIT(otg_id_plist);
static struct otg_id_notifier_block *otg_id_active;
static bool otg_id_cancelling;
static bool otg_id_inited;
static int otg_id_suspended;
static bool otg_id_pending;
static void otg_id_cancel(void)
{
if (otg_id_active) {
otg_id_cancelling = true;
mutex_unlock(&otg_id_lock);
otg_id_active->cancel(otg_id_active);
mutex_lock(&otg_id_lock);
otg_id_cancelling = false;
}
}
static void __otg_id_notify(void)
{
int ret = 0;
struct otg_id_notifier_block *otg_id_nb;
bool proxy_wait = false;
if (plist_head_empty(&otg_id_plist))
return;
plist_for_each_entry(otg_id_nb, &otg_id_plist, p) {
if (proxy_wait) {
if (otg_id_nb->proxy_wait)
ret = otg_id_nb->proxy_wait(otg_id_nb);
} else {
ret = otg_id_nb->detect(otg_id_nb);
}
if (ret == OTG_ID_HANDLED) {
otg_id_active = otg_id_nb;
return;
}
if (ret == OTG_ID_PROXY_WAIT)
proxy_wait = true;
}
WARN(1, "otg id event not handled");
otg_id_active = NULL;
}
int otg_id_init(void)
{
mutex_lock(&otg_id_lock);
otg_id_inited = true;
__otg_id_notify();
mutex_unlock(&otg_id_lock);
return 0;
}
late_initcall(otg_id_init);
/**
* otg_id_register_notifier
* @otg_id_nb: notifier block containing priority and callback function
*
* Register a notifier that will be called on any USB cable state change.
* The priority determines the order the callback will be called in, a higher
* number will be called first. A callback function needs to determine the
* type of USB cable that is connected. If it can determine the type, it
* should notify the appropriate drivers (for example, call an otg notifier
* with USB_EVENT_VBUS), and return OTG_ID_HANDLED. Once a callback has
* returned OTG_ID_HANDLED, it is responsible for calling otg_id_notify() when
* the detected USB cable is disconnected.
*/
int otg_id_register_notifier(struct otg_id_notifier_block *otg_id_nb)
{
plist_node_init(&otg_id_nb->p, otg_id_nb->priority);
mutex_lock(&otg_id_lock);
plist_add(&otg_id_nb->p, &otg_id_plist);
if (otg_id_inited) {
otg_id_cancel();
__otg_id_notify();
}
mutex_unlock(&otg_id_lock);
return 0;
}
void otg_id_unregister_notifier(struct otg_id_notifier_block *otg_id_nb)
{
mutex_lock(&otg_id_lock);
plist_del(&otg_id_nb->p, &otg_id_plist);
if (otg_id_inited && (otg_id_active == otg_id_nb)) {
otg_id_cancel();
__otg_id_notify();
}
mutex_unlock(&otg_id_lock);
}
/**
* otg_id_notify
*
* Notify listeners on any USB cable state change.
*
* A driver may only call otg_id_notify if it returned OTG_ID_HANDLED the last
* time it's notifier was called, and it's cancel function has not been called.
*/
void otg_id_notify(void)
{
mutex_lock(&otg_id_lock);
if (otg_id_cancelling)
goto out;
if (otg_id_suspended != 0) {
otg_id_pending = true;
goto out;
}
__otg_id_notify();
out:
mutex_unlock(&otg_id_lock);
}
/**
* otg_id_suspend
*
* Mark the otg_id subsystem as going into suspend. From here on out,
* any notifications will be deferred until the last otg_id client resumes.
* If there is a pending notification when calling this function, it will
* return a negative errno and expects that the caller will abort suspend.
* Returs 0 on success.
*/
int otg_id_suspend(void)
{
int ret = 0;
mutex_lock(&otg_id_lock);
/*
* if there's a pending notification, tell the caller to abort suspend
*/
if (otg_id_suspended != 0 && otg_id_pending) {
pr_info("otg_id: pending notification, should abort suspend\n");
ret = -EBUSY;
goto out;
}
otg_id_suspended++;
out:
mutex_unlock(&otg_id_lock);
return ret;
}
/**
* otg_id_resume
*
* Inform the otg_id subsystem that a client is resuming. If this is the
* last client to be resumed and there's a pending notification,
* otg_id_notify() is called.
*/
void otg_id_resume(void)
{
mutex_lock(&otg_id_lock);
if (WARN(!otg_id_suspended, "unbalanced otg_id_resume\n"))
goto out;
if (--otg_id_suspended == 0) {
if (otg_id_pending) {
pr_info("otg_id: had pending notification\n");
otg_id_pending = false;
__otg_id_notify();
}
}
out:
mutex_unlock(&otg_id_lock);
}