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 */ 31 u32 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 */ 84 void 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 */ 123 enum 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 132 void 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 */ 151 static 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 168 static 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 */ 199 static 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 */ 227 int 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 */ 246 void 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