From patchwork Tue Apr 4 07:46:28 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roger Quadros X-Patchwork-Id: 96698 Delivered-To: patch@linaro.org Received: by 10.140.89.233 with SMTP id v96csp71656qgd; Tue, 4 Apr 2017 00:47:16 -0700 (PDT) X-Received: by 10.99.119.67 with SMTP id s64mr1890074pgc.211.1491292036642; Tue, 04 Apr 2017 00:47:16 -0700 (PDT) Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id f16si16660055pli.143.2017.04.04.00.47.16; Tue, 04 Apr 2017 00:47:16 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=pass header.i=@ti.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=ti.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752813AbdDDHrO (ORCPT + 25 others); Tue, 4 Apr 2017 03:47:14 -0400 Received: from fllnx209.ext.ti.com ([198.47.19.16]:61148 "EHLO fllnx209.ext.ti.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751101AbdDDHrL (ORCPT ); Tue, 4 Apr 2017 03:47:11 -0400 Received: from dlelxv90.itg.ti.com ([172.17.2.17]) by fllnx209.ext.ti.com (8.15.1/8.15.1) with ESMTP id v347kZL2017722; Tue, 4 Apr 2017 02:46:35 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ti.com; s=ti-com-17Q1; t=1491291995; bh=XgYLCPimreDtvrYt2dOqkDqIcV1aDnnemZzgiFOcMaQ=; h=Subject:To:References:CC:From:Date:In-Reply-To; b=EWWknYbM2ZYxlUe4MHNkwOMWBY6cPlB0e+v1gqLOsaz+xU0e3g0Z6o1xpJqkkxD1f 2mDbZjuh/veGO6Babbl9/rTvohG649RWXW+1Zq/OQIfkdoUWjG/zxo39dTWnn/RN+L 1K6C1/hKb4Gy4jr286rgvt7SVlydXopjQ4LC/D+E= Received: from DLEE71.ent.ti.com (dlee71.ent.ti.com [157.170.170.114]) by dlelxv90.itg.ti.com (8.14.3/8.13.8) with ESMTP id v347kUZn004028; Tue, 4 Apr 2017 02:46:30 -0500 Received: from dlep33.itg.ti.com (157.170.170.75) by DLEE71.ent.ti.com (157.170.170.114) with Microsoft SMTP Server id 14.3.294.0; Tue, 4 Apr 2017 02:46:30 -0500 Received: from [192.168.2.6] (ileax41-snat.itg.ti.com [10.172.224.153]) by dlep33.itg.ti.com (8.14.3/8.13.8) with ESMTP id v347kSK5013858; Tue, 4 Apr 2017 02:46:29 -0500 Subject: [PATCH v4 3/3] usb: dwc3: Add dual-role support To: References: <1491222031-18120-1-git-send-email-rogerq@ti.com> <1491222031-18120-4-git-send-email-rogerq@ti.com> CC: , , , From: Roger Quadros Message-ID: <35e60bc4-0f74-2bbe-577d-abc728ff0f95@ti.com> Date: Tue, 4 Apr 2017 10:46:28 +0300 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Thunderbird/45.8.0 MIME-Version: 1.0 In-Reply-To: <1491222031-18120-4-git-send-email-rogerq@ti.com> Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org If dr_mode is "otg" then support dual role mode of operation. Currently this mode is only supported when an extcon handle is present in the dwc3 device tree node. This is needed to get the ID status events of the port. We're using a workqueue to manage the dual-role state transitions as the extcon notifier (dwc3_drd_notifier) is called in an atomic context by extcon_sync() and this doesn't go well with usb_del_gadget_udc() causing a lockdep and softirq warning. Signed-off-by: Roger Quadros --- v4: -fix build error if CONFIG_USB_DWC3_DUAL_ROLE is not set drivers/usb/dwc3/Makefile | 4 ++ drivers/usb/dwc3/core.c | 15 +---- drivers/usb/dwc3/core.h | 19 ++++++ drivers/usb/dwc3/drd.c | 167 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 193 insertions(+), 12 deletions(-) create mode 100644 drivers/usb/dwc3/drd.c -- 2.7.4 diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile index ffca340..f15fabb 100644 --- a/drivers/usb/dwc3/Makefile +++ b/drivers/usb/dwc3/Makefile @@ -17,6 +17,10 @@ ifneq ($(filter y,$(CONFIG_USB_DWC3_GADGET) $(CONFIG_USB_DWC3_DUAL_ROLE)),) dwc3-y += gadget.o ep0.o endif +ifneq ($(CONFIG_USB_DWC3_DUAL_ROLE),) + dwc3-y += drd.o +endif + ifneq ($(CONFIG_USB_DWC3_ULPI),) dwc3-y += ulpi.o endif diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index bf917c2..15c05a1 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -864,12 +864,10 @@ static int dwc3_core_init_mode(struct dwc3 *dwc) } break; case USB_DR_MODE_OTG: - /* start in peripheral role by default */ - dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE); - ret = dwc3_gadget_init(dwc); + ret = dwc3_drd_init(dwc); if (ret) { if (ret != -EPROBE_DEFER) - dev_err(dev, "failed to initialize gadget\n"); + dev_err(dev, "failed to initialize dual-role\n"); return ret; } break; @@ -891,14 +889,7 @@ static void dwc3_core_exit_mode(struct dwc3 *dwc) dwc3_host_exit(dwc); break; case USB_DR_MODE_OTG: - /* role might have changed since start */ - if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_HOST) { - dwc3_host_exit(dwc); - /* Add back UDC to match dwc3_gadget_exit() */ - usb_add_gadget_udc(dwc->dev, &dwc->gadget); - } - - dwc3_gadget_exit(dwc); + dwc3_drd_exit(dwc); break; default: /* do nothing */ diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index adb04af..b1be4a3 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -786,6 +786,10 @@ struct dwc3_scratchpad_array { * @revision: revision register contents * @dr_mode: requested mode of operation * @current_dr_role: current role of operation when in dual-role mode + * @edev: extcon handle + * @edev_nb: extcon notifier + * @drd_work: dual-role work + * @drd_prevent_change: flag to prevent dual-role state change * @hsphy_mode: UTMI phy mode, one of following: * - USBPHY_INTERFACE_MODE_UTMI * - USBPHY_INTERFACE_MODE_UTMIW @@ -903,6 +907,11 @@ struct dwc3 { enum usb_dr_mode dr_mode; u32 current_dr_role; + struct extcon_dev *edev; + struct notifier_block edev_nb; + bool drd_prevent_change; + struct work_struct drd_work; + enum usb_phy_interface hsphy_mode; u32 fladj; @@ -1225,6 +1234,16 @@ static inline int dwc3_send_gadget_generic_command(struct dwc3 *dwc, { return 0; } #endif +#if IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE) +int dwc3_drd_init(struct dwc3 *dwc); +void dwc3_drd_exit(struct dwc3 *dwc); +#else +static inline int dwc3_drd_init(struct dwc3 *dwc) +{ return 0; } +static inline void dwc3_drd_exit(struct dwc3 *dwc) +{ } +#endif + /* power management interface */ #if !IS_ENABLED(CONFIG_USB_DWC3_HOST) int dwc3_gadget_suspend(struct dwc3 *dwc); diff --git a/drivers/usb/dwc3/drd.c b/drivers/usb/dwc3/drd.c new file mode 100644 index 0000000..c9b02a3 --- /dev/null +++ b/drivers/usb/dwc3/drd.c @@ -0,0 +1,167 @@ +/** + * drd.c - DesignWare USB3 DRD Controller Dual-role support + * + * Copyright (C) 2017 Texas Instruments Incorporated - http://www.ti.com + * + * Authors: Roger Quadros + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 of + * the License 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "debug.h" +#include "core.h" +#include "gadget.h" + +static void dwc3_drd_update(struct dwc3 *dwc) +{ + int id; + int new_role; + unsigned long flags; + + if (dwc->drd_prevent_change) + return; + + id = extcon_get_state(dwc->edev, EXTCON_USB_HOST); + /* Host means ID is 0 */ + id = !id; + + if (!id) + new_role = DWC3_GCTL_PRTCAP_HOST; + else + new_role = DWC3_GCTL_PRTCAP_DEVICE; + + if (dwc->current_dr_role == new_role) + return; + + /* stop old role */ + switch (dwc->current_dr_role) { + case DWC3_GCTL_PRTCAP_HOST: + dwc3_host_exit(dwc); + break; + case DWC3_GCTL_PRTCAP_DEVICE: + usb_del_gadget_udc(&dwc->gadget); + break; + default: + break; + } + + /* switch PRTCAP mode. updates current_dr_role */ + spin_lock_irqsave(&dwc->lock, flags); + dwc3_set_mode(dwc, new_role); + spin_unlock_irqrestore(&dwc->lock, flags); + + /* start new role */ + switch (dwc->current_dr_role) { + case DWC3_GCTL_PRTCAP_HOST: + dwc3_host_init(dwc); + break; + case DWC3_GCTL_PRTCAP_DEVICE: + dwc3_event_buffers_setup(dwc); + usb_add_gadget_udc(dwc->dev, &dwc->gadget); + break; + default: + break; + } +} + +static void dwc3_drd_work(struct work_struct *work) +{ + struct dwc3 *dwc = container_of(work, struct dwc3, + drd_work); + dwc3_drd_update(dwc); +} + +static int dwc3_drd_notifier(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct dwc3 *dwc = container_of(nb, struct dwc3, edev_nb); + + queue_work(system_power_efficient_wq, &dwc->drd_work); + + return NOTIFY_DONE; +} + +int dwc3_drd_init(struct dwc3 *dwc) +{ + int ret; + int id; + struct device *dev = dwc->dev; + + INIT_WORK(&dwc->drd_work, dwc3_drd_work); + + if (dev->of_node) { + if (of_property_read_bool(dev->of_node, "extcon")) + dwc->edev = extcon_get_edev_by_phandle(dev, 0); + + if (IS_ERR(dwc->edev)) + return PTR_ERR(dwc->edev); + } else { + return -ENODEV; + } + + dwc->edev_nb.notifier_call = dwc3_drd_notifier; + ret = extcon_register_notifier(dwc->edev, EXTCON_USB_HOST, + &dwc->edev_nb); + if (ret < 0) { + dev_err(dwc->dev, "Couldn't register USB-HOST cable notifier\n"); + return -ENODEV; + } + + /* sanity check id & vbus states */ + id = extcon_get_state(dwc->edev, EXTCON_USB_HOST); + if (id < 0) { + dev_err(dwc->dev, "Invalid USB cable state. ID %d", id); + ret = -ENODEV; + goto fail; + } + + /* start in peripheral role by default */ + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE); + ret = dwc3_gadget_init(dwc); + if (ret) + goto fail; + + /* check & update drd state */ + dwc3_drd_update(dwc); + + return 0; + +fail: + extcon_unregister_notifier(dwc->edev, EXTCON_USB_HOST, + &dwc->edev_nb); + + return ret; +} + +void dwc3_drd_exit(struct dwc3 *dwc) +{ + unsigned long flags; + + spin_lock_irqsave(&dwc->lock, flags); + dwc->drd_prevent_change = true; + spin_unlock_irqrestore(&dwc->lock, flags); + + extcon_unregister_notifier(dwc->edev, EXTCON_USB_HOST, + &dwc->edev_nb); + + /* role might have changed since start, stop active controller */ + if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_HOST) { + dwc3_host_exit(dwc); + /* Add back UDC to match dwc3_gadget_exit() */ + usb_add_gadget_udc(dwc->dev, &dwc->gadget); + } + + dwc3_gadget_exit(dwc); +}