linux/drivers/usb/dwc2/drd.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * drd.c - DesignWare USB2 DRD Controller Dual-role support
   4 *
   5 * Copyright (C) 2020 STMicroelectronics
   6 *
   7 * Author(s): Amelie Delaunay <amelie.delaunay@st.com>
   8 */
   9
  10#include <linux/iopoll.h>
  11#include <linux/platform_device.h>
  12#include <linux/usb/role.h>
  13#include "core.h"
  14
  15static void dwc2_ovr_init(struct dwc2_hsotg *hsotg)
  16{
  17        unsigned long flags;
  18        u32 gotgctl;
  19
  20        spin_lock_irqsave(&hsotg->lock, flags);
  21
  22        gotgctl = dwc2_readl(hsotg, GOTGCTL);
  23        gotgctl |= GOTGCTL_BVALOEN | GOTGCTL_AVALOEN | GOTGCTL_VBVALOEN;
  24        gotgctl |= GOTGCTL_DBNCE_FLTR_BYPASS;
  25        gotgctl &= ~(GOTGCTL_BVALOVAL | GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL);
  26        dwc2_writel(hsotg, gotgctl, GOTGCTL);
  27
  28        dwc2_force_mode(hsotg, false);
  29
  30        spin_unlock_irqrestore(&hsotg->lock, flags);
  31}
  32
  33static int dwc2_ovr_avalid(struct dwc2_hsotg *hsotg, bool valid)
  34{
  35        u32 gotgctl = dwc2_readl(hsotg, GOTGCTL);
  36
  37        /* Check if A-Session is already in the right state */
  38        if ((valid && (gotgctl & GOTGCTL_ASESVLD)) ||
  39            (!valid && !(gotgctl & GOTGCTL_ASESVLD)))
  40                return -EALREADY;
  41
  42        if (valid)
  43                gotgctl |= GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL;
  44        else
  45                gotgctl &= ~(GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL);
  46        dwc2_writel(hsotg, gotgctl, GOTGCTL);
  47
  48        return 0;
  49}
  50
  51static int dwc2_ovr_bvalid(struct dwc2_hsotg *hsotg, bool valid)
  52{
  53        u32 gotgctl = dwc2_readl(hsotg, GOTGCTL);
  54
  55        /* Check if B-Session is already in the right state */
  56        if ((valid && (gotgctl & GOTGCTL_BSESVLD)) ||
  57            (!valid && !(gotgctl & GOTGCTL_BSESVLD)))
  58                return -EALREADY;
  59
  60        if (valid)
  61                gotgctl |= GOTGCTL_BVALOVAL | GOTGCTL_VBVALOVAL;
  62        else
  63                gotgctl &= ~(GOTGCTL_BVALOVAL | GOTGCTL_VBVALOVAL);
  64        dwc2_writel(hsotg, gotgctl, GOTGCTL);
  65
  66        return 0;
  67}
  68
  69static int dwc2_drd_role_sw_set(struct usb_role_switch *sw, enum usb_role role)
  70{
  71        struct dwc2_hsotg *hsotg = usb_role_switch_get_drvdata(sw);
  72        unsigned long flags;
  73        int already = 0;
  74
  75        /* Skip session not in line with dr_mode */
  76        if ((role == USB_ROLE_DEVICE && hsotg->dr_mode == USB_DR_MODE_HOST) ||
  77            (role == USB_ROLE_HOST && hsotg->dr_mode == USB_DR_MODE_PERIPHERAL))
  78                return -EINVAL;
  79
  80#if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || \
  81        IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE)
  82        /* Skip session if core is in test mode */
  83        if (role == USB_ROLE_NONE && hsotg->test_mode) {
  84                dev_dbg(hsotg->dev, "Core is in test mode\n");
  85                return -EBUSY;
  86        }
  87#endif
  88
  89        spin_lock_irqsave(&hsotg->lock, flags);
  90
  91        if (role == USB_ROLE_HOST) {
  92                already = dwc2_ovr_avalid(hsotg, true);
  93        } else if (role == USB_ROLE_DEVICE) {
  94                already = dwc2_ovr_bvalid(hsotg, true);
  95                /* This clear DCTL.SFTDISCON bit */
  96                dwc2_hsotg_core_connect(hsotg);
  97        } else {
  98                if (dwc2_is_device_mode(hsotg)) {
  99                        if (!dwc2_ovr_bvalid(hsotg, false))
 100                                /* This set DCTL.SFTDISCON bit */
 101                                dwc2_hsotg_core_disconnect(hsotg);
 102                } else {
 103                        dwc2_ovr_avalid(hsotg, false);
 104                }
 105        }
 106
 107        spin_unlock_irqrestore(&hsotg->lock, flags);
 108
 109        if (!already && hsotg->dr_mode == USB_DR_MODE_OTG)
 110                /* This will raise a Connector ID Status Change Interrupt */
 111                dwc2_force_mode(hsotg, role == USB_ROLE_HOST);
 112
 113        dev_dbg(hsotg->dev, "%s-session valid\n",
 114                role == USB_ROLE_NONE ? "No" :
 115                role == USB_ROLE_HOST ? "A" : "B");
 116
 117        return 0;
 118}
 119
 120int dwc2_drd_init(struct dwc2_hsotg *hsotg)
 121{
 122        struct usb_role_switch_desc role_sw_desc = {0};
 123        struct usb_role_switch *role_sw;
 124        int ret;
 125
 126        if (!device_property_read_bool(hsotg->dev, "usb-role-switch"))
 127                return 0;
 128
 129        role_sw_desc.driver_data = hsotg;
 130        role_sw_desc.fwnode = dev_fwnode(hsotg->dev);
 131        role_sw_desc.set = dwc2_drd_role_sw_set;
 132        role_sw_desc.allow_userspace_control = true;
 133
 134        role_sw = usb_role_switch_register(hsotg->dev, &role_sw_desc);
 135        if (IS_ERR(role_sw)) {
 136                ret = PTR_ERR(role_sw);
 137                dev_err(hsotg->dev,
 138                        "failed to register role switch: %d\n", ret);
 139                return ret;
 140        }
 141
 142        hsotg->role_sw = role_sw;
 143
 144        /* Enable override and initialize values */
 145        dwc2_ovr_init(hsotg);
 146
 147        return 0;
 148}
 149
 150void dwc2_drd_suspend(struct dwc2_hsotg *hsotg)
 151{
 152        u32 gintsts, gintmsk;
 153
 154        if (hsotg->role_sw && !hsotg->params.external_id_pin_ctl) {
 155                gintmsk = dwc2_readl(hsotg, GINTMSK);
 156                gintmsk &= ~GINTSTS_CONIDSTSCHNG;
 157                dwc2_writel(hsotg, gintmsk, GINTMSK);
 158                gintsts = dwc2_readl(hsotg, GINTSTS);
 159                dwc2_writel(hsotg, gintsts | GINTSTS_CONIDSTSCHNG, GINTSTS);
 160        }
 161}
 162
 163void dwc2_drd_resume(struct dwc2_hsotg *hsotg)
 164{
 165        u32 gintsts, gintmsk;
 166
 167        if (hsotg->role_sw && !hsotg->params.external_id_pin_ctl) {
 168                gintsts = dwc2_readl(hsotg, GINTSTS);
 169                dwc2_writel(hsotg, gintsts | GINTSTS_CONIDSTSCHNG, GINTSTS);
 170                gintmsk = dwc2_readl(hsotg, GINTMSK);
 171                gintmsk |= GINTSTS_CONIDSTSCHNG;
 172                dwc2_writel(hsotg, gintmsk, GINTMSK);
 173        }
 174}
 175
 176void dwc2_drd_exit(struct dwc2_hsotg *hsotg)
 177{
 178        if (hsotg->role_sw)
 179                usb_role_switch_unregister(hsotg->role_sw);
 180}
 181