blob: 5874a42508131e6845c09d5ffa7306ba7e91352a [file] [log] [blame]
/*
* Copyright (C) 2015-2017 Samsung Electronics Co. Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
/* usb_power_notify.c */
#define pr_fmt(fmt) "usb_notify: " fmt
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/err.h>
#include <linux/usb.h>
#include <linux/notifier.h>
#include <linux/version.h>
#include <linux/usb_notify.h>
#include "usb_power_notify.h"
#include "../core/hub.h"
enum usb_port_state {
PORT_EMPTY = 0, /* OTG only */
PORT_USB2, /* usb 2.0 device only */
PORT_USB3, /* usb 3.0 device only */
PORT_HUB /* usb hub single */
};
static enum usb_port_state port_state;
int is_otg_only;
static int check_usb3_hub(struct usb_device *dev, bool on)
{
struct usb_device *hdev;
struct usb_device *udev = dev;
struct usb_hub *hub;
enum usb_port_state pre_state;
int usb3_hub_detect = 0;
int usb2_detect = 0;
int port;
int bInterfaceClass = 0;
if (udev->bus->root_hub == udev) {
pr_info("this dev is a root hub\n");
goto skip;
}
pre_state = port_state;
/* Find root hub */
hdev = udev->parent;
if (!hdev)
goto skip;
for (port = 0; port < HUB_MAX_DEPTH; port++) {
if (hdev == udev->bus->root_hub)
break;
hdev = hdev->parent;
}
pr_info("root hub depth = %d\n", port);
hub = usb_hub_to_struct_hub(hdev);
if (!hub)
goto skip;
/* check all ports */
for (port = 1; port <= hdev->maxchild; port++) {
udev = hub->ports[port-1]->child;
if (udev && udev->state != USB_STATE_NOTATTACHED) {
if (udev->config->interface[0] == NULL)
continue;
bInterfaceClass = udev->config->interface[0]
->cur_altsetting->desc.bInterfaceClass;
if (on) {
#ifdef CONFIG_USB_HOST_SAMSUNG_FEATURE
if (bInterfaceClass == USB_CLASS_AUDIO) {
#else
if ((bInterfaceClass == USB_CLASS_HID) ||
(bInterfaceClass == USB_CLASS_AUDIO)) {
#endif
udev->do_remote_wakeup =
(udev->config->desc.bmAttributes &
USB_CONFIG_ATT_WAKEUP) ? 1 : 0;
if (udev->do_remote_wakeup == 1) {
device_init_wakeup(&udev->dev, 1);
usb_enable_autosuspend(dev);
}
}
}
if (udev->descriptor.bDeviceClass == USB_CLASS_HUB) {
port_state = PORT_HUB;
usb3_hub_detect = 1;
break;
}
if (udev->speed >= USB_SPEED_SUPER) {
port_state = PORT_USB3;
usb3_hub_detect = 1;
break;
} else {
port_state = PORT_USB2;
usb2_detect = 1;
}
}
}
if (!usb3_hub_detect && !usb2_detect)
port_state = PORT_EMPTY;
pr_info("%s %s state pre=%d now=%d\n", __func__,
on ? "on" : "off", pre_state, port_state);
return port_state;
skip:
return -EINVAL;
}
static void set_usb3_port_power(struct usb_device *dev, bool on)
{
switch (check_usb3_hub(dev, on)) {
case PORT_EMPTY:
pr_info("Port check empty\n");
is_otg_only = 1;
xhci_port_power_set(1, 1);
break;
case PORT_USB2:
pr_info("Port check usb2\n");
is_otg_only = 0;
xhci_port_power_set(0, 1);
break;
case PORT_USB3:
xhci_port_power_set(1, 1);
is_otg_only = 0;
pr_info("Port check usb3\n");
break;
case PORT_HUB:
/*xhci_port_power_set(1, 1);*/
pr_info("Port check hub\n");
is_otg_only = 0;
break;
default:
break;
}
}
static int usb_power_notify(struct notifier_block *self,
unsigned long action, void *dev)
{
switch (action) {
case USB_DEVICE_ADD:
set_usb3_port_power(dev, 1);
break;
case USB_DEVICE_REMOVE:
set_usb3_port_power(dev, 0);
break;
}
return NOTIFY_OK;
}
static struct notifier_block dev_nb = {
.notifier_call = usb_power_notify,
};
void register_usb_power_notify(void)
{
pr_info("%s\n", __func__);
is_otg_only = 1;
port_state = PORT_EMPTY;
usb_register_notify(&dev_nb);
}
EXPORT_SYMBOL(register_usb_power_notify);
void unregister_usb_power_notify(void)
{
pr_info("%s\n", __func__);
usb_unregister_notify(&dev_nb);
}
EXPORT_SYMBOL(unregister_usb_power_notify);