summaryrefslogtreecommitdiff
path: root/drivers/phy/phy-sun4i-usb.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/phy/phy-sun4i-usb.c')
-rw-r--r--drivers/phy/phy-sun4i-usb.c44
1 files changed, 36 insertions, 8 deletions
diff --git a/drivers/phy/phy-sun4i-usb.c b/drivers/phy/phy-sun4i-usb.c
index ddc399536c5c..4ba9856ac71a 100644
--- a/drivers/phy/phy-sun4i-usb.c
+++ b/drivers/phy/phy-sun4i-usb.c
@@ -22,6 +22,7 @@
*/
#include <linux/clk.h>
+#include <linux/delay.h>
#include <linux/err.h>
#include <linux/extcon.h>
#include <linux/io.h>
@@ -309,7 +310,7 @@ static int sun4i_usb_phy_power_on(struct phy *_phy)
phy->regulator_on = true;
/* We must report Vbus high within OTG_TIME_A_WAIT_VRISE msec. */
- if (phy->index == 0 && data->phy0_poll)
+ if (phy->index == 0 && data->vbus_det_gpio && data->phy0_poll)
mod_delayed_work(system_wq, &data->detect, DEBOUNCE_TIME);
return 0;
@@ -330,7 +331,7 @@ static int sun4i_usb_phy_power_off(struct phy *_phy)
* phy0 vbus typically slowly discharges, sometimes this causes the
* Vbus gpio to not trigger an edge irq on Vbus off, so force a rescan.
*/
- if (phy->index == 0 && !data->phy0_poll)
+ if (phy->index == 0 && data->vbus_det_gpio && !data->phy0_poll)
mod_delayed_work(system_wq, &data->detect, POLL_TIME);
return 0;
@@ -359,7 +360,10 @@ static void sun4i_usb_phy0_id_vbus_det_scan(struct work_struct *work)
int id_det, vbus_det, id_notify = 0, vbus_notify = 0;
id_det = gpiod_get_value_cansleep(data->id_det_gpio);
- vbus_det = gpiod_get_value_cansleep(data->vbus_det_gpio);
+ if (data->vbus_det_gpio)
+ vbus_det = gpiod_get_value_cansleep(data->vbus_det_gpio);
+ else
+ vbus_det = 1; /* Report vbus as high */
mutex_lock(&phy0->mutex);
@@ -369,6 +373,16 @@ static void sun4i_usb_phy0_id_vbus_det_scan(struct work_struct *work)
}
if (id_det != data->id_det) {
+ /*
+ * When a host cable (id == 0) gets plugged in on systems
+ * without vbus detection report vbus low for long enough for
+ * the musb-ip to end the current device session.
+ */
+ if (!data->vbus_det_gpio && id_det == 0) {
+ sun4i_usb_phy0_set_vbus_detect(phy0, 0);
+ msleep(200);
+ sun4i_usb_phy0_set_vbus_detect(phy0, 1);
+ }
sun4i_usb_phy0_set_id_detect(phy0, id_det);
data->id_det = id_det;
id_notify = 1;
@@ -382,9 +396,22 @@ static void sun4i_usb_phy0_id_vbus_det_scan(struct work_struct *work)
mutex_unlock(&phy0->mutex);
- if (id_notify)
+ if (id_notify) {
extcon_set_cable_state_(data->extcon, EXTCON_USB_HOST,
!id_det);
+ /*
+ * When a host cable gets unplugged (id == 1) on systems
+ * without vbus detection report vbus low for long enough to
+ * the musb-ip to end the current host session.
+ */
+ if (!data->vbus_det_gpio && id_det == 1) {
+ mutex_lock(&phy0->mutex);
+ sun4i_usb_phy0_set_vbus_detect(phy0, 0);
+ msleep(1000);
+ sun4i_usb_phy0_set_vbus_detect(phy0, 1);
+ mutex_unlock(&phy0->mutex);
+ }
+ }
if (vbus_notify)
extcon_set_cable_state_(data->extcon, EXTCON_USB, vbus_det);
@@ -495,9 +522,9 @@ static int sun4i_usb_phy_probe(struct platform_device *pdev)
data->vbus_det_gpio = NULL;
}
- /* We either want both gpio pins or neither (when in host mode) */
- if (!data->id_det_gpio != !data->vbus_det_gpio) {
- dev_err(dev, "failed to get id or vbus detect pin\n");
+ /* vbus_det without id_det makes no sense, and is not supported */
+ if (data->vbus_det_gpio && !data->id_det_gpio) {
+ dev_err(dev, "usb0_id_det missing or invalid\n");
return -ENODEV;
}
@@ -565,7 +592,8 @@ static int sun4i_usb_phy_probe(struct platform_device *pdev)
data->id_det_irq = gpiod_to_irq(data->id_det_gpio);
data->vbus_det_irq = gpiod_to_irq(data->vbus_det_gpio);
- if (data->id_det_irq < 0 || data->vbus_det_irq < 0)
+ if ((data->id_det_gpio && data->id_det_irq < 0) ||
+ (data->vbus_det_gpio && data->vbus_det_irq < 0))
data->phy0_poll = true;
if (data->id_det_irq >= 0) {