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                cable->changed = false;
  48
  49                if (cable->state)
  50                        val |= OTGSC_BSV;
  51                else
  52                        val &= ~OTGSC_BSV;
  53        }
  54
  55        cable = &ci->platdata->id_extcon;
  56        if (!IS_ERR(cable->edev)) {
  57                if (cable->changed)
  58                        val |= OTGSC_IDIS;
  59                else
  60                        val &= ~OTGSC_IDIS;
  61
  62                cable->changed = false;
  63
  64                if (cable->state)
  65                        val |= OTGSC_ID;
  66                else
  67                        val &= ~OTGSC_ID;
  68        }
  69
  70        return val;
  71}
  72
  73/**
  74 * hw_write_otgsc updates target bits of OTGSC register.
  75 * @mask: bitfield mask
  76 * @data: to be written
  77 */
  78void hw_write_otgsc(struct ci_hdrc *ci, u32 mask, u32 data)
  79{
  80        hw_write(ci, OP_OTGSC, mask | OTGSC_INT_STATUS_BITS, data);
  81}
  82
  83/**
  84 * ci_otg_role - pick role based on ID pin state
  85 * @ci: the controller
  86 */
  87enum ci_role ci_otg_role(struct ci_hdrc *ci)
  88{
  89        enum ci_role role = hw_read_otgsc(ci, OTGSC_ID)
  90                ? CI_ROLE_GADGET
  91                : CI_ROLE_HOST;
  92
  93        return role;
  94}
  95
  96void ci_handle_vbus_change(struct ci_hdrc *ci)
  97{
  98        if (!ci->is_otg)
  99                return;
 100
 101        if (hw_read_otgsc(ci, OTGSC_BSV))
 102                usb_gadget_vbus_connect(&ci->gadget);
 103        else
 104                usb_gadget_vbus_disconnect(&ci->gadget);
 105}
 106
 107#define CI_VBUS_STABLE_TIMEOUT_MS 5000
 108static void ci_handle_id_switch(struct ci_hdrc *ci)
 109{
 110        enum ci_role role = ci_otg_role(ci);
 111
 112        if (role != ci->role) {
 113                dev_dbg(ci->dev, "switching from %s to %s\n",
 114                        ci_role(ci)->name, ci->roles[role]->name);
 115
 116                ci_role_stop(ci);
 117
 118                if (role == CI_ROLE_GADGET)
 119                        /* wait vbus lower than OTGSC_BSV */
 120                        hw_wait_reg(ci, OP_OTGSC, OTGSC_BSV, 0,
 121                                        CI_VBUS_STABLE_TIMEOUT_MS);
 122
 123                ci_role_start(ci, role);
 124        }
 125}
 126/**
 127 * ci_otg_work - perform otg (vbus/id) event handle
 128 * @work: work struct
 129 */
 130static void ci_otg_work(struct work_struct *work)
 131{
 132        struct ci_hdrc *ci = container_of(work, struct ci_hdrc, work);
 133
 134        if (ci_otg_is_fsm_mode(ci) && !ci_otg_fsm_work(ci)) {
 135                enable_irq(ci->irq);
 136                return;
 137        }
 138
 139        pm_runtime_get_sync(ci->dev);
 140        if (ci->id_event) {
 141                ci->id_event = false;
 142                ci_handle_id_switch(ci);
 143        } else if (ci->b_sess_valid_event) {
 144                ci->b_sess_valid_event = false;
 145                ci_handle_vbus_change(ci);
 146        } else
 147                dev_err(ci->dev, "unexpected event occurs at %s\n", __func__);
 148        pm_runtime_put_sync(ci->dev);
 149
 150        enable_irq(ci->irq);
 151}
 152
 153
 154/**
 155 * ci_hdrc_otg_init - initialize otg struct
 156 * ci: the controller
 157 */
 158int ci_hdrc_otg_init(struct ci_hdrc *ci)
 159{
 160        INIT_WORK(&ci->work, ci_otg_work);
 161        ci->wq = create_freezable_workqueue("ci_otg");
 162        if (!ci->wq) {
 163                dev_err(ci->dev, "can't create workqueue\n");
 164                return -ENODEV;
 165        }
 166
 167        if (ci_otg_is_fsm_mode(ci))
 168                return ci_hdrc_otg_fsm_init(ci);
 169
 170        return 0;
 171}
 172
 173/**
 174 * ci_hdrc_otg_destroy - destroy otg struct
 175 * ci: the controller
 176 */
 177void ci_hdrc_otg_destroy(struct ci_hdrc *ci)
 178{
 179        if (ci->wq) {
 180                flush_workqueue(ci->wq);
 181                destroy_workqueue(ci->wq);
 182        }
 183        /* Disable all OTG irq and clear status */
 184        hw_write_otgsc(ci, OTGSC_INT_EN_BITS | OTGSC_INT_STATUS_BITS,
 185                                                OTGSC_INT_STATUS_BITS);
 186        if (ci_otg_is_fsm_mode(ci))
 187                ci_hdrc_otg_fsm_remove(ci);
 188}
 189