| /* |
| * 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); |
| } |