2013-01-11 13:10:38 +01:00
|
|
|
/*
|
|
|
|
* usb port device code
|
|
|
|
*
|
|
|
|
* Copyright (C) 2012 Intel Corp
|
|
|
|
*
|
|
|
|
* Author: Lan Tianyu <tianyu.lan@intel.com>
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
|
|
* published by the Free Software Foundation.
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2013-01-19 15:30:19 +01:00
|
|
|
#include <linux/slab.h>
|
2013-01-22 21:26:29 +01:00
|
|
|
#include <linux/pm_qos.h>
|
2013-01-19 15:30:19 +01:00
|
|
|
|
2013-01-11 13:10:38 +01:00
|
|
|
#include "hub.h"
|
|
|
|
|
2013-01-19 18:53:32 +01:00
|
|
|
static const struct attribute_group *port_dev_group[];
|
|
|
|
|
|
|
|
static ssize_t show_port_connect_type(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
|
|
|
{
|
|
|
|
struct usb_port *port_dev = to_usb_port(dev);
|
|
|
|
char *result;
|
|
|
|
|
|
|
|
switch (port_dev->connect_type) {
|
|
|
|
case USB_PORT_CONNECT_TYPE_HOT_PLUG:
|
|
|
|
result = "hotplug";
|
|
|
|
break;
|
|
|
|
case USB_PORT_CONNECT_TYPE_HARD_WIRED:
|
|
|
|
result = "hardwired";
|
|
|
|
break;
|
|
|
|
case USB_PORT_NOT_USED:
|
|
|
|
result = "not used";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
result = "unknown";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return sprintf(buf, "%s\n", result);
|
|
|
|
}
|
|
|
|
static DEVICE_ATTR(connect_type, S_IRUGO, show_port_connect_type,
|
|
|
|
NULL);
|
|
|
|
|
|
|
|
static struct attribute *port_dev_attrs[] = {
|
|
|
|
&dev_attr_connect_type.attr,
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct attribute_group port_dev_attr_grp = {
|
|
|
|
.attrs = port_dev_attrs,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct attribute_group *port_dev_group[] = {
|
|
|
|
&port_dev_attr_grp,
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
2013-01-11 13:10:38 +01:00
|
|
|
static void usb_port_device_release(struct device *dev)
|
|
|
|
{
|
|
|
|
struct usb_port *port_dev = to_usb_port(dev);
|
|
|
|
|
2013-01-22 21:26:27 +01:00
|
|
|
usb_acpi_unregister_power_resources(dev);
|
2013-01-11 13:10:38 +01:00
|
|
|
kfree(port_dev);
|
|
|
|
}
|
|
|
|
|
2013-01-22 21:26:29 +01:00
|
|
|
#ifdef CONFIG_USB_SUSPEND
|
|
|
|
static int usb_port_runtime_resume(struct device *dev)
|
|
|
|
{
|
|
|
|
struct usb_port *port_dev = to_usb_port(dev);
|
|
|
|
struct usb_device *hdev = to_usb_device(dev->parent->parent);
|
|
|
|
struct usb_interface *intf = to_usb_interface(dev->parent);
|
2013-01-22 21:26:30 +01:00
|
|
|
struct usb_hub *hub = usb_hub_to_struct_hub(hdev);
|
|
|
|
int port1 = port_dev->portnum;
|
2013-01-22 21:26:29 +01:00
|
|
|
int retval;
|
|
|
|
|
2013-01-22 21:26:30 +01:00
|
|
|
if (!hub)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2013-01-22 21:26:29 +01:00
|
|
|
usb_autopm_get_interface(intf);
|
2013-01-22 21:26:30 +01:00
|
|
|
set_bit(port1, hub->busy_bits);
|
|
|
|
|
|
|
|
retval = usb_hub_set_port_power(hdev, port1, true);
|
|
|
|
if (port_dev->child && !retval) {
|
|
|
|
/*
|
|
|
|
* Wait for usb hub port to be reconnected in order to make
|
|
|
|
* the resume procedure successful.
|
|
|
|
*/
|
|
|
|
retval = hub_port_debounce_be_connected(hub, port1);
|
|
|
|
if (retval < 0) {
|
|
|
|
dev_dbg(&port_dev->dev, "can't get reconnection after setting port power on, status %d\n",
|
|
|
|
retval);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_ENABLE);
|
|
|
|
|
|
|
|
/* Set return value to 0 if debounce successful */
|
|
|
|
retval = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
|
|
|
clear_bit(port1, hub->busy_bits);
|
2013-01-22 21:26:29 +01:00
|
|
|
usb_autopm_put_interface(intf);
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int usb_port_runtime_suspend(struct device *dev)
|
|
|
|
{
|
|
|
|
struct usb_port *port_dev = to_usb_port(dev);
|
|
|
|
struct usb_device *hdev = to_usb_device(dev->parent->parent);
|
|
|
|
struct usb_interface *intf = to_usb_interface(dev->parent);
|
2013-01-22 21:26:30 +01:00
|
|
|
struct usb_hub *hub = usb_hub_to_struct_hub(hdev);
|
|
|
|
int port1 = port_dev->portnum;
|
2013-01-22 21:26:29 +01:00
|
|
|
int retval;
|
|
|
|
|
2013-01-22 21:26:30 +01:00
|
|
|
if (!hub)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2013-01-22 21:26:29 +01:00
|
|
|
if (dev_pm_qos_flags(&port_dev->dev, PM_QOS_FLAG_NO_POWER_OFF)
|
|
|
|
== PM_QOS_FLAGS_ALL)
|
|
|
|
return -EAGAIN;
|
|
|
|
|
|
|
|
usb_autopm_get_interface(intf);
|
2013-01-22 21:26:30 +01:00
|
|
|
set_bit(port1, hub->busy_bits);
|
|
|
|
retval = usb_hub_set_port_power(hdev, port1, false);
|
|
|
|
usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_CONNECTION);
|
|
|
|
usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_ENABLE);
|
|
|
|
clear_bit(port1, hub->busy_bits);
|
2013-01-22 21:26:29 +01:00
|
|
|
usb_autopm_put_interface(intf);
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static const struct dev_pm_ops usb_port_pm_ops = {
|
|
|
|
#ifdef CONFIG_USB_SUSPEND
|
|
|
|
.runtime_suspend = usb_port_runtime_suspend,
|
|
|
|
.runtime_resume = usb_port_runtime_resume,
|
|
|
|
.runtime_idle = pm_generic_runtime_idle,
|
|
|
|
#endif
|
|
|
|
};
|
|
|
|
|
2013-01-11 13:10:38 +01:00
|
|
|
struct device_type usb_port_device_type = {
|
|
|
|
.name = "usb_port",
|
|
|
|
.release = usb_port_device_release,
|
2013-01-22 21:26:29 +01:00
|
|
|
.pm = &usb_port_pm_ops,
|
2013-01-11 13:10:38 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
int usb_hub_create_port_device(struct usb_hub *hub, int port1)
|
|
|
|
{
|
|
|
|
struct usb_port *port_dev = NULL;
|
|
|
|
int retval;
|
|
|
|
|
|
|
|
port_dev = kzalloc(sizeof(*port_dev), GFP_KERNEL);
|
|
|
|
if (!port_dev) {
|
|
|
|
retval = -ENOMEM;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
hub->ports[port1 - 1] = port_dev;
|
2013-01-22 21:26:29 +01:00
|
|
|
port_dev->portnum = port1;
|
2013-01-22 21:26:30 +01:00
|
|
|
port_dev->power_is_on = true;
|
2013-01-11 13:10:38 +01:00
|
|
|
port_dev->dev.parent = hub->intfdev;
|
2013-01-19 18:53:32 +01:00
|
|
|
port_dev->dev.groups = port_dev_group;
|
2013-01-11 13:10:38 +01:00
|
|
|
port_dev->dev.type = &usb_port_device_type;
|
|
|
|
dev_set_name(&port_dev->dev, "port%d", port1);
|
|
|
|
|
|
|
|
retval = device_register(&port_dev->dev);
|
|
|
|
if (retval)
|
|
|
|
goto error_register;
|
|
|
|
|
2013-01-22 21:26:29 +01:00
|
|
|
pm_runtime_set_active(&port_dev->dev);
|
|
|
|
pm_runtime_enable(&port_dev->dev);
|
|
|
|
|
2013-01-22 21:26:27 +01:00
|
|
|
retval = usb_acpi_register_power_resources(&port_dev->dev);
|
|
|
|
if (retval && retval != -ENODEV)
|
|
|
|
dev_warn(&port_dev->dev, "the port can't register its ACPI power resource.\n");
|
|
|
|
|
2013-01-11 13:10:38 +01:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
error_register:
|
|
|
|
put_device(&port_dev->dev);
|
|
|
|
exit:
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
void usb_hub_remove_port_device(struct usb_hub *hub,
|
|
|
|
int port1)
|
|
|
|
{
|
|
|
|
device_unregister(&hub->ports[port1 - 1]->dev);
|
|
|
|
}
|
|
|
|
|