From patchwork Mon Apr 3 12:20:31 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roger Quadros X-Patchwork-Id: 96625 Delivered-To: patch@linaro.org Received: by 10.140.89.233 with SMTP id v96csp72501qgd; Mon, 3 Apr 2017 05:21:14 -0700 (PDT) X-Received: by 10.84.195.1 with SMTP id i1mr21438161pld.175.1491222074811; Mon, 03 Apr 2017 05:21:14 -0700 (PDT) Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id 80si14130231pga.172.2017.04.03.05.21.14; Mon, 03 Apr 2017 05:21:14 -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 S1753125AbdDCMUx (ORCPT + 25 others); Mon, 3 Apr 2017 08:20:53 -0400 Received: from lelnx193.ext.ti.com ([198.47.27.77]:29050 "EHLO lelnx193.ext.ti.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752984AbdDCMUu (ORCPT ); Mon, 3 Apr 2017 08:20:50 -0400 Received: from dlelxv90.itg.ti.com ([172.17.2.17]) by lelnx193.ext.ti.com (8.15.1/8.15.1) with ESMTP id v33CKfS2009744; Mon, 3 Apr 2017 07:20:41 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ti.com; s=ti-com-17Q1; t=1491222041; bh=HnBqMlEVEV8bE/Per3iR0MmrpzOCwp32RKWTCLKQjcs=; h=From:To:CC:Subject:Date:In-Reply-To:References; b=B+T6bVtwb+dkGozthK0ipIVHc7avYzy4NO+eEBmTaKREWzsIFErBQfOaGQ0tRS7Qb xv5P9nwQc3hZ8NfiqtnsDJdBda78pzNnyqGUyjU9iaLX0qfPQemuoRS5TTNuGGovmo Hd86nqEm1mTzBRJ8zSDZSOG7J4BznKbl9qnmLUPY= 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 v33CKfla013405; Mon, 3 Apr 2017 07:20:41 -0500 Received: from dflp33.itg.ti.com (10.64.6.16) by DLEE71.ent.ti.com (157.170.170.114) with Microsoft SMTP Server id 14.3.294.0; Mon, 3 Apr 2017 07:20:40 -0500 Received: from lta0400828d.ti.com (ileax41-snat.itg.ti.com [10.172.224.153]) by dflp33.itg.ti.com (8.14.3/8.13.8) with ESMTP id v33CKXus010968; Mon, 3 Apr 2017 07:20:39 -0500 From: Roger Quadros To: CC: , , , Roger Quadros Subject: [PATCH v3 3/3] usb: dwc3: Add dual-role support Date: Mon, 3 Apr 2017 15:20:31 +0300 Message-ID: <1491222031-18120-4-git-send-email-rogerq@ti.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1491222031-18120-1-git-send-email-rogerq@ti.com> References: <1491222031-18120-1-git-send-email-rogerq@ti.com> MIME-Version: 1.0 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 --- 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..8402d8f 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 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); +}