linux/drivers/usb/mtu3/mtu3_dr.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * mtu3_dr.c - dual role switch and host glue layer
   4 *
   5 * Copyright (C) 2016 MediaTek Inc.
   6 *
   7 * Author: Chunfeng Yun <chunfeng.yun@mediatek.com>
   8 */
   9
  10#include "mtu3.h"
  11#include "mtu3_dr.h"
  12#include "mtu3_debug.h"
  13
  14#define USB2_PORT 2
  15#define USB3_PORT 3
  16
  17enum mtu3_vbus_id_state {
  18        MTU3_ID_FLOAT = 1,
  19        MTU3_ID_GROUND,
  20        MTU3_VBUS_OFF,
  21        MTU3_VBUS_VALID,
  22};
  23
  24static char *mailbox_state_string(enum mtu3_vbus_id_state state)
  25{
  26        switch (state) {
  27        case MTU3_ID_FLOAT:
  28                return "ID_FLOAT";
  29        case MTU3_ID_GROUND:
  30                return "ID_GROUND";
  31        case MTU3_VBUS_OFF:
  32                return "VBUS_OFF";
  33        case MTU3_VBUS_VALID:
  34                return "VBUS_VALID";
  35        default:
  36                return "UNKNOWN";
  37        }
  38}
  39
  40static void toggle_opstate(struct ssusb_mtk *ssusb)
  41{
  42        if (!ssusb->otg_switch.is_u3_drd) {
  43                mtu3_setbits(ssusb->mac_base, U3D_DEVICE_CONTROL, DC_SESSION);
  44                mtu3_setbits(ssusb->mac_base, U3D_POWER_MANAGEMENT, SOFT_CONN);
  45        }
  46}
  47
  48/* only port0 supports dual-role mode */
  49static int ssusb_port0_switch(struct ssusb_mtk *ssusb,
  50        int version, bool tohost)
  51{
  52        void __iomem *ibase = ssusb->ippc_base;
  53        u32 value;
  54
  55        dev_dbg(ssusb->dev, "%s (switch u%d port0 to %s)\n", __func__,
  56                version, tohost ? "host" : "device");
  57
  58        if (version == USB2_PORT) {
  59                /* 1. power off and disable u2 port0 */
  60                value = mtu3_readl(ibase, SSUSB_U2_CTRL(0));
  61                value |= SSUSB_U2_PORT_PDN | SSUSB_U2_PORT_DIS;
  62                mtu3_writel(ibase, SSUSB_U2_CTRL(0), value);
  63
  64                /* 2. power on, enable u2 port0 and select its mode */
  65                value = mtu3_readl(ibase, SSUSB_U2_CTRL(0));
  66                value &= ~(SSUSB_U2_PORT_PDN | SSUSB_U2_PORT_DIS);
  67                value = tohost ? (value | SSUSB_U2_PORT_HOST_SEL) :
  68                        (value & (~SSUSB_U2_PORT_HOST_SEL));
  69                mtu3_writel(ibase, SSUSB_U2_CTRL(0), value);
  70        } else {
  71                /* 1. power off and disable u3 port0 */
  72                value = mtu3_readl(ibase, SSUSB_U3_CTRL(0));
  73                value |= SSUSB_U3_PORT_PDN | SSUSB_U3_PORT_DIS;
  74                mtu3_writel(ibase, SSUSB_U3_CTRL(0), value);
  75
  76                /* 2. power on, enable u3 port0 and select its mode */
  77                value = mtu3_readl(ibase, SSUSB_U3_CTRL(0));
  78                value &= ~(SSUSB_U3_PORT_PDN | SSUSB_U3_PORT_DIS);
  79                value = tohost ? (value | SSUSB_U3_PORT_HOST_SEL) :
  80                        (value & (~SSUSB_U3_PORT_HOST_SEL));
  81                mtu3_writel(ibase, SSUSB_U3_CTRL(0), value);
  82        }
  83
  84        return 0;
  85}
  86
  87static void switch_port_to_host(struct ssusb_mtk *ssusb)
  88{
  89        u32 check_clk = 0;
  90
  91        dev_dbg(ssusb->dev, "%s\n", __func__);
  92
  93        ssusb_port0_switch(ssusb, USB2_PORT, true);
  94
  95        if (ssusb->otg_switch.is_u3_drd) {
  96                ssusb_port0_switch(ssusb, USB3_PORT, true);
  97                check_clk = SSUSB_U3_MAC_RST_B_STS;
  98        }
  99
 100        ssusb_check_clocks(ssusb, check_clk);
 101
 102        /* after all clocks are stable */
 103        toggle_opstate(ssusb);
 104}
 105
 106static void switch_port_to_device(struct ssusb_mtk *ssusb)
 107{
 108        u32 check_clk = 0;
 109
 110        dev_dbg(ssusb->dev, "%s\n", __func__);
 111
 112        ssusb_port0_switch(ssusb, USB2_PORT, false);
 113
 114        if (ssusb->otg_switch.is_u3_drd) {
 115                ssusb_port0_switch(ssusb, USB3_PORT, false);
 116                check_clk = SSUSB_U3_MAC_RST_B_STS;
 117        }
 118
 119        ssusb_check_clocks(ssusb, check_clk);
 120}
 121
 122int ssusb_set_vbus(struct otg_switch_mtk *otg_sx, int is_on)
 123{
 124        struct ssusb_mtk *ssusb =
 125                container_of(otg_sx, struct ssusb_mtk, otg_switch);
 126        struct regulator *vbus = otg_sx->vbus;
 127        int ret;
 128
 129        /* vbus is optional */
 130        if (!vbus)
 131                return 0;
 132
 133        dev_dbg(ssusb->dev, "%s: turn %s\n", __func__, is_on ? "on" : "off");
 134
 135        if (is_on) {
 136                ret = regulator_enable(vbus);
 137                if (ret) {
 138                        dev_err(ssusb->dev, "vbus regulator enable failed\n");
 139                        return ret;
 140                }
 141        } else {
 142                regulator_disable(vbus);
 143        }
 144
 145        return 0;
 146}
 147
 148/*
 149 * switch to host: -> MTU3_VBUS_OFF --> MTU3_ID_GROUND
 150 * switch to device: -> MTU3_ID_FLOAT --> MTU3_VBUS_VALID
 151 */
 152static void ssusb_set_mailbox(struct otg_switch_mtk *otg_sx,
 153        enum mtu3_vbus_id_state status)
 154{
 155        struct ssusb_mtk *ssusb =
 156                container_of(otg_sx, struct ssusb_mtk, otg_switch);
 157        struct mtu3 *mtu = ssusb->u3d;
 158
 159        dev_dbg(ssusb->dev, "mailbox %s\n", mailbox_state_string(status));
 160        mtu3_dbg_trace(ssusb->dev, "mailbox %s", mailbox_state_string(status));
 161
 162        switch (status) {
 163        case MTU3_ID_GROUND:
 164                switch_port_to_host(ssusb);
 165                ssusb_set_vbus(otg_sx, 1);
 166                ssusb->is_host = true;
 167                break;
 168        case MTU3_ID_FLOAT:
 169                ssusb->is_host = false;
 170                ssusb_set_vbus(otg_sx, 0);
 171                switch_port_to_device(ssusb);
 172                break;
 173        case MTU3_VBUS_OFF:
 174                mtu3_stop(mtu);
 175                pm_relax(ssusb->dev);
 176                break;
 177        case MTU3_VBUS_VALID:
 178                /* avoid suspend when works as device */
 179                pm_stay_awake(ssusb->dev);
 180                mtu3_start(mtu);
 181                break;
 182        default:
 183                dev_err(ssusb->dev, "invalid state\n");
 184        }
 185}
 186
 187static void ssusb_id_work(struct work_struct *work)
 188{
 189        struct otg_switch_mtk *otg_sx =
 190                container_of(work, struct otg_switch_mtk, id_work);
 191
 192        if (otg_sx->id_event)
 193                ssusb_set_mailbox(otg_sx, MTU3_ID_GROUND);
 194        else
 195                ssusb_set_mailbox(otg_sx, MTU3_ID_FLOAT);
 196}
 197
 198static void ssusb_vbus_work(struct work_struct *work)
 199{
 200        struct otg_switch_mtk *otg_sx =
 201                container_of(work, struct otg_switch_mtk, vbus_work);
 202
 203        if (otg_sx->vbus_event)
 204                ssusb_set_mailbox(otg_sx, MTU3_VBUS_VALID);
 205        else
 206                ssusb_set_mailbox(otg_sx, MTU3_VBUS_OFF);
 207}
 208
 209/*
 210 * @ssusb_id_notifier is called in atomic context, but @ssusb_set_mailbox
 211 * may sleep, so use work queue here
 212 */
 213static int ssusb_id_notifier(struct notifier_block *nb,
 214        unsigned long event, void *ptr)
 215{
 216        struct otg_switch_mtk *otg_sx =
 217                container_of(nb, struct otg_switch_mtk, id_nb);
 218
 219        otg_sx->id_event = event;
 220        schedule_work(&otg_sx->id_work);
 221
 222        return NOTIFY_DONE;
 223}
 224
 225static int ssusb_vbus_notifier(struct notifier_block *nb,
 226        unsigned long event, void *ptr)
 227{
 228        struct otg_switch_mtk *otg_sx =
 229                container_of(nb, struct otg_switch_mtk, vbus_nb);
 230
 231        otg_sx->vbus_event = event;
 232        schedule_work(&otg_sx->vbus_work);
 233
 234        return NOTIFY_DONE;
 235}
 236
 237static int ssusb_extcon_register(struct otg_switch_mtk *otg_sx)
 238{
 239        struct ssusb_mtk *ssusb =
 240                container_of(otg_sx, struct ssusb_mtk, otg_switch);
 241        struct extcon_dev *edev = otg_sx->edev;
 242        int ret;
 243
 244        /* extcon is optional */
 245        if (!edev)
 246                return 0;
 247
 248        otg_sx->vbus_nb.notifier_call = ssusb_vbus_notifier;
 249        ret = devm_extcon_register_notifier(ssusb->dev, edev, EXTCON_USB,
 250                                        &otg_sx->vbus_nb);
 251        if (ret < 0) {
 252                dev_err(ssusb->dev, "failed to register notifier for USB\n");
 253                return ret;
 254        }
 255
 256        otg_sx->id_nb.notifier_call = ssusb_id_notifier;
 257        ret = devm_extcon_register_notifier(ssusb->dev, edev, EXTCON_USB_HOST,
 258                                        &otg_sx->id_nb);
 259        if (ret < 0) {
 260                dev_err(ssusb->dev, "failed to register notifier for USB-HOST\n");
 261                return ret;
 262        }
 263
 264        dev_dbg(ssusb->dev, "EXTCON_USB: %d, EXTCON_USB_HOST: %d\n",
 265                extcon_get_state(edev, EXTCON_USB),
 266                extcon_get_state(edev, EXTCON_USB_HOST));
 267
 268        /* default as host, switch to device mode if needed */
 269        if (extcon_get_state(edev, EXTCON_USB_HOST) == false)
 270                ssusb_set_mailbox(otg_sx, MTU3_ID_FLOAT);
 271        if (extcon_get_state(edev, EXTCON_USB) == true)
 272                ssusb_set_mailbox(otg_sx, MTU3_VBUS_VALID);
 273
 274        return 0;
 275}
 276
 277/*
 278 * We provide an interface via debugfs to switch between host and device modes
 279 * depending on user input.
 280 * This is useful in special cases, such as uses TYPE-A receptacle but also
 281 * wants to support dual-role mode.
 282 */
 283void ssusb_mode_manual_switch(struct ssusb_mtk *ssusb, int to_host)
 284{
 285        struct otg_switch_mtk *otg_sx = &ssusb->otg_switch;
 286
 287        if (to_host) {
 288                ssusb_set_force_mode(ssusb, MTU3_DR_FORCE_HOST);
 289                ssusb_set_mailbox(otg_sx, MTU3_VBUS_OFF);
 290                ssusb_set_mailbox(otg_sx, MTU3_ID_GROUND);
 291        } else {
 292                ssusb_set_force_mode(ssusb, MTU3_DR_FORCE_DEVICE);
 293                ssusb_set_mailbox(otg_sx, MTU3_ID_FLOAT);
 294                ssusb_set_mailbox(otg_sx, MTU3_VBUS_VALID);
 295        }
 296}
 297
 298void ssusb_set_force_mode(struct ssusb_mtk *ssusb,
 299                          enum mtu3_dr_force_mode mode)
 300{
 301        u32 value;
 302
 303        value = mtu3_readl(ssusb->ippc_base, SSUSB_U2_CTRL(0));
 304        switch (mode) {
 305        case MTU3_DR_FORCE_DEVICE:
 306                value |= SSUSB_U2_PORT_FORCE_IDDIG | SSUSB_U2_PORT_RG_IDDIG;
 307                break;
 308        case MTU3_DR_FORCE_HOST:
 309                value |= SSUSB_U2_PORT_FORCE_IDDIG;
 310                value &= ~SSUSB_U2_PORT_RG_IDDIG;
 311                break;
 312        case MTU3_DR_FORCE_NONE:
 313                value &= ~(SSUSB_U2_PORT_FORCE_IDDIG | SSUSB_U2_PORT_RG_IDDIG);
 314                break;
 315        default:
 316                return;
 317        }
 318        mtu3_writel(ssusb->ippc_base, SSUSB_U2_CTRL(0), value);
 319}
 320
 321int ssusb_otg_switch_init(struct ssusb_mtk *ssusb)
 322{
 323        struct otg_switch_mtk *otg_sx = &ssusb->otg_switch;
 324        int ret = 0;
 325
 326        INIT_WORK(&otg_sx->id_work, ssusb_id_work);
 327        INIT_WORK(&otg_sx->vbus_work, ssusb_vbus_work);
 328
 329        if (otg_sx->manual_drd_enabled)
 330                ssusb_dr_debugfs_init(ssusb);
 331        else
 332                ret = ssusb_extcon_register(otg_sx);
 333
 334        return ret;
 335}
 336
 337void ssusb_otg_switch_exit(struct ssusb_mtk *ssusb)
 338{
 339        struct otg_switch_mtk *otg_sx = &ssusb->otg_switch;
 340
 341        cancel_work_sync(&otg_sx->id_work);
 342        cancel_work_sync(&otg_sx->vbus_work);
 343}
 344