summaryrefslogtreecommitdiff
path: root/drivers/usb/chipidea/core.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/chipidea/core.c')
-rw-r--r--drivers/usb/chipidea/core.c328
1 files changed, 226 insertions, 102 deletions
diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c
index e96e3a5808b3..068d05dea4f7 100644
--- a/drivers/usb/chipidea/core.c
+++ b/drivers/usb/chipidea/core.c
@@ -461,7 +461,7 @@ void ci_platform_configure(struct ci_hdrc *ci)
*
* This function returns an error code
*/
-static int hw_controller_reset(struct ci_hdrc *ci)
+int hw_controller_reset(struct ci_hdrc *ci)
{
int count = 0;
@@ -555,7 +555,7 @@ static irqreturn_t ci_irq(int irq, void *data)
* and disconnection events.
*/
if (ci->is_otg && (otgsc & OTGSC_BSVIE) && (otgsc & OTGSC_BSVIS)) {
- ci->b_sess_valid_event = true;
+ ci->vbus_glitch_check_event = true;
/* Clear BSV irq */
hw_write_otgsc(ci, OTGSC_BSVIS, OTGSC_BSVIS);
ci_otg_queue_work(ci);
@@ -569,35 +569,14 @@ static irqreturn_t ci_irq(int irq, void *data)
return ret;
}
-static int ci_vbus_notifier(struct notifier_block *nb, unsigned long event,
- void *ptr)
+static int ci_cable_notifier(struct notifier_block *nb, unsigned long event,
+ void *ptr)
{
- struct ci_hdrc_cable *vbus = container_of(nb, struct ci_hdrc_cable, nb);
- struct ci_hdrc *ci = vbus->ci;
+ struct ci_hdrc_cable *cbl = container_of(nb, struct ci_hdrc_cable, nb);
+ struct ci_hdrc *ci = cbl->ci;
- if (event)
- vbus->state = true;
- else
- vbus->state = false;
-
- vbus->changed = true;
-
- ci_irq(ci->irq, ci);
- return NOTIFY_DONE;
-}
-
-static int ci_id_notifier(struct notifier_block *nb, unsigned long event,
- void *ptr)
-{
- struct ci_hdrc_cable *id = container_of(nb, struct ci_hdrc_cable, nb);
- struct ci_hdrc *ci = id->ci;
-
- if (event)
- id->state = false;
- else
- id->state = true;
-
- id->changed = true;
+ cbl->connected = event;
+ cbl->changed = true;
ci_irq(ci->irq, ci);
return NOTIFY_DONE;
@@ -706,27 +685,27 @@ static int ci_get_platdata(struct device *dev,
}
cable = &platdata->vbus_extcon;
- cable->nb.notifier_call = ci_vbus_notifier;
+ cable->nb.notifier_call = ci_cable_notifier;
cable->edev = ext_vbus;
if (!IS_ERR(ext_vbus)) {
- ret = extcon_get_cable_state_(cable->edev, EXTCON_USB);
+ ret = extcon_get_state(cable->edev, EXTCON_USB);
if (ret)
- cable->state = true;
+ cable->connected = true;
else
- cable->state = false;
+ cable->connected = false;
}
cable = &platdata->id_extcon;
- cable->nb.notifier_call = ci_id_notifier;
+ cable->nb.notifier_call = ci_cable_notifier;
cable->edev = ext_id;
if (!IS_ERR(ext_id)) {
- ret = extcon_get_cable_state_(cable->edev, EXTCON_USB_HOST);
+ ret = extcon_get_state(cable->edev, EXTCON_USB_HOST);
if (ret)
- cable->state = false;
+ cable->connected = true;
else
- cable->state = true;
+ cable->connected = false;
}
return 0;
}
@@ -738,9 +717,9 @@ static int ci_extcon_register(struct ci_hdrc *ci)
id = &ci->platdata->id_extcon;
id->ci = ci;
- if (!IS_ERR(id->edev)) {
- ret = extcon_register_notifier(id->edev, EXTCON_USB_HOST,
- &id->nb);
+ if (!IS_ERR_OR_NULL(id->edev)) {
+ ret = devm_extcon_register_notifier(ci->dev, id->edev,
+ EXTCON_USB_HOST, &id->nb);
if (ret < 0) {
dev_err(ci->dev, "register ID failed\n");
return ret;
@@ -749,12 +728,10 @@ static int ci_extcon_register(struct ci_hdrc *ci)
vbus = &ci->platdata->vbus_extcon;
vbus->ci = ci;
- if (!IS_ERR(vbus->edev)) {
- ret = extcon_register_notifier(vbus->edev, EXTCON_USB,
- &vbus->nb);
+ if (!IS_ERR_OR_NULL(vbus->edev)) {
+ ret = devm_extcon_register_notifier(ci->dev, vbus->edev,
+ EXTCON_USB, &vbus->nb);
if (ret < 0) {
- extcon_unregister_notifier(id->edev, EXTCON_USB_HOST,
- &id->nb);
dev_err(ci->dev, "register VBUS failed\n");
return ret;
}
@@ -763,20 +740,6 @@ static int ci_extcon_register(struct ci_hdrc *ci)
return 0;
}
-static void ci_extcon_unregister(struct ci_hdrc *ci)
-{
- struct ci_hdrc_cable *cable;
-
- cable = &ci->platdata->id_extcon;
- if (!IS_ERR(cable->edev))
- extcon_unregister_notifier(cable->edev, EXTCON_USB_HOST,
- &cable->nb);
-
- cable = &ci->platdata->vbus_extcon;
- if (!IS_ERR(cable->edev))
- extcon_unregister_notifier(cable->edev, EXTCON_USB, &cable->nb);
-}
-
static DEFINE_IDA(ci_ida);
struct platform_device *ci_hdrc_add_device(struct device *dev,
@@ -801,9 +764,6 @@ struct platform_device *ci_hdrc_add_device(struct device *dev,
}
pdev->dev.parent = dev;
- pdev->dev.dma_mask = dev->dma_mask;
- pdev->dev.dma_parms = dev->dma_parms;
- dma_set_coherent_mask(&pdev->dev, dev->coherent_dma_mask);
ret = platform_device_add_resources(pdev, res, nres);
if (ret)
@@ -835,12 +795,39 @@ void ci_hdrc_remove_device(struct platform_device *pdev)
}
EXPORT_SYMBOL_GPL(ci_hdrc_remove_device);
+/**
+ * ci_hdrc_query_available_role: get runtime available operation mode
+ *
+ * The glue layer can get current operation mode (host/peripheral/otg)
+ * This function should be called after ci core device has created.
+ *
+ * @pdev: the platform device of ci core.
+ *
+ * Return USB_DR_MODE_XXX.
+ */
+enum usb_dr_mode ci_hdrc_query_available_role(struct platform_device *pdev)
+{
+ struct ci_hdrc *ci = platform_get_drvdata(pdev);
+
+ if (!ci)
+ return USB_DR_MODE_UNKNOWN;
+ if (ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET])
+ return USB_DR_MODE_OTG;
+ else if (ci->roles[CI_ROLE_HOST])
+ return USB_DR_MODE_HOST;
+ else if (ci->roles[CI_ROLE_GADGET])
+ return USB_DR_MODE_PERIPHERAL;
+ else
+ return USB_DR_MODE_UNKNOWN;
+}
+EXPORT_SYMBOL_GPL(ci_hdrc_query_available_role);
+
static inline void ci_role_destroy(struct ci_hdrc *ci)
{
- ci_hdrc_gadget_destroy(ci);
- ci_hdrc_host_destroy(ci);
if (ci->is_otg && ci->roles[CI_ROLE_GADGET])
ci_hdrc_otg_destroy(ci);
+ ci_hdrc_gadget_destroy(ci);
+ ci_hdrc_host_destroy(ci);
}
static void ci_get_otg_capable(struct ci_hdrc *ci)
@@ -859,6 +846,104 @@ static void ci_get_otg_capable(struct ci_hdrc *ci)
}
}
+static enum ci_role ci_get_role(struct ci_hdrc *ci)
+{
+ if (ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET]) {
+ if (ci->is_otg) {
+ hw_write_otgsc(ci, OTGSC_IDIE, OTGSC_IDIE);
+ return ci_otg_role(ci);
+ } else {
+ /*
+ * If the controller is not OTG capable, but support
+ * role switch, the defalt role is gadget, and the
+ * user can switch it through debugfs.
+ */
+ return CI_ROLE_GADGET;
+ }
+ } else {
+ return ci->roles[CI_ROLE_HOST]
+ ? CI_ROLE_HOST
+ : CI_ROLE_GADGET;
+ }
+}
+
+static void ci_start_new_role(struct ci_hdrc *ci)
+{
+ enum ci_role role = ci_get_role(ci);
+
+ if (ci->role != role) {
+ ci_handle_id_switch(ci);
+ } else if (role == CI_ROLE_GADGET) {
+ if (ci->vbus_active)
+ usb_gadget_vbus_disconnect(&ci->gadget);
+ ci_handle_vbus_connected(ci);
+ }
+}
+
+static void ci_power_lost_work(struct work_struct *work)
+{
+ struct ci_hdrc *ci = container_of(work, struct ci_hdrc,
+ power_lost_work);
+
+ pm_runtime_get_sync(ci->dev);
+ if (!ci_otg_is_fsm_mode(ci))
+ ci_start_new_role(ci);
+ else
+ ci_hdrc_otg_fsm_restart(ci);
+ pm_runtime_put_sync(ci->dev);
+ enable_irq(ci->irq);
+}
+
+static ssize_t ci_role_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct ci_hdrc *ci = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%s\n", ci_role(ci)->name);
+}
+
+static ssize_t ci_role_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t n)
+{
+ struct ci_hdrc *ci = dev_get_drvdata(dev);
+ enum ci_role role;
+ int ret;
+
+ if (!(ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET])) {
+ dev_warn(dev, "Current configuration is not dual-role, quit\n");
+ return -EPERM;
+ }
+
+ for (role = CI_ROLE_HOST; role < CI_ROLE_END; role++)
+ if (!strncmp(buf, ci->roles[role]->name,
+ strlen(ci->roles[role]->name)))
+ break;
+
+ if (role == CI_ROLE_END || role == ci->role)
+ return -EINVAL;
+
+ pm_runtime_get_sync(dev);
+ disable_irq(ci->irq);
+ ci_role_stop(ci);
+ ret = ci_role_start(ci, role);
+ if (!ret && ci->role == CI_ROLE_GADGET)
+ ci_handle_vbus_change(ci);
+ enable_irq(ci->irq);
+ pm_runtime_put_sync(dev);
+
+ return (ret == 0) ? n : ret;
+}
+static DEVICE_ATTR(role, 0644, ci_role_show, ci_role_store);
+
+static struct attribute *ci_attrs[] = {
+ &dev_attr_role.attr,
+ NULL,
+};
+
+static struct attribute_group ci_attr_group = {
+ .attrs = ci_attrs,
+};
+
static int ci_hdrc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -978,30 +1063,12 @@ static int ci_hdrc_probe(struct platform_device *pdev)
}
}
- if (ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET]) {
- if (ci->is_otg) {
- ci->role = ci_otg_role(ci);
- /* Enable ID change irq */
- hw_write_otgsc(ci, OTGSC_IDIE, OTGSC_IDIE);
- } else {
- /*
- * If the controller is not OTG capable, but support
- * role switch, the defalt role is gadget, and the
- * user can switch it through debugfs.
- */
- ci->role = CI_ROLE_GADGET;
- }
- } else {
- ci->role = ci->roles[CI_ROLE_HOST]
- ? CI_ROLE_HOST
- : CI_ROLE_GADGET;
- }
+ ci->role = ci_get_role(ci);
+ /* only update vbus status for peripheral */
+ if (ci->role == CI_ROLE_GADGET)
+ ci_handle_vbus_connected(ci);
if (!ci_otg_is_fsm_mode(ci)) {
- /* only update vbus status for peripheral */
- if (ci->role == CI_ROLE_GADGET)
- ci_handle_vbus_change(ci);
-
ret = ci_role_start(ci, ci->role);
if (ret) {
dev_err(dev, "can't start %s role\n",
@@ -1033,11 +1100,22 @@ static int ci_hdrc_probe(struct platform_device *pdev)
device_set_wakeup_capable(&pdev->dev, true);
+ /* Init workqueue for controller power lost handling */
+ INIT_WORK(&ci->power_lost_work, ci_power_lost_work);
+ mutex_init(&ci->mutex);
+
ret = dbg_create_files(ci);
- if (!ret)
- return 0;
+ if (ret)
+ goto stop;
+
+ ret = sysfs_create_group(&dev->kobj, &ci_attr_group);
+ if (ret)
+ goto remove_debug;
- ci_extcon_unregister(ci);
+ return 0;
+
+remove_debug:
+ dbg_remove_files(ci);
stop:
if (ci->is_otg && ci->roles[CI_ROLE_GADGET])
ci_hdrc_otg_destroy(ci);
@@ -1062,7 +1140,7 @@ static int ci_hdrc_remove(struct platform_device *pdev)
}
dbg_remove_files(ci);
- ci_extcon_unregister(ci);
+ sysfs_remove_group(&ci->dev->kobj, &ci_attr_group);
ci_role_destroy(ci);
ci_hdrc_enter_lpm(ci, true);
ci_usb_phy_exit(ci);
@@ -1088,13 +1166,10 @@ static void ci_otg_fsm_wakeup_by_srp(struct ci_hdrc *ci)
{
if ((ci->fsm.otg->state == OTG_STATE_A_IDLE) &&
(ci->fsm.a_bus_drop == 1) && (ci->fsm.a_bus_req == 0)) {
- if (!hw_read_otgsc(ci, OTGSC_ID)) {
- ci->fsm.a_srp_det = 1;
- ci->fsm.a_bus_drop = 0;
- } else {
+ if (!hw_read_otgsc(ci, OTGSC_ID))
+ otg_add_timer(&ci->fsm, A_DP_END);
+ else
ci->fsm.id = 1;
- }
- ci_otg_queue_work(ci);
}
}
@@ -1110,16 +1185,37 @@ static void ci_controller_suspend(struct ci_hdrc *ci)
enable_irq(ci->irq);
}
+/*
+ * Handle the wakeup interrupt triggered by extcon connector
+ * We need to call ci_irq again for extcon since the first
+ * interrupt (wakeup int) only let the controller be out of
+ * low power mode, but not handle any interrupts.
+ */
+static void ci_extcon_wakeup_int(struct ci_hdrc *ci)
+{
+ struct ci_hdrc_cable *cable_id, *cable_vbus;
+ u32 otgsc = hw_read_otgsc(ci, ~0);
+
+ cable_id = &ci->platdata->id_extcon;
+ cable_vbus = &ci->platdata->vbus_extcon;
+
+ if (!IS_ERR(cable_id->edev) && ci->is_otg &&
+ (otgsc & OTGSC_IDIE) && (otgsc & OTGSC_IDIS))
+ ci_irq(ci->irq, ci);
+
+ if (!IS_ERR(cable_vbus->edev) && ci->is_otg &&
+ (otgsc & OTGSC_BSVIE) && (otgsc & OTGSC_BSVIS))
+ ci_irq(ci->irq, ci);
+}
+
static int ci_controller_resume(struct device *dev)
{
struct ci_hdrc *ci = dev_get_drvdata(dev);
dev_dbg(dev, "at %s\n", __func__);
- if (!ci->in_lpm) {
- WARN_ON(1);
+ if (!ci->in_lpm)
return 0;
- }
ci_hdrc_enter_lpm(ci, false);
if (ci->usb_phy) {
@@ -1136,6 +1232,7 @@ static int ci_controller_resume(struct device *dev)
enable_irq(ci->irq);
if (ci_otg_is_fsm_mode(ci))
ci_otg_fsm_wakeup_by_srp(ci);
+ ci_extcon_wakeup_int(ci);
}
return 0;
@@ -1162,6 +1259,10 @@ static int ci_suspend(struct device *dev)
return 0;
}
+ /* Extra routine per role before system suspend */
+ if (ci->role != CI_ROLE_END && ci_role(ci)->suspend)
+ ci_role(ci)->suspend(ci);
+
if (device_may_wakeup(dev)) {
if (ci_otg_is_fsm_mode(ci))
ci_otg_fsm_suspend_for_srp(ci);
@@ -1178,8 +1279,18 @@ static int ci_suspend(struct device *dev)
static int ci_resume(struct device *dev)
{
struct ci_hdrc *ci = dev_get_drvdata(dev);
+ bool power_lost = false;
+ u32 sample_reg_val;
int ret;
+ /* Check if controller resume from power lost */
+ sample_reg_val = hw_read(ci, OP_ENDPTLISTADDR, ~0);
+ if (sample_reg_val == 0)
+ power_lost = true;
+ else if (sample_reg_val == 0xFFFFFFFF)
+ /* Restore value 0 if it was set for power lost check */
+ hw_write(ci, OP_ENDPTLISTADDR, ~0, 0);
+
if (device_may_wakeup(dev))
disable_irq_wake(ci->irq);
@@ -1187,6 +1298,21 @@ static int ci_resume(struct device *dev)
if (ret)
return ret;
+ if (power_lost) {
+ /* shutdown and re-init for phy */
+ ci_usb_phy_exit(ci);
+ ci_usb_phy_init(ci);
+ }
+
+ /* Extra routine per role after system resume */
+ if (ci->role != CI_ROLE_END && ci_role(ci)->resume)
+ ci_role(ci)->resume(ci, power_lost);
+
+ if (power_lost) {
+ disable_irq_nosync(ci->irq);
+ schedule_work(&ci->power_lost_work);
+ }
+
if (ci->supports_runtime_pm) {
pm_runtime_disable(dev);
pm_runtime_set_active(dev);
@@ -1203,10 +1329,8 @@ static int ci_runtime_suspend(struct device *dev)
dev_dbg(dev, "at %s\n", __func__);
- if (ci->in_lpm) {
- WARN_ON(1);
+ if (ci->in_lpm)
return 0;
- }
if (ci_otg_is_fsm_mode(ci))
ci_otg_fsm_suspend_for_srp(ci);