USB: add device-tree support for interfaces
authorJohan Hovold <johan@kernel.org>
Thu, 9 Nov 2017 17:07:21 +0000 (18:07 +0100)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Tue, 28 Nov 2017 14:12:38 +0000 (15:12 +0100)
Add OF device-tree support for USB interfaces.

USB "interface nodes" are children of USB "device nodes" and are
identified by an interface number and a configuration value:

&usb1 { /* host controller */
dev1: device@1 { /* device at port 1 */
compatible = "usb1234,5678";
reg = <1>;

#address-cells = <2>;
#size-cells = <0>;

interface@0,2 { /* interface 0 of configuration 2 */
compatible = "usbif1234,5678.config2.0";
reg = <0 2>;
};
};
};

The configuration component is not included in the textual
representation of an interface-node unit address for configuration 1:

&dev1 {
interface@0 { /* interface 0 of configuration 1 */
compatible = "usbif1234,5678.config1.0";
reg = <0 1>;
};
};

When a USB device of class 0 or 9 (hub) has only a single configuration
with a single interface, a special case "combined node" is used instead
of a device node with an interface node:

&usb1 {
device@2 {
compatible = "usb1234,abcd";
reg = <2>;
};
};

Combined nodes are shared by the two device structures representing the
USB device and its interface in the kernel's device model.

Note that, as for device nodes, the compatible strings for interface
nodes are currently not used.

For more details see "Open Firmware Recommended Practice: Universal
Serial Bus Version 1" and the binding documentation.

Signed-off-by: Johan Hovold <johan@kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/core/message.c
drivers/usb/core/of.c
include/linux/usb/of.h

index 5a8ab77bc3675e4d116ccde6002338cb8dc08132..f836bae1e4859fa8e0b18ea393e7526ca9ae9b81 100644 (file)
@@ -18,6 +18,7 @@
 #include <linux/usb/cdc.h>
 #include <linux/usb/quirks.h>
 #include <linux/usb/hcd.h>     /* for usbcore internals */
+#include <linux/usb/of.h>
 #include <asm/byteorder.h>
 
 #include "usb.h"
@@ -1583,6 +1584,7 @@ static void usb_release_interface(struct device *dev)
 
        kref_put(&intfc->ref, usb_release_interface_cache);
        usb_put_dev(interface_to_usbdev(intf));
+       of_node_put(dev->of_node);
        kfree(intf);
 }
 
@@ -1868,6 +1870,7 @@ free_interfaces:
                struct usb_interface_cache *intfc;
                struct usb_interface *intf;
                struct usb_host_interface *alt;
+               u8 ifnum;
 
                cp->interface[i] = intf = new_interfaces[i];
                intfc = cp->intf_cache[i];
@@ -1886,11 +1889,17 @@ free_interfaces:
                if (!alt)
                        alt = &intf->altsetting[0];
 
-               intf->intf_assoc =
-                       find_iad(dev, cp, alt->desc.bInterfaceNumber);
+               ifnum = alt->desc.bInterfaceNumber;
+               intf->intf_assoc = find_iad(dev, cp, ifnum);
                intf->cur_altsetting = alt;
                usb_enable_interface(dev, intf, true);
                intf->dev.parent = &dev->dev;
+               if (usb_of_has_combined_node(dev)) {
+                       device_set_of_node_from_dev(&intf->dev, &dev->dev);
+               } else {
+                       intf->dev.of_node = usb_of_get_interface_node(dev,
+                                       configuration, ifnum);
+               }
                intf->dev.driver = NULL;
                intf->dev.bus = &usb_bus_type;
                intf->dev.type = &usb_if_device_type;
@@ -1905,9 +1914,8 @@ free_interfaces:
                intf->minor = -1;
                device_initialize(&intf->dev);
                pm_runtime_no_callbacks(&intf->dev);
-               dev_set_name(&intf->dev, "%d-%s:%d.%d",
-                       dev->bus->busnum, dev->devpath,
-                       configuration, alt->desc.bInterfaceNumber);
+               dev_set_name(&intf->dev, "%d-%s:%d.%d", dev->bus->busnum,
+                               dev->devpath, configuration, ifnum);
                usb_get_dev(dev);
        }
        kfree(new_interfaces);
index 2be9683532573cb546432b651b6212565e8c66a1..074fabc26d6ce3bd3d6946adbed8ffe3ebd91606 100644 (file)
@@ -3,7 +3,8 @@
  * of.c                The helpers for hcd device tree support
  *
  * Copyright (C) 2016 Freescale Semiconductor, Inc.
