Kernel  |  3.0

下载     查看原文件
C++程序  |  206行  |  4.74 KB
/*
 * 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;
	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);
}