From patchwork Mon Sep 28 17:13:54 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Matthias Kaehlcke X-Patchwork-Id: 11804311 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 0AE55618 for ; Mon, 28 Sep 2020 17:14:04 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id E2A4620774 for ; Mon, 28 Sep 2020 17:14:03 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (1024-bit key) header.d=chromium.org header.i=@chromium.org header.b="fUmFEKMP" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726670AbgI1ROA (ORCPT ); Mon, 28 Sep 2020 13:14:00 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37338 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726661AbgI1RN7 (ORCPT ); Mon, 28 Sep 2020 13:13:59 -0400 Received: from mail-pj1-x1041.google.com (mail-pj1-x1041.google.com [IPv6:2607:f8b0:4864:20::1041]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 9498FC0613CE for ; Mon, 28 Sep 2020 10:13:59 -0700 (PDT) Received: by mail-pj1-x1041.google.com with SMTP id b17so1017830pji.1 for ; Mon, 28 Sep 2020 10:13:59 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; h=from:to:cc:subject:date:message-id:mime-version :content-transfer-encoding; bh=6LRa3EvjxYw5aX67BhkFrS+Y1RXh3J3rTX/vMwJ4fBY=; b=fUmFEKMPipnra4fmuQoqW2QD7diKt59CdvKRgJUQsIz3kme6UzweIUnkraR8O8Dxe+ Lnj32eVJfWh211rHwYWM214y6149q4KKfvonKPYpPw0+E2VmREcBHn9tBfuDTm6HxMxk DnPAvbL8Hr7mCPlxxhDX5GFz9Ta8qYSDLN2lQ= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:mime-version :content-transfer-encoding; bh=6LRa3EvjxYw5aX67BhkFrS+Y1RXh3J3rTX/vMwJ4fBY=; b=MbICtrVDLzvj8cWV9X9HjdcF8i6DOYw+NjpuVr6Vh3UohRsTn0BYZJJhX1ZHBXnank bVk3HB9NmB7wWziMbC0UMTgsKWR2JHPoo0/cZlPyoGyh7QcQq0EZiYOPh0wSTLHiciIh sEFe2mBh8iZNry/a4OUwOl/iyRp2j610y9NoOZHTP7uSnbJSPLRuyVUhHxGQS2iBDe/s Nj2sZ1bOkqInqlyapkgOQ9doKNiFPHUITMuDzRjH1O3ZpXDDuTgtNcrugb7SSn8R3U1k roC7L6rAFSazw72iobB6haVb7OrUM0kLN5s/LXQU7hn9nARTaaKp9hWsGRoT9j/sLpON UnnQ== X-Gm-Message-State: AOAM533CBzFyQom0F7K3rx1QLfuNx+j1nA4s0o9Ll6G21bYSGt6jZEOx lXBIrfMRbTihhHRmUGJENvbfeQ== X-Google-Smtp-Source: ABdhPJyy/zNJpHFnMQFB7JuPIm4GDw3cJVD1GeygsRruKUtVR+7mZzv+HgAc6/mIiMlJZgHS/5Q9Wg== X-Received: by 2002:a17:90a:ca17:: with SMTP id x23mr263620pjt.96.1601313239051; Mon, 28 Sep 2020 10:13:59 -0700 (PDT) Received: from localhost ([2620:15c:202:1:f693:9fff:fef4:e70a]) by smtp.gmail.com with ESMTPSA id gq14sm1869082pjb.44.2020.09.28.10.13.58 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Mon, 28 Sep 2020 10:13:58 -0700 (PDT) From: Matthias Kaehlcke To: Greg Kroah-Hartman , Rob Herring , Frank Rowand Cc: linux-kernel@vger.kernel.org, linux-usb@vger.kernel.org, Bastien Nocera , Stephen Boyd , Douglas Anderson , Alan Stern , Ravi Chandra Sadineni , Krzysztof Kozlowski , devicetree@vger.kernel.org, Peter Chen , Matthias Kaehlcke Subject: [PATCH v4 1/2] dt-bindings: usb: Add binding for discrete onboard USB hubs Date: Mon, 28 Sep 2020 10:13:54 -0700 Message-Id: <20200928101326.v4.1.I248292623d3d0f6a4f0c5bc58478ca3c0062b49a@changeid> X-Mailer: git-send-email 2.28.0.709.gb0816b6eb0-goog MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-usb@vger.kernel.org Discrete onboard USB hubs (an example for such a hub is the Realtek RTS5411) need to be powered and may require initialization of other resources (like GPIOs or clocks) to work properly. This adds a device tree binding for these hubs. Signed-off-by: Matthias Kaehlcke --- (no changes since v3) Changes in v3: - updated commit message - removed recursive reference to $self - adjusted 'compatible' definition to support multiple entries - changed USB controller phandle to be a node Changes in v2: - removed 'wakeup-source' and 'power-off-in-suspend' properties - consistently use spaces for indentation in example .../bindings/usb/onboard_usb_hub.yaml | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 Documentation/devicetree/bindings/usb/onboard_usb_hub.yaml diff --git a/Documentation/devicetree/bindings/usb/onboard_usb_hub.yaml b/Documentation/devicetree/bindings/usb/onboard_usb_hub.yaml new file mode 100644 index 000000000000..c9783da3e75c --- /dev/null +++ b/Documentation/devicetree/bindings/usb/onboard_usb_hub.yaml @@ -0,0 +1,54 @@ +# SPDX-License-Identifier: GPL-2.0-only or BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/onboard_usb_hub.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Binding for onboard USB hubs + +maintainers: + - Matthias Kaehlcke + +properties: + compatible: + items: + - enum: + - realtek,rts5411 + - const: onboard-usb-hub + + vdd-supply: + description: + phandle to the regulator that provides power to the hub. + +required: + - compatible + - vdd-supply + +examples: + - | + usb_hub: usb-hub { + compatible = "realtek,rts5411", "onboard-usb-hub"; + vdd-supply = <&pp3300_hub>; + }; + + usb_controller { + dr_mode = "host"; + #address-cells = <1>; + #size-cells = <0>; + + /* 2.0 hub on port 1 */ + hub@1 { + compatible = "usbbda,5411"; + reg = <1>; + hub = <&usb_hub>; + }; + + /* 3.0 hub on port 2 */ + hub@2 { + compatible = "usbbda,411"; + reg = <2>; + hub = <&usb_hub>; + }; + }; + +... From patchwork Mon Sep 28 17:13:55 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Matthias Kaehlcke X-Patchwork-Id: 11804313 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 9E2E8618 for ; Mon, 28 Sep 2020 17:14:09 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 7495B2100A for ; Mon, 28 Sep 2020 17:14:09 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=pass (1024-bit key) header.d=chromium.org header.i=@chromium.org header.b="Fz5jouqA" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726691AbgI1ROF (ORCPT ); Mon, 28 Sep 2020 13:14:05 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37356 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726666AbgI1ROE (ORCPT ); Mon, 28 Sep 2020 13:14:04 -0400 Received: from mail-pf1-x441.google.com (mail-pf1-x441.google.com [IPv6:2607:f8b0:4864:20::441]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 5F051C0613CE for ; Mon, 28 Sep 2020 10:14:04 -0700 (PDT) Received: by mail-pf1-x441.google.com with SMTP id k8so1641858pfk.2 for ; Mon, 28 Sep 2020 10:14:04 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=W8Mrz0VTtam4k9EbDnGZRSgqObwcyTLH3/J7dD64Vgs=; b=Fz5jouqAAhdJbJPD1kRPdhSHIJofwccfCmjqfdybt0lhTyG5brv56COJoDKuN3n8oK 98XSmVlCmafWNIxMFa1Jzs5WyNY28H7ykQb85vWFg/DhK6QARXU9l/o7VRC5TtbRsZRH CjTT2rlNLRhExUDoTBb/XLKJvLetHG+tfieyg= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=W8Mrz0VTtam4k9EbDnGZRSgqObwcyTLH3/J7dD64Vgs=; b=YWiu60YLhFzvDv6BuyFpbpLFvFqe+z9QhsKhp0SWGJWqSvODkWpQGjWu3s348FxviJ M5FvgWmX9evFD35tr4OY59mVdTgJAt2mdpZDuJfguNlU9wO5I5E5deJgFurHzAKgSe5p ClZk/SkdugyYHD521xtWtb7gWTdGXAJ0LyKrprslo9JBEvlMzibB/FtcGDu4QbmACPYc KYQC+/7yuKpwtaDjJpvAvgE85QhHrX6d3Vr3R/nNuKv10kAsyaF02O4A5J8/xRmXZdwT +B6nmZz4NkTfCF2tenv66j+lPP0k5nQUvK+dlnhXrccrmbypaH7ga5uKS4PdAMNLWExg C4VA== X-Gm-Message-State: AOAM531hspFaquDdGgKef8TCu7X/Z5N53PJHT3xv6CWwyGTCuo9KjcnU ki6LlP5agMbC6KspTXbaKeozgA== X-Google-Smtp-Source: ABdhPJx41eG4F6mpyiywKkL4gfeQHFZKyAqXMkXjrVBIrU9rYI1+EvlvBQle/1ZlRLCL8h7dylaZ9w== X-Received: by 2002:a63:2bd1:: with SMTP id r200mr110395pgr.20.1601313243761; Mon, 28 Sep 2020 10:14:03 -0700 (PDT) Received: from localhost ([2620:15c:202:1:f693:9fff:fef4:e70a]) by smtp.gmail.com with ESMTPSA id d128sm2363412pfc.8.2020.09.28.10.14.02 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Mon, 28 Sep 2020 10:14:03 -0700 (PDT) From: Matthias Kaehlcke To: Greg Kroah-Hartman , Rob Herring , Frank Rowand Cc: linux-kernel@vger.kernel.org, linux-usb@vger.kernel.org, Bastien Nocera , Stephen Boyd , Douglas Anderson , Alan Stern , Ravi Chandra Sadineni , Krzysztof Kozlowski , devicetree@vger.kernel.org, Peter Chen , Matthias Kaehlcke , "Alexander A. Klimov" , "David S. Miller" , Johan Hovold , Masahiro Yamada , Mauro Carvalho Chehab , Rob Herring Subject: [PATCH v4 2/2] USB: misc: Add onboard_usb_hub driver Date: Mon, 28 Sep 2020 10:13:55 -0700 Message-Id: <20200928101326.v4.2.I7c9a1f1d6ced41dd8310e8a03da666a32364e790@changeid> X-Mailer: git-send-email 2.28.0.709.gb0816b6eb0-goog In-Reply-To: <20200928101326.v4.1.I248292623d3d0f6a4f0c5bc58478ca3c0062b49a@changeid> References: <20200928101326.v4.1.I248292623d3d0f6a4f0c5bc58478ca3c0062b49a@changeid> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-usb@vger.kernel.org The main issue this driver addresses is that a USB hub needs to be powered before it can be discovered. For discrete onboard hubs (an example for such a hub is the Realtek RTS5411) this is often solved by supplying the hub with an 'always-on' regulator, which is kind of a hack. Some onboard hubs may require further initialization steps, like changing the state of a GPIO or enabling a clock, which requires even more hacks. This driver creates a platform device representing the hub which performs the necessary initialization. Currently it only supports switching on a single regulator, support for multiple regulators or other actions can be added as needed. Different initialization sequences can be supported based on the compatible string. Besides performing the initialization the driver can be configured to power the hub off during system suspend. This can help to extend battery life on battery powered devices which have no requirements to keep the hub powered during suspend. The driver can also be configured to leave the hub powered when a wakeup capable USB device is connected when suspending, and power it off otherwise. Technically the driver consists of two drivers, the platform driver described above and a very thin USB driver that subclasses the generic driver. The purpose of this driver is to provide the platform driver with the USB devices corresponding to the hub(s) (a hub controller may provide multiple 'logical' hubs, e.g. one to support USB 2.0 and another for USB 3.x). Co-developed-by: Ravi Chandra Sadineni Signed-off-by: Ravi Chandra Sadineni Signed-off-by: Matthias Kaehlcke Acked-by: Alan Stern Reviewed-by: Douglas Anderson --- Changes in v4: - updated Kconfig documentation - changed the loop in onboard_hub_remove() to release the hub lock before unbinding the USB device and make self deadlock prevention less clunky - fixed return value in onboard_hub_usbdev_probe() - added entry to MAINTAINERS file Changes in v3: - updated the commit message - updated description in Kconfig - remove include of 'core/usb.h' - use 'is_powered_on' flag instead of 'has_wakeup_capable_descendants' - added 'going_away' flag to struct onboard_hub - don't allow adding new USB devices when the platform device is going away - don't bother with deleting the list item in onboard_hub_remove_usbdev() when the platform device is going away - don't assume in onboard_hub_suspend() that all USB hub devices are connected to the same controller - removed unnecessary devm_kfree() from onboard_hub_remove_usbdev() - fixed error handling in onboard_hub_remove_usbdev() - use kstrtobool() instead of strtobool() in power_off_in_suspend_store() - unbind USB devices in onboard_hub_remove() to avoid dangling references to the platform device - moved put_device() for platform device to _find_onboard_hub() - changed return value of onboard_hub_remove_usbdev() to void - evaluate return value of onboard_hub_add_usbdev() - register 'power_off_in_suspend' as managed device attribute - use USB_DEVICE macro instead manual initialization - add unwinding to onboard_hub_init() - updated MODULE_DESCRIPTION - use module_init() instead of device_initcall() Changes in v2: - check wakeup enabled state of the USB controller instead of using 'wakeup-source' property - use sysfs attribute instead of DT property to determine if the hub should be powered off at all during system suspend - added missing brace in onboard_hub_suspend() - updated commit message - use pm_ptr for pm_ops as suggested by Alan Changes in v1: - renamed the driver to 'onboard_usb_hub' - single file for platform and USB driver - USB hub devices register with the platform device - the DT includes a phandle of the platform device - the platform device now controls when power is turned off - the USB driver became a very thin subclass of the generic USB driver - enabled autosuspend support MAINTAINERS | 7 + drivers/usb/misc/Kconfig | 17 ++ drivers/usb/misc/Makefile | 1 + drivers/usb/misc/onboard_usb_hub.c | 371 +++++++++++++++++++++++++++++ 4 files changed, 396 insertions(+) create mode 100644 drivers/usb/misc/onboard_usb_hub.c diff --git a/MAINTAINERS b/MAINTAINERS index 0d0862b19ce5..fa7502ed6771 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12859,6 +12859,13 @@ T: git git://linuxtv.org/media_tree.git F: Documentation/devicetree/bindings/media/i2c/ov9650.txt F: drivers/media/i2c/ov9650.c +ONBOARD USB HUB DRIVER +M: Matthias Kaehlcke +L: linux-usb@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/usb/onboard_usb_hub.yaml +F: drivers/usb/misc/onboard_usb_hub.c + ONENAND FLASH DRIVER M: Kyungmin Park L: linux-mtd@lists.infradead.org diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig index 6818ea689cd9..09e6ca1a004c 100644 --- a/drivers/usb/misc/Kconfig +++ b/drivers/usb/misc/Kconfig @@ -275,3 +275,20 @@ config USB_CHAOSKEY To compile this driver as a module, choose M here: the module will be called chaoskey. + +config USB_ONBOARD_HUB + tristate "Onboard USB hub support" + depends on OF || COMPILE_TEST + help + Say Y here if you want to support discrete onboard USB hubs that + don't require an additional control bus for initialization, but + need some nontrivial form of initialization, such as enabling a + power regulator. An example for such a hub is the Realtek + RTS5411. + + The driver can be configured to turn off the power of the hub + during system suspend. This may reduce power consumption while + the system is suspended. + + To compile this driver as a module, choose M here: the + module will be called onboard_usb_hub. diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile index da39bddb0604..6f10a1c6f7e9 100644 --- a/drivers/usb/misc/Makefile +++ b/drivers/usb/misc/Makefile @@ -31,3 +31,4 @@ obj-$(CONFIG_USB_CHAOSKEY) += chaoskey.o obj-$(CONFIG_USB_SISUSBVGA) += sisusbvga/ obj-$(CONFIG_USB_LINK_LAYER_TEST) += lvstest.o +obj-$(CONFIG_USB_ONBOARD_HUB) += onboard_usb_hub.o diff --git a/drivers/usb/misc/onboard_usb_hub.c b/drivers/usb/misc/onboard_usb_hub.c new file mode 100644 index 000000000000..eb3ba7512c69 --- /dev/null +++ b/drivers/usb/misc/onboard_usb_hub.c @@ -0,0 +1,371 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for onboard USB hubs + * + * Copyright (c) 2020, Google LLC + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/************************** Platform driver **************************/ + +struct udev_node { + struct usb_device *udev; + struct list_head list; +}; + +struct onboard_hub { + struct regulator *vdd; + struct device *dev; + bool power_off_in_suspend; + bool is_powered_on; + bool going_away; + struct list_head udev_list; + struct mutex lock; +}; + +static int onboard_hub_power_on(struct onboard_hub *hub) +{ + int err; + + err = regulator_enable(hub->vdd); + if (err) { + dev_err(hub->dev, "failed to enable regulator: %d\n", err); + return err; + } + + hub->is_powered_on = true; + + return 0; +} + +static int onboard_hub_power_off(struct onboard_hub *hub) +{ + int err; + + err = regulator_disable(hub->vdd); + if (err) { + dev_err(hub->dev, "failed to enable regulator: %d\n", err); + return err; + } + + hub->is_powered_on = false; + + return 0; +} + +static int __maybe_unused onboard_hub_suspend(struct device *dev) +{ + struct onboard_hub *hub = dev_get_drvdata(dev); + struct udev_node *node; + bool power_off; + int rc = 0; + + if (!hub->power_off_in_suspend) + return 0; + + power_off = true; + + mutex_lock(&hub->lock); + + list_for_each_entry(node, &hub->udev_list, list) { + if (!device_may_wakeup(node->udev->bus->controller)) + continue; + + if (usb_wakeup_enabled_descendants(node->udev)) { + power_off = false; + break; + } + } + + mutex_unlock(&hub->lock); + + if (power_off) + rc = onboard_hub_power_off(hub); + + return rc; +} + +static int __maybe_unused onboard_hub_resume(struct device *dev) +{ + struct onboard_hub *hub = dev_get_drvdata(dev); + int rc = 0; + + if (!hub->is_powered_on) + rc = onboard_hub_power_on(hub); + + return rc; +} + +static int onboard_hub_add_usbdev(struct onboard_hub *hub, struct usb_device *udev) +{ + struct udev_node *node; + int ret = 0; + + mutex_lock(&hub->lock); + + if (hub->going_away) { + ret = -EINVAL; + goto unlock; + } + + node = devm_kzalloc(hub->dev, sizeof(*node), GFP_KERNEL); + if (!node) { + ret = -ENOMEM; + goto unlock; + } + + node->udev = udev; + + list_add(&node->list, &hub->udev_list); + +unlock: + mutex_unlock(&hub->lock); + + return ret; +} + +static void onboard_hub_remove_usbdev(struct onboard_hub *hub, struct usb_device *udev) +{ + struct udev_node *node; + + mutex_lock(&hub->lock); + + list_for_each_entry(node, &hub->udev_list, list) { + if (node->udev == udev) { + list_del(&node->list); + break; + } + } + + mutex_unlock(&hub->lock); +} + +static ssize_t power_off_in_suspend_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct onboard_hub *hub = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", hub->power_off_in_suspend); +} + +static ssize_t power_off_in_suspend_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct onboard_hub *hub = dev_get_drvdata(dev); + bool val; + int ret; + + ret = kstrtobool(buf, &val); + if (ret < 0) + return ret; + + hub->power_off_in_suspend = val; + + return count; +} +static DEVICE_ATTR_RW(power_off_in_suspend); + +static struct attribute *onboard_hub_sysfs_entries[] = { + &dev_attr_power_off_in_suspend.attr, + NULL, +}; + +static const struct attribute_group onboard_hub_sysfs_group = { + .attrs = onboard_hub_sysfs_entries, +}; + +static int onboard_hub_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct onboard_hub *hub; + int err; + + hub = devm_kzalloc(dev, sizeof(*hub), GFP_KERNEL); + if (!hub) + return -ENOMEM; + + hub->vdd = devm_regulator_get(dev, "vdd"); + if (IS_ERR(hub->vdd)) + return PTR_ERR(hub->vdd); + + hub->dev = dev; + mutex_init(&hub->lock); + INIT_LIST_HEAD(&hub->udev_list); + + dev_set_drvdata(dev, hub); + + err = devm_device_add_group(dev, &onboard_hub_sysfs_group); + if (err) { + dev_err(dev, "failed to create sysfs entries: %d\n", err); + return err; + } + + return onboard_hub_power_on(hub); +} + +static int onboard_hub_remove(struct platform_device *pdev) +{ + struct onboard_hub *hub = dev_get_drvdata(&pdev->dev); + struct udev_node *node; + struct usb_device *udev; + + hub->going_away = true; + + mutex_lock(&hub->lock); + + /* unbind the USB devices to avoid dangling references to this device */ + while (!list_empty(&hub->udev_list)) { + node = list_first_entry(&hub->udev_list, struct udev_node, list); + udev = node->udev; + + /* + * Unbinding the driver will call onboard_hub_remove_usbdev(), + * which acquires hub->lock. We must release the lock first. + */ + get_device(&udev->dev); + mutex_unlock(&hub->lock); + device_release_driver(&udev->dev); + put_device(&udev->dev); + mutex_lock(&hub->lock); + } + + mutex_unlock(&hub->lock); + + return onboard_hub_power_off(hub); +} + +static const struct of_device_id onboard_hub_match[] = { + { .compatible = "onboard-usb-hub" }, + { .compatible = "realtek,rts5411" }, + {} +}; +MODULE_DEVICE_TABLE(of, onboard_hub_match); + +static SIMPLE_DEV_PM_OPS(onboard_hub_pm_ops, onboard_hub_suspend, onboard_hub_resume); + +static struct platform_driver onboard_hub_driver = { + .probe = onboard_hub_probe, + .remove = onboard_hub_remove, + + .driver = { + .name = "onboard-usb-hub", + .of_match_table = onboard_hub_match, + .pm = pm_ptr(&onboard_hub_pm_ops), + }, +}; + +/************************** USB driver **************************/ + +#define VENDOR_ID_REALTEK 0x0bda + +static struct onboard_hub *_find_onboard_hub(struct device *dev) +{ + const phandle *ph; + struct device_node *np; + struct platform_device *pdev; + + ph = of_get_property(dev->of_node, "hub", NULL); + if (!ph) { + dev_err(dev, "failed to read 'hub' property\n"); + return ERR_PTR(-EINVAL); + } + + np = of_find_node_by_phandle(be32_to_cpu(*ph)); + if (!np) { + dev_err(dev, "failed find device node for onboard hub\n"); + return ERR_PTR(-EINVAL); + } + + pdev = of_find_device_by_node(np); + of_node_put(np); + if (!pdev) + return ERR_PTR(-EPROBE_DEFER); + + put_device(&pdev->dev); + + return dev_get_drvdata(&pdev->dev); +} + +static int onboard_hub_usbdev_probe(struct usb_device *udev) +{ + struct device *dev = &udev->dev; + struct onboard_hub *hub; + + /* ignore supported hubs without device tree node */ + if (!dev->of_node) + return -ENODEV; + + hub = _find_onboard_hub(dev); + if (IS_ERR(hub)) + return PTR_ERR(hub); + + dev_set_drvdata(dev, hub); + + return onboard_hub_add_usbdev(hub, udev); +} + +static void onboard_hub_usbdev_disconnect(struct usb_device *udev) +{ + struct onboard_hub *hub = dev_get_drvdata(&udev->dev); + + onboard_hub_remove_usbdev(hub, udev); +} + +static const struct usb_device_id onboard_hub_id_table[] = { + { USB_DEVICE(VENDOR_ID_REALTEK, 0x0411) }, /* RTS5411 USB 3.0 */ + { USB_DEVICE(VENDOR_ID_REALTEK, 0x5411) }, /* RTS5411 USB 2.0 */ + {}, +}; + +MODULE_DEVICE_TABLE(usb, onboard_hub_id_table); + +static struct usb_device_driver onboard_hub_usbdev_driver = { + + .name = "onboard-usb-hub", + .probe = onboard_hub_usbdev_probe, + .disconnect = onboard_hub_usbdev_disconnect, + .generic_subclass = 1, + .supports_autosuspend = 1, + .id_table = onboard_hub_id_table, +}; + +/************************** Driver (de)registration **************************/ + +static int __init onboard_hub_init(void) +{ + int ret; + + ret = platform_driver_register(&onboard_hub_driver); + if (ret) + return ret; + + ret = usb_register_device_driver(&onboard_hub_usbdev_driver, THIS_MODULE); + if (ret) + platform_driver_unregister(&onboard_hub_driver); + + return ret; +} +module_init(onboard_hub_init); + +static void __exit onboard_hub_exit(void) +{ + usb_deregister_device_driver(&onboard_hub_usbdev_driver); + platform_driver_unregister(&onboard_hub_driver); +} +module_exit(onboard_hub_exit); + +MODULE_AUTHOR("Matthias Kaehlcke "); +MODULE_DESCRIPTION("Driver for discrete onboard USB hubs"); +MODULE_LICENSE("GPL v2");