- * Author: Peter Chen <peter.chen@freescale.com>
+ *     Author: Peter Chen <peter.chen@freescale.com>
+ * Copyright (C) 2017 Johan Hovold <johan@kernel.org>
  */
 
 #include <linux/of.h>
@@ -37,6 +38,73 @@ struct device_node *usb_of_get_child_node(struct device_node *parent,
 }
 EXPORT_SYMBOL_GPL(usb_of_get_child_node);
 
+/**
+ * usb_of_has_combined_node() - determine whether a device has a combined node
+ * @udev: USB device
+ *
+ * Determine whether a USB device has a so called combined node which is
+ * shared with its sole interface. This is the case if and only if the device
+ * has a node and its decriptors report the following:
+ *
+ *     1) bDeviceClass is 0 or 9, and
+ *     2) bNumConfigurations is 1, and
+ *     3) bNumInterfaces is 1.
+ *
+ * Return: True iff the device has a device node and its descriptors match the
+ * criteria for a combined node.
+ */
+bool usb_of_has_combined_node(struct usb_device *udev)
+{
+       struct usb_device_descriptor *ddesc = &udev->descriptor;
+       struct usb_config_descriptor *cdesc;
+
+       if (!udev->dev.of_node)
+               return false;
+
+       switch (ddesc->bDeviceClass) {
+       case USB_CLASS_PER_INTERFACE:
+       case USB_CLASS_HUB:
+               if (ddesc->bNumConfigurations == 1) {
+                       cdesc = &udev->config->desc;
+                       if (cdesc->bNumInterfaces == 1)
+                               return true;
+               }
+       }
+
+       return false;
+}
+EXPORT_SYMBOL_GPL(usb_of_has_combined_node);
+
+/**
+ * usb_of_get_interface_node() - get a USB interface node
+ * @udev: USB device of interface
+ * @config: configuration value
+ * @ifnum: interface number
+ *
+ * Look up the node of a USB interface given its USB device, configuration
+ * value and interface number.
+ *
+ * Return: A pointer to the node with incremented refcount if found, or
+ * %NULL otherwise.
+ */
+struct device_node *
+usb_of_get_interface_node(struct usb_device *udev, u8 config, u8 ifnum)
+{
+       struct device_node *node;
+       u32 reg[2];
+
+       for_each_child_of_node(udev->dev.of_node, node) {
+               if (of_property_read_u32_array(node, "reg", reg, 2))
+                       continue;
+
+               if (reg[0] == ifnum && reg[1] == config)
+                       return node;
+       }
+
+       return NULL;
+}
+EXPORT_SYMBOL_GPL(usb_of_get_interface_node);
+
 /**
  * usb_of_get_companion_dev - Find the companion device
  * @dev: the device pointer to find a companion
index 6cbe7a5c2b578e5a44323674b968cef2e3e0e888..0294ccac4f1d62842a2727883946991cd0a3a46f 100644 (file)
@@ -12,6 +12,8 @@
 #include <linux/usb/otg.h>
 #include <linux/usb/phy.h>
 
+struct usb_device;
+
 #if IS_ENABLED(CONFIG_OF)
 enum usb_dr_mode of_usb_get_dr_mode_by_phy(struct device_node *np, int arg0);
 bool of_usb_host_tpl_support(struct device_node *np);
@@ -19,6 +21,9 @@ int of_usb_update_otg_caps(struct device_node *np,
                        struct usb_otg_caps *otg_caps);
 struct device_node *usb_of_get_child_node(struct device_node *parent,
                        int portnum);
+bool usb_of_has_combined_node(struct usb_device *udev);
+struct device_node *usb_of_get_interface_node(struct usb_device *udev,
+               u8 config, u8 ifnum);
 struct device *usb_of_get_companion_dev(struct device *dev);
 #else
 static inline enum usb_dr_mode
@@ -40,6 +45,15 @@ static inline struct device_node *usb_of_get_child_node
 {
        return NULL;
 }
+static inline bool usb_of_has_combined_node(struct usb_device *udev)
+{
+       return false;
+}
+static inline struct device_node *
+usb_of_get_interface_node(struct usb_device *udev, u8 config, u8 ifnum)
+{
+       return NULL;
+}
 static inline struct device *usb_of_get_companion_dev(struct device *dev)
 {
        return NULL;