18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * otg.c - ChipIdea USB IP core OTG driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2013 Freescale Semiconductor, Inc. 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Author: Peter Chen 88c2ecf20Sopenharmony_ci */ 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci/* 118c2ecf20Sopenharmony_ci * This file mainly handles otgsc register, OTG fsm operations for HNP and SRP 128c2ecf20Sopenharmony_ci * are also included. 138c2ecf20Sopenharmony_ci */ 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_ci#include <linux/usb/otg.h> 168c2ecf20Sopenharmony_ci#include <linux/usb/gadget.h> 178c2ecf20Sopenharmony_ci#include <linux/usb/chipidea.h> 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci#include "ci.h" 208c2ecf20Sopenharmony_ci#include "bits.h" 218c2ecf20Sopenharmony_ci#include "otg.h" 228c2ecf20Sopenharmony_ci#include "otg_fsm.h" 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci/** 258c2ecf20Sopenharmony_ci * hw_read_otgsc returns otgsc register bits value. 268c2ecf20Sopenharmony_ci * @ci: the controller 278c2ecf20Sopenharmony_ci * @mask: bitfield mask 288c2ecf20Sopenharmony_ci */ 298c2ecf20Sopenharmony_ciu32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask) 308c2ecf20Sopenharmony_ci{ 318c2ecf20Sopenharmony_ci struct ci_hdrc_cable *cable; 328c2ecf20Sopenharmony_ci u32 val = hw_read(ci, OP_OTGSC, mask); 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci /* 358c2ecf20Sopenharmony_ci * If using extcon framework for VBUS and/or ID signal 368c2ecf20Sopenharmony_ci * detection overwrite OTGSC register value 378c2ecf20Sopenharmony_ci */ 388c2ecf20Sopenharmony_ci cable = &ci->platdata->vbus_extcon; 398c2ecf20Sopenharmony_ci if (!IS_ERR(cable->edev) || ci->role_switch) { 408c2ecf20Sopenharmony_ci if (cable->changed) 418c2ecf20Sopenharmony_ci val |= OTGSC_BSVIS; 428c2ecf20Sopenharmony_ci else 438c2ecf20Sopenharmony_ci val &= ~OTGSC_BSVIS; 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci if (cable->connected) 468c2ecf20Sopenharmony_ci val |= OTGSC_BSV; 478c2ecf20Sopenharmony_ci else 488c2ecf20Sopenharmony_ci val &= ~OTGSC_BSV; 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci if (cable->enabled) 518c2ecf20Sopenharmony_ci val |= OTGSC_BSVIE; 528c2ecf20Sopenharmony_ci else 538c2ecf20Sopenharmony_ci val &= ~OTGSC_BSVIE; 548c2ecf20Sopenharmony_ci } 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci cable = &ci->platdata->id_extcon; 578c2ecf20Sopenharmony_ci if (!IS_ERR(cable->edev) || ci->role_switch) { 588c2ecf20Sopenharmony_ci if (cable->changed) 598c2ecf20Sopenharmony_ci val |= OTGSC_IDIS; 608c2ecf20Sopenharmony_ci else 618c2ecf20Sopenharmony_ci val &= ~OTGSC_IDIS; 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci if (cable->connected) 648c2ecf20Sopenharmony_ci val &= ~OTGSC_ID; /* host */ 658c2ecf20Sopenharmony_ci else 668c2ecf20Sopenharmony_ci val |= OTGSC_ID; /* device */ 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci if (cable->enabled) 698c2ecf20Sopenharmony_ci val |= OTGSC_IDIE; 708c2ecf20Sopenharmony_ci else 718c2ecf20Sopenharmony_ci val &= ~OTGSC_IDIE; 728c2ecf20Sopenharmony_ci } 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci return val & mask; 758c2ecf20Sopenharmony_ci} 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci/** 788c2ecf20Sopenharmony_ci * hw_write_otgsc updates target bits of OTGSC register. 798c2ecf20Sopenharmony_ci * @ci: the controller 808c2ecf20Sopenharmony_ci * @mask: bitfield mask 818c2ecf20Sopenharmony_ci * @data: to be written 828c2ecf20Sopenharmony_ci */ 838c2ecf20Sopenharmony_civoid hw_write_otgsc(struct ci_hdrc *ci, u32 mask, u32 data) 848c2ecf20Sopenharmony_ci{ 858c2ecf20Sopenharmony_ci struct ci_hdrc_cable *cable; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci cable = &ci->platdata->vbus_extcon; 888c2ecf20Sopenharmony_ci if (!IS_ERR(cable->edev) || ci->role_switch) { 898c2ecf20Sopenharmony_ci if (data & mask & OTGSC_BSVIS) 908c2ecf20Sopenharmony_ci cable->changed = false; 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci /* Don't enable vbus interrupt if using external notifier */ 938c2ecf20Sopenharmony_ci if (data & mask & OTGSC_BSVIE) { 948c2ecf20Sopenharmony_ci cable->enabled = true; 958c2ecf20Sopenharmony_ci data &= ~OTGSC_BSVIE; 968c2ecf20Sopenharmony_ci } else if (mask & OTGSC_BSVIE) { 978c2ecf20Sopenharmony_ci cable->enabled = false; 988c2ecf20Sopenharmony_ci } 998c2ecf20Sopenharmony_ci } 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci cable = &ci->platdata->id_extcon; 1028c2ecf20Sopenharmony_ci if (!IS_ERR(cable->edev) || ci->role_switch) { 1038c2ecf20Sopenharmony_ci if (data & mask & OTGSC_IDIS) 1048c2ecf20Sopenharmony_ci cable->changed = false; 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci /* Don't enable id interrupt if using external notifier */ 1078c2ecf20Sopenharmony_ci if (data & mask & OTGSC_IDIE) { 1088c2ecf20Sopenharmony_ci cable->enabled = true; 1098c2ecf20Sopenharmony_ci data &= ~OTGSC_IDIE; 1108c2ecf20Sopenharmony_ci } else if (mask & OTGSC_IDIE) { 1118c2ecf20Sopenharmony_ci cable->enabled = false; 1128c2ecf20Sopenharmony_ci } 1138c2ecf20Sopenharmony_ci } 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci hw_write(ci, OP_OTGSC, mask | OTGSC_INT_STATUS_BITS, data); 1168c2ecf20Sopenharmony_ci} 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci/** 1198c2ecf20Sopenharmony_ci * ci_otg_role - pick role based on ID pin state 1208c2ecf20Sopenharmony_ci * @ci: the controller 1218c2ecf20Sopenharmony_ci */ 1228c2ecf20Sopenharmony_cienum ci_role ci_otg_role(struct ci_hdrc *ci) 1238c2ecf20Sopenharmony_ci{ 1248c2ecf20Sopenharmony_ci enum ci_role role = hw_read_otgsc(ci, OTGSC_ID) 1258c2ecf20Sopenharmony_ci ? CI_ROLE_GADGET 1268c2ecf20Sopenharmony_ci : CI_ROLE_HOST; 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci return role; 1298c2ecf20Sopenharmony_ci} 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_civoid ci_handle_vbus_change(struct ci_hdrc *ci) 1328c2ecf20Sopenharmony_ci{ 1338c2ecf20Sopenharmony_ci if (!ci->is_otg) 1348c2ecf20Sopenharmony_ci return; 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci if (hw_read_otgsc(ci, OTGSC_BSV) && !ci->vbus_active) 1378c2ecf20Sopenharmony_ci usb_gadget_vbus_connect(&ci->gadget); 1388c2ecf20Sopenharmony_ci else if (!hw_read_otgsc(ci, OTGSC_BSV) && ci->vbus_active) 1398c2ecf20Sopenharmony_ci usb_gadget_vbus_disconnect(&ci->gadget); 1408c2ecf20Sopenharmony_ci} 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci/** 1438c2ecf20Sopenharmony_ci * When we switch to device mode, the vbus value should be lower 1448c2ecf20Sopenharmony_ci * than OTGSC_BSV before connecting to host. 1458c2ecf20Sopenharmony_ci * 1468c2ecf20Sopenharmony_ci * @ci: the controller 1478c2ecf20Sopenharmony_ci * 1488c2ecf20Sopenharmony_ci * This function returns an error code if timeout 1498c2ecf20Sopenharmony_ci */ 1508c2ecf20Sopenharmony_cistatic int hw_wait_vbus_lower_bsv(struct ci_hdrc *ci) 1518c2ecf20Sopenharmony_ci{ 1528c2ecf20Sopenharmony_ci unsigned long elapse = jiffies + msecs_to_jiffies(5000); 1538c2ecf20Sopenharmony_ci u32 mask = OTGSC_BSV; 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci while (hw_read_otgsc(ci, mask)) { 1568c2ecf20Sopenharmony_ci if (time_after(jiffies, elapse)) { 1578c2ecf20Sopenharmony_ci dev_err(ci->dev, "timeout waiting for %08x in OTGSC\n", 1588c2ecf20Sopenharmony_ci mask); 1598c2ecf20Sopenharmony_ci return -ETIMEDOUT; 1608c2ecf20Sopenharmony_ci } 1618c2ecf20Sopenharmony_ci msleep(20); 1628c2ecf20Sopenharmony_ci } 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci return 0; 1658c2ecf20Sopenharmony_ci} 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_cistatic void ci_handle_id_switch(struct ci_hdrc *ci) 1688c2ecf20Sopenharmony_ci{ 1698c2ecf20Sopenharmony_ci enum ci_role role; 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci mutex_lock(&ci->mutex); 1728c2ecf20Sopenharmony_ci role = ci_otg_role(ci); 1738c2ecf20Sopenharmony_ci if (role != ci->role) { 1748c2ecf20Sopenharmony_ci dev_dbg(ci->dev, "switching from %s to %s\n", 1758c2ecf20Sopenharmony_ci ci_role(ci)->name, ci->roles[role]->name); 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci if (ci->vbus_active && ci->role == CI_ROLE_GADGET) 1788c2ecf20Sopenharmony_ci /* 1798c2ecf20Sopenharmony_ci * vbus disconnect event is lost due to role 1808c2ecf20Sopenharmony_ci * switch occurs during system suspend. 1818c2ecf20Sopenharmony_ci */ 1828c2ecf20Sopenharmony_ci usb_gadget_vbus_disconnect(&ci->gadget); 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci ci_role_stop(ci); 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci if (role == CI_ROLE_GADGET && 1878c2ecf20Sopenharmony_ci IS_ERR(ci->platdata->vbus_extcon.edev)) 1888c2ecf20Sopenharmony_ci /* 1898c2ecf20Sopenharmony_ci * Wait vbus lower than OTGSC_BSV before connecting 1908c2ecf20Sopenharmony_ci * to host. If connecting status is from an external 1918c2ecf20Sopenharmony_ci * connector instead of register, we don't need to 1928c2ecf20Sopenharmony_ci * care vbus on the board, since it will not affect 1938c2ecf20Sopenharmony_ci * external connector status. 1948c2ecf20Sopenharmony_ci */ 1958c2ecf20Sopenharmony_ci hw_wait_vbus_lower_bsv(ci); 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci ci_role_start(ci, role); 1988c2ecf20Sopenharmony_ci /* vbus change may have already occurred */ 1998c2ecf20Sopenharmony_ci if (role == CI_ROLE_GADGET) 2008c2ecf20Sopenharmony_ci ci_handle_vbus_change(ci); 2018c2ecf20Sopenharmony_ci } 2028c2ecf20Sopenharmony_ci mutex_unlock(&ci->mutex); 2038c2ecf20Sopenharmony_ci} 2048c2ecf20Sopenharmony_ci/** 2058c2ecf20Sopenharmony_ci * ci_otg_work - perform otg (vbus/id) event handle 2068c2ecf20Sopenharmony_ci * @work: work struct 2078c2ecf20Sopenharmony_ci */ 2088c2ecf20Sopenharmony_cistatic void ci_otg_work(struct work_struct *work) 2098c2ecf20Sopenharmony_ci{ 2108c2ecf20Sopenharmony_ci struct ci_hdrc *ci = container_of(work, struct ci_hdrc, work); 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci if (ci_otg_is_fsm_mode(ci) && !ci_otg_fsm_work(ci)) { 2138c2ecf20Sopenharmony_ci enable_irq(ci->irq); 2148c2ecf20Sopenharmony_ci return; 2158c2ecf20Sopenharmony_ci } 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci pm_runtime_get_sync(ci->dev); 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci if (ci->id_event) { 2208c2ecf20Sopenharmony_ci ci->id_event = false; 2218c2ecf20Sopenharmony_ci ci_handle_id_switch(ci); 2228c2ecf20Sopenharmony_ci } 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ci if (ci->b_sess_valid_event) { 2258c2ecf20Sopenharmony_ci ci->b_sess_valid_event = false; 2268c2ecf20Sopenharmony_ci ci_handle_vbus_change(ci); 2278c2ecf20Sopenharmony_ci } 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci pm_runtime_put_sync(ci->dev); 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_ci enable_irq(ci->irq); 2328c2ecf20Sopenharmony_ci} 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_ci/** 2368c2ecf20Sopenharmony_ci * ci_hdrc_otg_init - initialize otg struct 2378c2ecf20Sopenharmony_ci * @ci: the controller 2388c2ecf20Sopenharmony_ci */ 2398c2ecf20Sopenharmony_ciint ci_hdrc_otg_init(struct ci_hdrc *ci) 2408c2ecf20Sopenharmony_ci{ 2418c2ecf20Sopenharmony_ci INIT_WORK(&ci->work, ci_otg_work); 2428c2ecf20Sopenharmony_ci ci->wq = create_freezable_workqueue("ci_otg"); 2438c2ecf20Sopenharmony_ci if (!ci->wq) { 2448c2ecf20Sopenharmony_ci dev_err(ci->dev, "can't create workqueue\n"); 2458c2ecf20Sopenharmony_ci return -ENODEV; 2468c2ecf20Sopenharmony_ci } 2478c2ecf20Sopenharmony_ci 2488c2ecf20Sopenharmony_ci if (ci_otg_is_fsm_mode(ci)) 2498c2ecf20Sopenharmony_ci return ci_hdrc_otg_fsm_init(ci); 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci return 0; 2528c2ecf20Sopenharmony_ci} 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_ci/** 2558c2ecf20Sopenharmony_ci * ci_hdrc_otg_destroy - destroy otg struct 2568c2ecf20Sopenharmony_ci * @ci: the controller 2578c2ecf20Sopenharmony_ci */ 2588c2ecf20Sopenharmony_civoid ci_hdrc_otg_destroy(struct ci_hdrc *ci) 2598c2ecf20Sopenharmony_ci{ 2608c2ecf20Sopenharmony_ci if (ci->wq) { 2618c2ecf20Sopenharmony_ci flush_workqueue(ci->wq); 2628c2ecf20Sopenharmony_ci destroy_workqueue(ci->wq); 2638c2ecf20Sopenharmony_ci } 2648c2ecf20Sopenharmony_ci /* Disable all OTG irq and clear status */ 2658c2ecf20Sopenharmony_ci hw_write_otgsc(ci, OTGSC_INT_EN_BITS | OTGSC_INT_STATUS_BITS, 2668c2ecf20Sopenharmony_ci OTGSC_INT_STATUS_BITS); 2678c2ecf20Sopenharmony_ci if (ci_otg_is_fsm_mode(ci)) 2688c2ecf20Sopenharmony_ci ci_hdrc_otg_fsm_remove(ci); 2698c2ecf20Sopenharmony_ci} 270