linux/drivers/usb/chipidea/otg.c
<<
>>
Prefs
   1/*
   2 * otg.c - ChipIdea USB IP core OTG driver
   3 *
   4 * Copyright (C) 2013 Freescale Semiconductor, Inc.
   5 *
   6 * Author: Peter Chen
   7 *
   8 * This program is free software; you can redistribute it and/or modify
   9 * it under the terms of the GNU General Public License version 2 as
  10 * published by the Free Software Foundation.
  11 */
  12
  13/*
  14 * This file mainly handles otgsc register, OTG fsm operations for HNP and SRP
  15 * are also included.
  16 */
  17
  18#include <linux/usb/otg.h>
  19#include <linux/usb/gadget.h>
  20#include <linux/usb/chipidea.h>
  21
  22#include "ci.h"
  23#include "bits.h"
  24#include "otg.h"
  25#include "otg_fsm.h"
  26
  27/**
  28 * hw_read_otgsc returns otgsc register bits value.
  29 * @mask: bitfield mask
  30 */
  31u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask)
  32{
  33        struct ci_hdrc_cable *cable;
  34        u32 val = hw_read(ci, OP_OTGSC, mask);
  35
  36        /*
  37         * If using extcon framework for VBUS and/or ID signal
  38         * detection overwrite OTGSC register value
  39         */
  40        cable = &ci->platdata->vbus_extcon;
  41        if (!IS_ERR(cable->edev)) {
  42                if (cable->changed)
  43                        val |= OTGSC_BSVIS;
  44                else
  45                        val &= ~OTGSC_BSVIS;
  46
  47                if (cable->connected)
  48                        val |= OTGSC_BSV;
  49                else
  50                        val &= ~OTGSC_BSV;
  51
  52                if (cable->enabled)
  53                        val |= OTGSC_BSVIE;
  54                else
  55                        val &= ~OTGSC_BSVIE;
  56        }
  57
  58        cable = &ci->platdata->id_extcon;
  59        if (!IS_ERR(cable->edev)) {
  60                if (cable->changed)
  61                        val |= OTGSC_IDIS;
  62                else
  63                        val &= ~OTGSC_IDIS;
  64
  65                if (cable->connected)
  66                        val &= ~OTGSC_ID; /* host */
  67                else
  68                        val |= OTGSC_ID; /* device */
  69
  70                if (cable->enabled)
  71                        val |= OTGSC_IDIE;
  72                else
  73                        val &= ~OTGSC_IDIE;
  74        }
  75
  76        return val & mask;
  77}
  78
  79/**
  80 * hw_write_otgsc updates target bits of OTGSC register.
  81 * @mask: bitfield mask
  82 * @data: to be written
  83 */
  84void hw_write_otgsc(struct ci_hdrc *ci, u32 mask, u32 data)
  85{
  86        struct ci_hdrc_cable *cable;
  87
  88        cable = &ci->platdata->vbus_extcon;
  89        if (!IS_ERR(cable->edev)) {
  90                if (data & mask & OTGSC_BSVIS)
  91                        cable->changed = false;
  92
  93                /* Don't enable vbus interrupt if using external notifier */
  94                if (data & mask & OTGSC_BSVIE) {
  95                        cable->enabled = true;
  96                        data &= ~OTGSC_BSVIE;
  97                } else if (mask & OTGSC_BSVIE) {
  98                        cable->enabled = false;
  99                }
 100        }
 101
 102        cable = &ci->platdata->id_extcon;
 103        if (!IS_ERR(cable->edev)) {
 104                if (data & mask & OTGSC_IDIS)
 105                        cable->changed = false;
 106
 107                /* Don't enable id interrupt if using external notifier */
 108                if (data & mask & OTGSC_IDIE) {
 109                        cable->enabled = true;
 110                        data &= ~OTGSC_IDIE;
 111                } else if (mask & OTGSC_IDIE) {
 112                        cable->enabled = false;
 113                }
 114        }
 115
 116        hw_write(ci, OP_OTGSC, mask | OTGSC_INT_STATUS_BITS, data);
 117}
 118
 119/**
 120 * ci_otg_role - pick role based on ID pin state
 121 * @ci: the controller
 122 */
 123enum ci_role ci_otg_role(struct ci_hdrc *ci)
 124{
 125        enum ci_role role = hw_read_otgsc(ci, OTGSC_ID)
 126                ? CI_ROLE_GADGET
 127                : CI_ROLE_HOST;
 128
 129        return role;
 130}
 131
 132void ci_handle_vbus_change(struct ci_hdrc *ci)
 133{
 134        if (!ci->is_otg)
 135                return;
 136
 137        if (hw_read_otgsc(ci, OTGSC_BSV) && !ci->vbus_active)
 138                usb_gadget_vbus_connect(&ci->gadget);
 139        else if (!hw_read_otgsc(ci, OTGSC_BSV) && ci->vbus_active)
 140                usb_gadget_vbus_disconnect(&ci->gadget);
 141}
 142
 143/**
 144 * When we switch to device mode, the vbus value should be lower
 145 * than OTGSC_BSV before connecting to host.
 146 *
 147 * @ci: the controller
 148 *
 149 * This function returns an error code if timeout
 150 */
 151static int hw_wait_vbus_lower_bsv(struct ci_hdrc *ci)
 152{
 153        unsigned long elapse = jiffies + msecs_to_jiffies(5000);
 154        u32 mask = OTGSC_BSV;
 155
 156        while (hw_read_otgsc(ci, mask)) {
 157                if (time_after(jiffies, elapse)) {
 158                        dev_err(ci->dev, "timeout waiting for %08x in OTGSC\n",
 159                                        mask);
 160                        return -ETIMEDOUT;
 161                }
 162                msleep(20);
 163        }
 164
 165        return 0;
 166}
 167
 168static void ci_handle_id_switch(struct ci_hdrc *ci)
 169{
 170        enum ci_role role = ci_otg_role(ci);
 171
 172        if (role != ci->role) {
 173                dev_dbg(ci->dev, "switching from %s to %s\n",
 174                        ci_role(ci)->name, ci->roles[role]->name);
 175
 176                ci_role_stop(ci);
 177
 178                if (role == CI_ROLE_GADGET &&
 179                                IS_ERR(ci->platdata->vbus_extcon.edev))
 180                        /*
 181                         * Wait vbus lower than OTGSC_BSV before connecting
 182                         * to host. If connecting status is from an external
 183                         * connector instead of register, we don't need to
 184                         * care vbus on the board, since it will not affect
 185                         * external connector status.
 186                         */
 187                        hw_wait_vbus_lower_bsv(ci);
 188
 189                ci_role_start(ci, role);
 190                /* vbus change may have already occurred */
 191                if (role == CI_ROLE_GADGET)
 192                        ci_handle_vbus_change(ci);
 193        }
 194}
 195/**
 196 * ci_otg_work - perform otg (vbus/id) event handle
 197 * @work: work struct
 198 */
 199static void ci_otg_work(struct work_struct *work)
 200{
 201        struct ci_hdrc *ci = container_of(work, struct ci_hdrc, work);
 202
 203        if (ci_otg_is_fsm_mode(ci) && !ci_otg_fsm_work(ci)) {
 204                enable_irq(ci->irq);
 205                return;
 206        }
 207
 208        pm_runtime_get_sync(ci->dev);
 209        if (ci->id_event) {
 210                ci->id_event = false;
 211                ci_handle_id_switch(ci);
 212        } else if (ci->b_sess_valid_event) {
 213                ci->b_sess_valid_event = false;
 214                ci_handle_vbus_change(ci);
 215        } else
 216                dev_err(ci->dev, "unexpected event occurs at %s\n", __func__);
 217        pm_runtime_put_sync(ci->dev);
 218
 219        enable_irq(ci->irq);
 220}
 221
 222
 223/**
 224 * ci_hdrc_otg_init - initialize otg struct
 225 * ci: the controller
 226 */
 227int ci_hdrc_otg_init(struct ci_hdrc *ci)
 228{
 229        INIT_WORK(&ci->work, ci_otg_work);
 230        ci->wq = create_freezable_workqueue("ci_otg");
 231        if (!ci->wq) {
 232                dev_err(ci->dev, "can't create workqueue\n");
 233                return -ENODEV;
 234        }
 235
 236        if (ci_otg_is_fsm_mode(ci))
 237                return ci_hdrc_otg_fsm_init(ci);
 238
 239        return 0;
 240}
 241
 242/**
 243 * ci_hdrc_otg_destroy - destroy otg struct
 244 * ci: the controller
 245 */
 246void ci_hdrc_otg_destroy(struct ci_hdrc *ci)
 247{
 248        if (ci->wq) {
 249                flush_workqueue(ci->wq);
 250                destroy_workqueue(ci->wq);
 251        }
 252        /* Disable all OTG irq and clear status */
 253        hw_write_otgsc(ci, OTGSC_INT_EN_BITS | OTGSC_INT_STATUS_BITS,
 254                                                OTGSC_INT_STATUS_BITS);
 255        if (ci_otg_is_fsm_mode(ci))
 256                ci_hdrc_otg_fsm_remove(ci);
 257}
 258