From patchwork Wed Jul 8 10:19:36 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roger Quadros X-Patchwork-Id: 6744181 Return-Path: X-Original-To: patchwork-linux-omap@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork2.web.kernel.org (Postfix) with ESMTP id BCB96C05AC for ; Wed, 8 Jul 2015 10:22:02 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 88F76206F4 for ; Wed, 8 Jul 2015 10:22:01 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 2E5D8206E9 for ; Wed, 8 Jul 2015 10:22:00 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S933746AbbGHKVm (ORCPT ); Wed, 8 Jul 2015 06:21:42 -0400 Received: from bear.ext.ti.com ([192.94.94.41]:37280 "EHLO bear.ext.ti.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1758236AbbGHKUc (ORCPT ); Wed, 8 Jul 2015 06:20:32 -0400 Received: from dflxv15.itg.ti.com ([128.247.5.124]) by bear.ext.ti.com (8.13.7/8.13.7) with ESMTP id t68AKLkb012716; Wed, 8 Jul 2015 05:20:21 -0500 Received: from DLEE71.ent.ti.com (dlee71.ent.ti.com [157.170.170.114]) by dflxv15.itg.ti.com (8.14.3/8.13.8) with ESMTP id t68AKLwP025339; Wed, 8 Jul 2015 05:20:21 -0500 Received: from dlep32.itg.ti.com (157.170.170.100) by DLEE71.ent.ti.com (157.170.170.114) with Microsoft SMTP Server id 14.3.224.2; Wed, 8 Jul 2015 05:20:06 -0500 Received: from rockdesk.ti.com (ileax41-snat.itg.ti.com [10.172.224.153]) by dlep32.itg.ti.com (8.14.3/8.13.8) with ESMTP id t68AJg6e013513; Wed, 8 Jul 2015 05:20:18 -0500 From: Roger Quadros To: , , , CC: , , , , , , , , Roger Quadros Subject: [PATCH v3 10/11] usb: otg: Add dual-role device (DRD) support Date: Wed, 8 Jul 2015 13:19:36 +0300 Message-ID: <1436350777-28056-11-git-send-email-rogerq@ti.com> X-Mailer: git-send-email 2.1.4 In-Reply-To: <1436350777-28056-1-git-send-email-rogerq@ti.com> References: <1436350777-28056-1-git-send-email-rogerq@ti.com> MIME-Version: 1.0 Sender: linux-omap-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-omap@vger.kernel.org X-Spam-Status: No, score=-7.6 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_HI, RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP DRD mode is a reduced functionality OTG mode. In this mode we don't support SRP, HNP and dynamic role-swap. In DRD operation, the controller mode (Host or Peripheral) is decided based on the ID pin status. Once a cable plug (Type-A or Type-B) is attached the controller selects the state and doesn't change till the cable in unplugged and a different cable type is inserted. As we don't need most of the complex OTG states and OTG timers we implement a lean DRD state machine in usb-otg.c. The DRD state machine is only interested in 2 hardware inputs 'id' and 'vbus; that are still passed via the origintal struct otg_fsm. Most of the usb-otg.c functionality remains the same except adding a new parameter to usb_otg_register() to indicate that the OTG controller needs to operate in DRD mode. Signed-off-by: Roger Quadros --- drivers/usb/common/usb-otg.c | 179 ++++++++++++++++++++++++++++++++++++++++--- include/linux/usb/otg-fsm.h | 8 +- include/linux/usb/otg.h | 5 +- 3 files changed, 180 insertions(+), 12 deletions(-) diff --git a/drivers/usb/common/usb-otg.c b/drivers/usb/common/usb-otg.c index 1f19001..9b89f4b 100644 --- a/drivers/usb/common/usb-otg.c +++ b/drivers/usb/common/usb-otg.c @@ -44,6 +44,7 @@ struct otg_hcd { struct otg_data { struct device *dev; /* HCD & GCD's parent device */ + bool drd_only; /* Dual-role only, no OTG features */ struct otg_fsm fsm; /* HCD, GCD and usb_otg_state are present in otg_fsm->otg * HCD is bus_to_hcd(fsm->otg->host) @@ -272,20 +273,172 @@ static int usb_otg_start_gadget(struct otg_fsm *fsm, int on) return 0; } +/* Change USB protocol when there is a protocol change */ +static int drd_set_protocol(struct otg_fsm *fsm, int protocol) +{ + struct otg_data *otgd = container_of(fsm, struct otg_data, fsm); + int ret = 0; + + if (fsm->protocol != protocol) { + dev_dbg(otgd->dev, "otg: changing role fsm->protocol= %d; new protocol= %d\n", + fsm->protocol, protocol); + /* stop old protocol */ + if (fsm->protocol == PROTO_HOST) + ret = otg_start_host(fsm, 0); + else if (fsm->protocol == PROTO_GADGET) + ret = otg_start_gadget(fsm, 0); + if (ret) + return ret; + + /* start new protocol */ + if (protocol == PROTO_HOST) + ret = otg_start_host(fsm, 1); + else if (protocol == PROTO_GADGET) + ret = otg_start_gadget(fsm, 1); + if (ret) + return ret; + + fsm->protocol = protocol; + return 0; + } + + return 0; +} + +/* Called when entering a DRD state */ +static void drd_set_state(struct otg_fsm *fsm, enum usb_otg_state new_state) +{ + struct otg_data *otgd = container_of(fsm, struct otg_data, fsm); + + if (fsm->otg->state == new_state) + return; + + fsm->state_changed = 1; + dev_dbg(otgd->dev, "otg: set state: %s\n", + usb_otg_state_string(new_state)); + switch (new_state) { + case OTG_STATE_B_IDLE: + drd_set_protocol(fsm, PROTO_UNDEF); + break; + case OTG_STATE_B_PERIPHERAL: + drd_set_protocol(fsm, PROTO_GADGET); + break; + case OTG_STATE_A_HOST: + drd_set_protocol(fsm, PROTO_HOST); + break; + case OTG_STATE_UNDEFINED: + case OTG_STATE_B_SRP_INIT: + case OTG_STATE_B_WAIT_ACON: + case OTG_STATE_B_HOST: + case OTG_STATE_A_IDLE: + case OTG_STATE_A_WAIT_VRISE: + case OTG_STATE_A_WAIT_BCON: + case OTG_STATE_A_SUSPEND: + case OTG_STATE_A_PERIPHERAL: + case OTG_STATE_A_WAIT_VFALL: + case OTG_STATE_A_VBUS_ERR: + default: + dev_warn(otgd->dev, "%s: otg: invalid state: %s\n", + __func__, usb_otg_state_string(new_state)); + break; + } + + fsm->otg->state = new_state; +} + /** - * OTG FSM work function + * DRD state change judgement + * + * For DRD we're only interested in some of the OTG states + * i.e. OTG_STATE_B_IDLE: both peripheral and host are stopped + * OTG_STATE_B_PERIPHERAL: peripheral active + * OTG_STATE_A_HOST: host active + * we're only interested in the following inputs + * fsm->id, fsm->vbus + */ +static int drd_statemachine(struct otg_fsm *fsm) +{ + struct otg_data *otgd = container_of(fsm, struct otg_data, fsm); + enum usb_otg_state state; + + mutex_lock(&fsm->lock); + + state = fsm->otg->state; + + switch (state) { + case OTG_STATE_UNDEFINED: + if (!fsm->id) + drd_set_state(fsm, OTG_STATE_A_HOST); + else if (fsm->id && fsm->vbus) + drd_set_state(fsm, OTG_STATE_B_PERIPHERAL); + else + drd_set_state(fsm, OTG_STATE_B_IDLE); + break; + case OTG_STATE_B_IDLE: + if (!fsm->id) + drd_set_state(fsm, OTG_STATE_A_HOST); + else if (fsm->vbus) + drd_set_state(fsm, OTG_STATE_B_PERIPHERAL); + break; + case OTG_STATE_B_PERIPHERAL: + if (!fsm->id) + drd_set_state(fsm, OTG_STATE_A_HOST); + else if (!fsm->vbus) + drd_set_state(fsm, OTG_STATE_B_IDLE); + break; + case OTG_STATE_A_HOST: + if (fsm->id && fsm->vbus) + drd_set_state(fsm, OTG_STATE_B_PERIPHERAL); + else if (fsm->id && !fsm->vbus) + drd_set_state(fsm, OTG_STATE_B_IDLE); + break; + + /* invalid states for DRD */ + case OTG_STATE_B_SRP_INIT: + case OTG_STATE_B_WAIT_ACON: + case OTG_STATE_B_HOST: + case OTG_STATE_A_IDLE: + case OTG_STATE_A_WAIT_VRISE: + case OTG_STATE_A_WAIT_BCON: + case OTG_STATE_A_SUSPEND: + case OTG_STATE_A_PERIPHERAL: + case OTG_STATE_A_WAIT_VFALL: + case OTG_STATE_A_VBUS_ERR: + dev_err(otgd->dev, "%s: otg: invalid usb-drd state: %s\n", + __func__, usb_otg_state_string(state)); + drd_set_state(fsm, OTG_STATE_UNDEFINED); + break; + } + + mutex_unlock(&fsm->lock); + dev_dbg(otgd->dev, "otg: quit statemachine, changed %d\n", + fsm->state_changed); + + return fsm->state_changed; +} + +/** + * OTG FSM/DRD work function */ static void usb_otg_work(struct work_struct *work) { struct otg_data *otgd = container_of(work, struct otg_data, work); - otg_statemachine(&otgd->fsm); + /* OTG state machine */ + if (!otgd->drd_only) { + otg_statemachine(&otgd->fsm); + return; + } + + /* DRD state machine */ + drd_statemachine(&otgd->fsm); } /** * usb_otg_register() - Register the OTG device to OTG core * @parent_device: parent device of Host & Gadget controllers. * @otg_fsm_ops: otg state machine ops. + * @drd_only: dual-role only. no OTG features. * * Register parent device that contains both HCD and GCD into * the USB OTG core. HCD and GCD will be prevented from starting @@ -294,7 +447,8 @@ static void usb_otg_work(struct work_struct *work) * Return: struct otg_fsm * if success, NULL if error. */ struct otg_fsm *usb_otg_register(struct device *parent_dev, - struct otg_fsm_ops *fsm_ops) + struct otg_fsm_ops *fsm_ops, + bool drd_only) { struct otg_data *otgd; int ret = 0; @@ -328,7 +482,15 @@ struct otg_fsm *usb_otg_register(struct device *parent_dev, goto err_wq; } - usb_otg_init_timers(otgd); + otgd->drd_only = drd_only; + /* For DRD mode we don't need OTG timers */ + if (!drd_only) { + usb_otg_init_timers(otgd); + + /* FIXME: we ignore caller's timer ops */ + otgd->fsm_ops.add_timer = usb_otg_add_timer; + otgd->fsm_ops.del_timer = usb_otg_del_timer; + } /* save original start host/gadget ops */ otgd->start_host = fsm_ops->start_host; @@ -338,9 +500,6 @@ struct otg_fsm *usb_otg_register(struct device *parent_dev, /* override ops */ otgd->fsm_ops.start_host = usb_otg_start_host; otgd->fsm_ops.start_gadget = usb_otg_start_gadget; - /* FIXME: we ignore caller's timer ops */ - otgd->fsm_ops.add_timer = usb_otg_add_timer; - otgd->fsm_ops.del_timer = usb_otg_del_timer; /* set otg ops */ otgd->fsm.ops = &otgd->fsm_ops; otgd->fsm.otg = &otgd->otg; @@ -443,8 +602,10 @@ static void usb_otg_stop_fsm(struct otg_fsm *fsm) otgd->fsm_running = false; /* Stop state machine / timers */ - for (i = 0; i < ARRAY_SIZE(otgd->timers); i++) - hrtimer_cancel(&otgd->timers[i].timer); + if (!otgd->drd_only) { + for (i = 0; i < ARRAY_SIZE(otgd->timers); i++) + hrtimer_cancel(&otgd->timers[i].timer); + } flush_workqueue(otgd->wq); fsm->otg->state = OTG_STATE_UNDEFINED; diff --git a/include/linux/usb/otg-fsm.h b/include/linux/usb/otg-fsm.h index 22d8baa..ae9c30a 100644 --- a/include/linux/usb/otg-fsm.h +++ b/include/linux/usb/otg-fsm.h @@ -48,6 +48,11 @@ enum otg_fsm_timer { /** * struct otg_fsm - OTG state machine according to the OTG spec * + * DRD mode hardware Inputs + * + * @id: TRUE for B-device, FALSE for A-device. + * @vbus: VBUS voltage in regulation. + * * OTG hardware Inputs * * Common inputs for A and B device @@ -122,7 +127,8 @@ enum otg_fsm_timer { */ struct otg_fsm { /* Input */ - int id; + int id; /* DRD + OTG */ + int vbus; /* DRD only */ int adp_change; int power_up; int a_srp_det; diff --git a/include/linux/usb/otg.h b/include/linux/usb/otg.h index ce6f8d8..1086a0b 100644 --- a/include/linux/usb/otg.h +++ b/include/linux/usb/otg.h @@ -58,7 +58,7 @@ enum usb_dr_mode { #if IS_ENABLED(CONFIG_USB_OTG) struct otg_fsm *usb_otg_register(struct device *parent_dev, - struct otg_fsm_ops *fsm_ops); + struct otg_fsm_ops *fsm_ops, bool drd_only); int usb_otg_unregister(struct device *parent_dev); int usb_otg_register_hcd(struct usb_hcd *hcd, unsigned int irqnum, unsigned long irqflags, struct otg_hcd_ops *ops); @@ -73,7 +73,8 @@ struct device *usb_otg_fsm_to_dev(struct otg_fsm *fsm); #else /* CONFIG_USB_OTG */ static inline struct otg_fsm *usb_otg_register(struct device *parent_dev, - struct otg_fsm_ops *fsm_ops) + struct otg_fsm_ops *fsm_ops, + bool drd_only) { return ERR_PTR(-ENOSYS); }