summaryrefslogtreecommitdiff
path: root/drivers/usb
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb')
-rw-r--r--drivers/usb/chipidea/bits.h4
-rw-r--r--drivers/usb/chipidea/ci.h45
-rw-r--r--drivers/usb/chipidea/ci_hdrc_imx.c90
-rw-r--r--drivers/usb/chipidea/ci_hdrc_imx.h4
-rw-r--r--drivers/usb/chipidea/core.c138
-rw-r--r--drivers/usb/chipidea/host.c165
-rw-r--r--drivers/usb/chipidea/otg.c2
-rw-r--r--drivers/usb/chipidea/otg.h1
-rw-r--r--drivers/usb/chipidea/udc.c50
-rw-r--r--drivers/usb/chipidea/usbmisc_imx.c196
-rw-r--r--drivers/usb/core/hcd.c10
-rw-r--r--drivers/usb/core/hub.c352
-rw-r--r--drivers/usb/core/message.c29
-rw-r--r--drivers/usb/core/otg_productlist.h73
-rw-r--r--drivers/usb/core/usb.h4
-rw-r--r--drivers/usb/dwc3/core.c214
-rw-r--r--drivers/usb/dwc3/core.h38
-rw-r--r--drivers/usb/dwc3/drd.c15
-rw-r--r--drivers/usb/dwc3/dwc3-imx8mp.c52
-rw-r--r--drivers/usb/dwc3/gadget.c4
-rw-r--r--drivers/usb/dwc3/host.c57
-rw-r--r--drivers/usb/host/ehci-hcd.c2
-rw-r--r--drivers/usb/host/xhci-hub.c13
-rw-r--r--drivers/usb/host/xhci-plat.c7
-rw-r--r--drivers/usb/host/xhci-ring.c130
-rw-r--r--drivers/usb/host/xhci.c5
-rw-r--r--drivers/usb/host/xhci.h12
-rw-r--r--drivers/usb/phy/Kconfig2
-rw-r--r--drivers/usb/phy/phy-mxs-usb.c477
-rw-r--r--drivers/usb/typec/mux/Kconfig6
-rw-r--r--drivers/usb/typec/mux/Makefile1
-rw-r--r--drivers/usb/typec/mux/gpio-switch.c118
-rw-r--r--drivers/usb/typec/tcpm/tcpci.c88
-rw-r--r--drivers/usb/typec/tcpm/tcpm.c34
34 files changed, 2000 insertions, 438 deletions
diff --git a/drivers/usb/chipidea/bits.h b/drivers/usb/chipidea/bits.h
index b1540ce93264..cf8dbd586fe2 100644
--- a/drivers/usb/chipidea/bits.h
+++ b/drivers/usb/chipidea/bits.h
@@ -70,6 +70,9 @@
#define PORTSC_FPR BIT(6)
#define PORTSC_SUSP BIT(7)
#define PORTSC_HSP BIT(9)
+#define PORTSC_LS (BIT(11) | BIT(10))
+#define PORTSC_LS_J BIT(11)
+#define PORTSC_LS_K BIT(10)
#define PORTSC_PP BIT(12)
#define PORTSC_PTC (0x0FUL << 16)
#define PORTSC_WKCN BIT(20)
@@ -78,6 +81,7 @@
#define PORTSC_PFSC BIT(24)
#define PORTSC_PTS(d) \
(u32)((((d) & 0x3) << 30) | (((d) & 0x4) ? BIT(25) : 0))
+#define PORT_SPEED_LOW(d) ((((d) >> 26) & 0x3) == 1)
#define PORTSC_PTW BIT(28)
#define PORTSC_STS BIT(29)
diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h
index 50e37846f037..3c8e3640acd1 100644
--- a/drivers/usb/chipidea/ci.h
+++ b/drivers/usb/chipidea/ci.h
@@ -126,12 +126,16 @@ enum ci_revision {
* struct ci_role_driver - host/gadget role driver
* @start: start this role
* @stop: stop this role
+ * @suspend: system suspend handler for this role
+ * @resume: system resume handler for this role
* @irq: irq handler for this role
* @name: role name string (host/gadget)
*/
struct ci_role_driver {
int (*start)(struct ci_hdrc *);
void (*stop)(struct ci_hdrc *);
+ void (*suspend)(struct ci_hdrc *);
+ void (*resume)(struct ci_hdrc *, bool power_lost);
irqreturn_t (*irq)(struct ci_hdrc *);
const char *name;
};
@@ -203,7 +207,9 @@ struct hw_bank {
* @in_lpm: if the core in low power mode
* @wakeup_int: if wakeup interrupt occur
* @rev: The revision number for controller
- * @mutex: protect code from concorrent running when doing role switch
+ * @power_lost_work: work item when controller power is lost
+ * @power_lost_wq: work queue for controller power is lost
+ * @mutex: protect code from concorrent running
*/
struct ci_hdrc {
struct device *dev;
@@ -256,7 +262,20 @@ struct ci_hdrc {
bool in_lpm;
bool wakeup_int;
enum ci_revision rev;
- struct mutex mutex;
+ struct work_struct power_lost_work;
+ struct workqueue_struct *power_lost_wq;
+ /* register save area for suspend&resume */
+ u32 pm_command;
+ u32 pm_status;
+ u32 pm_intr_enable;
+ u32 pm_frame_index;
+ u32 pm_segment;
+ u32 pm_frame_list;
+ u32 pm_async_next;
+ u32 pm_configured_flag;
+ u32 pm_portsc;
+ u32 pm_usbmode;
+ struct mutex mutex;
};
static inline struct ci_role_driver *ci_role(struct ci_hdrc *ci)
@@ -276,9 +295,21 @@ static inline int ci_role_start(struct ci_hdrc *ci, enum ci_role role)
return -ENXIO;
ret = ci->roles[role]->start(ci);
- if (!ret)
- ci->role = role;
- return ret;
+ if (ret)
+ return ret;
+
+ ci->role = role;
+
+ if (ci->usb_phy) {
+ if (role == CI_ROLE_HOST)
+ usb_phy_set_mode(ci->usb_phy,
+ CUR_USB_MODE_HOST);
+ else
+ usb_phy_set_mode(ci->usb_phy,
+ CUR_USB_MODE_DEVICE);
+ }
+
+ return 0;
}
static inline void ci_role_stop(struct ci_hdrc *ci)
@@ -291,6 +322,9 @@ static inline void ci_role_stop(struct ci_hdrc *ci)
ci->role = CI_ROLE_END;
ci->roles[role]->stop(ci);
+
+ if (ci->usb_phy)
+ usb_phy_set_mode(ci->usb_phy, CUR_USB_MODE_NONE);
}
static inline enum usb_role ci_role_to_usb_role(struct ci_hdrc *ci)
@@ -468,4 +502,5 @@ void ci_platform_configure(struct ci_hdrc *ci);
void dbg_create_files(struct ci_hdrc *ci);
void dbg_remove_files(struct ci_hdrc *ci);
+void ci_hdrc_enter_lpm(struct ci_hdrc *ci, bool enable);
#endif /* __DRIVERS_USB_CHIPIDEA_CI_H */
diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c
index d8efa90479e2..b0372a3c0ec9 100644
--- a/drivers/usb/chipidea/ci_hdrc_imx.c
+++ b/drivers/usb/chipidea/ci_hdrc_imx.c
@@ -14,6 +14,7 @@
#include <linux/clk.h>
#include <linux/pinctrl/consumer.h>
#include <linux/pm_qos.h>
+#include <linux/busfreq-imx.h>
#include "ci.h"
#include "ci_hdrc_imx.h"
@@ -93,6 +94,7 @@ struct ci_hdrc_imx_data {
struct usb_phy *phy;
struct platform_device *ci_pdev;
struct clk *clk;
+ struct clk *clk_wakeup;
struct imx_usbmisc_data *usbmisc_data;
bool supports_runtime_pm;
bool override_phy_control;
@@ -193,7 +195,7 @@ static int imx_get_clks(struct device *dev)
data->clk_ipg = devm_clk_get(dev, "ipg");
if (IS_ERR(data->clk_ipg)) {
- /* If the platform only needs one clocks */
+ /* If the platform only needs one primary clock */
data->clk = devm_clk_get(dev, NULL);
if (IS_ERR(data->clk)) {
ret = PTR_ERR(data->clk);
@@ -202,6 +204,18 @@ static int imx_get_clks(struct device *dev)
PTR_ERR(data->clk), PTR_ERR(data->clk_ipg));
return ret;
}
+ /* Get wakeup clock. Not all of the platforms need to
+ * handle this clock. So make it optional.
+ */
+ data->clk_wakeup = devm_clk_get_optional(dev,
+ "usb_wakeup_clk");
+ if (IS_ERR(data->clk_wakeup)) {
+ ret = PTR_ERR(data->clk_wakeup);
+ dev_err(dev,
+ "Failed to get wakeup clk, err=%ld\n",
+ PTR_ERR(data->clk_wakeup));
+ return ret;
+ }
return ret;
}
@@ -416,6 +430,7 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev)
if (pdata.flags & CI_HDRC_PMQOS)
cpu_latency_qos_add_request(&data->pm_qos_req, 0);
+ request_bus_freq(BUS_FREQ_HIGH);
ret = imx_get_clks(dev);
if (ret)
goto disable_hsic_regulator;
@@ -424,6 +439,10 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev)
if (ret)
goto disable_hsic_regulator;
+ ret = clk_prepare_enable(data->clk_wakeup);
+ if (ret)
+ goto err_wakeup_clk;
+
data->phy = devm_usb_get_phy_by_phandle(dev, "fsl,usbphy", 0);
if (IS_ERR(data->phy)) {
ret = PTR_ERR(data->phy);
@@ -454,6 +473,11 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev)
if (pdata.flags & CI_HDRC_SUPPORTS_RUNTIME_PM)
data->supports_runtime_pm = true;
+ if (of_find_property(np, "ci-disable-lpm", NULL)) {
+ data->supports_runtime_pm = false;
+ pdata.flags &= ~CI_HDRC_SUPPORTS_RUNTIME_PM;
+ }
+
ret = imx_usbmisc_init(data->usbmisc_data);
if (ret) {
dev_err(dev, "usbmisc init failed, ret=%d\n", ret);
@@ -503,8 +527,11 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev)
disable_device:
ci_hdrc_remove_device(data->ci_pdev);
err_clk:
+ clk_disable_unprepare(data->clk_wakeup);
+err_wakeup_clk:
imx_disable_unprepare_clks(dev);
disable_hsic_regulator:
+ release_bus_freq(BUS_FREQ_HIGH);
if (data->hsic_pad_regulator)
/* don't overwrite original ret (cf. EPROBE_DEFER) */
regulator_disable(data->hsic_pad_regulator);
@@ -529,6 +556,8 @@ static int ci_hdrc_imx_remove(struct platform_device *pdev)
usb_phy_shutdown(data->phy);
if (data->ci_pdev) {
imx_disable_unprepare_clks(&pdev->dev);
+ clk_disable_unprepare(data->clk_wakeup);
+ release_bus_freq(BUS_FREQ_HIGH);
if (data->plat_data->flags & CI_HDRC_PMQOS)
cpu_latency_qos_remove_request(&data->pm_qos_req);
if (data->hsic_pad_regulator)
@@ -543,20 +572,24 @@ static void ci_hdrc_imx_shutdown(struct platform_device *pdev)
ci_hdrc_imx_remove(pdev);
}
-static int __maybe_unused imx_controller_suspend(struct device *dev)
+static int __maybe_unused imx_controller_suspend(struct device *dev,
+ pm_message_t msg)
{
struct ci_hdrc_imx_data *data = dev_get_drvdata(dev);
int ret = 0;
dev_dbg(dev, "at %s\n", __func__);
- ret = imx_usbmisc_hsic_set_clk(data->usbmisc_data, false);
+ ret = imx_usbmisc_suspend(data->usbmisc_data,
+ PMSG_IS_AUTO(msg) || device_may_wakeup(dev));
if (ret) {
- dev_err(dev, "usbmisc hsic_set_clk failed, ret=%d\n", ret);
+ dev_err(dev,
+ "usbmisc suspend failed, ret=%d\n", ret);
return ret;
}
imx_disable_unprepare_clks(dev);
+ release_bus_freq(BUS_FREQ_HIGH);
if (data->plat_data->flags & CI_HDRC_PMQOS)
cpu_latency_qos_remove_request(&data->pm_qos_req);
@@ -565,43 +598,36 @@ static int __maybe_unused imx_controller_suspend(struct device *dev)
return 0;
}
-static int __maybe_unused imx_controller_resume(struct device *dev)
+static int __maybe_unused imx_controller_resume(struct device *dev,
+ pm_message_t msg)
{
struct ci_hdrc_imx_data *data = dev_get_drvdata(dev);
int ret = 0;
dev_dbg(dev, "at %s\n", __func__);
- if (!data->in_lpm) {
- WARN_ON(1);
+ if (!data->in_lpm)
return 0;
- }
if (data->plat_data->flags & CI_HDRC_PMQOS)
cpu_latency_qos_add_request(&data->pm_qos_req, 0);
+ request_bus_freq(BUS_FREQ_HIGH);
ret = imx_prepare_enable_clks(dev);
if (ret)
return ret;
data->in_lpm = false;
- ret = imx_usbmisc_set_wakeup(data->usbmisc_data, false);
+ ret = imx_usbmisc_resume(data->usbmisc_data,
+ PMSG_IS_AUTO(msg) || device_may_wakeup(dev));
if (ret) {
- dev_err(dev, "usbmisc set_wakeup failed, ret=%d\n", ret);
+ dev_err(dev, "usbmisc resume failed, ret=%d\n", ret);
goto clk_disable;
}
- ret = imx_usbmisc_hsic_set_clk(data->usbmisc_data, true);
- if (ret) {
- dev_err(dev, "usbmisc hsic_set_clk failed, ret=%d\n", ret);
- goto hsic_set_clk_fail;
- }
-
return 0;
-hsic_set_clk_fail:
- imx_usbmisc_set_wakeup(data->usbmisc_data, true);
clk_disable:
imx_disable_unprepare_clks(dev);
return ret;
@@ -617,16 +643,7 @@ static int __maybe_unused ci_hdrc_imx_suspend(struct device *dev)
/* The core's suspend doesn't run */
return 0;
- if (device_may_wakeup(dev)) {
- ret = imx_usbmisc_set_wakeup(data->usbmisc_data, true);
- if (ret) {
- dev_err(dev, "usbmisc set_wakeup failed, ret=%d\n",
- ret);
- return ret;
- }
- }
-
- ret = imx_controller_suspend(dev);
+ ret = imx_controller_suspend(dev, PMSG_SUSPEND);
if (ret)
return ret;
@@ -640,7 +657,7 @@ static int __maybe_unused ci_hdrc_imx_resume(struct device *dev)
int ret;
pinctrl_pm_select_default_state(dev);
- ret = imx_controller_resume(dev);
+ ret = imx_controller_resume(dev, PMSG_RESUME);
if (!ret && data->supports_runtime_pm) {
pm_runtime_disable(dev);
pm_runtime_set_active(dev);
@@ -653,25 +670,16 @@ static int __maybe_unused ci_hdrc_imx_resume(struct device *dev)
static int __maybe_unused ci_hdrc_imx_runtime_suspend(struct device *dev)
{
struct ci_hdrc_imx_data *data = dev_get_drvdata(dev);
- int ret;
- if (data->in_lpm) {
- WARN_ON(1);
+ if (data->in_lpm)
return 0;
- }
-
- ret = imx_usbmisc_set_wakeup(data->usbmisc_data, true);
- if (ret) {
- dev_err(dev, "usbmisc set_wakeup failed, ret=%d\n", ret);
- return ret;
- }
- return imx_controller_suspend(dev);
+ return imx_controller_suspend(dev, PMSG_AUTO_SUSPEND);
}
static int __maybe_unused ci_hdrc_imx_runtime_resume(struct device *dev)
{
- return imx_controller_resume(dev);
+ return imx_controller_resume(dev, PMSG_AUTO_RESUME);
}
static const struct dev_pm_ops ci_hdrc_imx_pm_ops = {
diff --git a/drivers/usb/chipidea/ci_hdrc_imx.h b/drivers/usb/chipidea/ci_hdrc_imx.h
index 999c65390b7f..cbd580e2bba6 100644
--- a/drivers/usb/chipidea/ci_hdrc_imx.h
+++ b/drivers/usb/chipidea/ci_hdrc_imx.h
@@ -32,9 +32,9 @@ struct imx_usbmisc_data {
int imx_usbmisc_init(struct imx_usbmisc_data *data);
int imx_usbmisc_init_post(struct imx_usbmisc_data *data);
-int imx_usbmisc_set_wakeup(struct imx_usbmisc_data *data, bool enabled);
int imx_usbmisc_hsic_set_connect(struct imx_usbmisc_data *data);
-int imx_usbmisc_hsic_set_clk(struct imx_usbmisc_data *data, bool on);
int imx_usbmisc_charger_detection(struct imx_usbmisc_data *data, bool connect);
+int imx_usbmisc_suspend(struct imx_usbmisc_data *data, bool wakeup);
+int imx_usbmisc_resume(struct imx_usbmisc_data *data, bool wakeup);
#endif /* __DRIVER_USB_CHIPIDEA_CI_HDRC_IMX_H */
diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c
index 0e8f4aa031f8..9cc22036349f 100644
--- a/drivers/usb/chipidea/core.c
+++ b/drivers/usb/chipidea/core.c
@@ -208,7 +208,7 @@ static void ci_hdrc_enter_lpm_common(struct ci_hdrc *ci, bool enable)
0);
}
-static void ci_hdrc_enter_lpm(struct ci_hdrc *ci, bool enable)
+void ci_hdrc_enter_lpm(struct ci_hdrc *ci, bool enable)
{
return ci->platdata->enter_lpm(ci, enable);
}
@@ -455,7 +455,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;
@@ -521,6 +521,13 @@ static irqreturn_t ci_irq_handler(int irq, void *data)
u32 otgsc = 0;
if (ci->in_lpm) {
+ /*
+ * If we already have a wakeup irq pending there,
+ * let's just return to wait resume finished firstly.
+ */
+ if (ci->wakeup_int)
+ return IRQ_HANDLED;
+
disable_irq_nosync(irq);
ci->wakeup_int = true;
pm_runtime_get(ci->dev);
@@ -1004,6 +1011,54 @@ static struct attribute *ci_attrs[] = {
};
ATTRIBUTE_GROUPS(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);
+ if (hw_read_otgsc(ci, OTGSC_BSV))
+ usb_gadget_vbus_connect(&ci->gadget);
+ }
+}
+
+static void ci_power_lost_work(struct work_struct *work)
+{
+ struct ci_hdrc *ci = container_of(work, struct ci_hdrc,
+ power_lost_work);
+
+ disable_irq_nosync(ci->irq);
+ pm_runtime_get_sync(ci->dev);
+ if (!ci_otg_is_fsm_mode(ci))
+ ci_start_new_role(ci);
+ pm_runtime_put_sync(ci->dev);
+ enable_irq(ci->irq);
+}
+
static int ci_hdrc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -1157,25 +1212,7 @@ 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);
if (!ci_otg_is_fsm_mode(ci)) {
/* only update vbus status for peripheral */
if (ci->role == CI_ROLE_GADGET) {
@@ -1212,11 +1249,22 @@ static int ci_hdrc_probe(struct platform_device *pdev)
if (ci_otg_is_fsm_mode(ci))
ci_hdrc_otg_fsm_start(ci);
+ /* Init workqueue for controller power lost handling */
+ ci->power_lost_wq = create_freezable_workqueue("ci_power_lost");
+ if (!ci->power_lost_wq) {
+ dev_err(ci->dev, "can't create power_lost workqueue\n");
+ goto remove_debug;
+ }
+
+ INIT_WORK(&ci->power_lost_work, ci_power_lost_work);
device_set_wakeup_capable(&pdev->dev, true);
dbg_create_files(ci);
+ mutex_init(&ci->mutex);
return 0;
+remove_debug:
+ dbg_remove_files(ci);
stop:
if (ci->role_switch)
usb_role_switch_unregister(ci->role_switch);
@@ -1248,6 +1296,8 @@ static int ci_hdrc_remove(struct platform_device *pdev)
pm_runtime_put_noidle(&pdev->dev);
}
+ flush_workqueue(ci->power_lost_wq);
+ destroy_workqueue(ci->power_lost_wq);
dbg_remove_files(ci);
ci_role_destroy(ci);
ci_hdrc_enter_lpm(ci, true);
@@ -1311,12 +1361,14 @@ static void ci_extcon_wakeup_int(struct ci_hdrc *ci)
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))
+ if ((!IS_ERR(cable_id->edev) || ci->role_switch)
+ && ci->is_otg && (otgsc & OTGSC_IDIE)
+ && (otgsc & OTGSC_IDIS))
ci_irq(ci);
- if (!IS_ERR(cable_vbus->edev) && ci->is_otg &&
- (otgsc & OTGSC_BSVIE) && (otgsc & OTGSC_BSVIS))
+ if ((!IS_ERR(cable_vbus->edev) || ci->role_switch)
+ && ci->is_otg && (otgsc & OTGSC_BSVIE)
+ && (otgsc & OTGSC_BSVIS))
ci_irq(ci);
}
@@ -1327,10 +1379,8 @@ static int ci_controller_resume(struct device *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);
@@ -1363,6 +1413,7 @@ static int ci_suspend(struct device *dev)
{
struct ci_hdrc *ci = dev_get_drvdata(dev);
+ flush_workqueue(ci->power_lost_wq);
if (ci->wq)
flush_workqueue(ci->wq);
/*
@@ -1379,6 +1430,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);
@@ -1396,6 +1451,16 @@ static int ci_resume(struct device *dev)
{
struct ci_hdrc *ci = dev_get_drvdata(dev);
int ret;
+ bool power_lost = false;
+ u32 sample_reg_val;
+
+ /* 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);
@@ -1404,6 +1469,19 @@ 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)
+ queue_work(ci->power_lost_wq, &ci->power_lost_work);
+
if (ci->supports_runtime_pm) {
pm_runtime_disable(dev);
pm_runtime_set_active(dev);
@@ -1420,10 +1498,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);
diff --git a/drivers/usb/chipidea/host.c b/drivers/usb/chipidea/host.c
index 786ddb3c3289..02d3a9de6255 100644
--- a/drivers/usb/chipidea/host.c
+++ b/drivers/usb/chipidea/host.c
@@ -23,6 +23,7 @@
static struct hc_driver __read_mostly ci_ehci_hc_driver;
static int (*orig_bus_suspend)(struct usb_hcd *hcd);
+static int (*orig_bus_resume)(struct usb_hcd *hcd);
struct ehci_ci_priv {
struct regulator *reg_vbus;
@@ -241,7 +242,7 @@ static int ci_ehci_hub_control(
struct ehci_hcd *ehci = hcd_to_ehci(hcd);
unsigned int ports = HCS_N_PORTS(ehci->hcs_params);
u32 __iomem *status_reg;
- u32 temp, port_index;
+ u32 temp, port_index, suspend_line_state;
unsigned long flags;
int retval = 0;
bool done = false;
@@ -285,6 +286,17 @@ static int ci_ehci_hub_control(
PORT_SUSPEND, 5000))
ehci_err(ehci, "timeout waiting for SUSPEND\n");
+ if (ci->platdata->flags & CI_HDRC_HOST_SUSP_PHY_LPM) {
+ if (PORT_SPEED_LOW(temp))
+ suspend_line_state = PORTSC_LS_K;
+ else
+ suspend_line_state = PORTSC_LS_J;
+ if (!ehci_handshake(ehci, status_reg, PORTSC_LS,
+ suspend_line_state, 5000))
+ ci_hdrc_enter_lpm(ci, true);
+ }
+
+
if (ci->platdata->flags & CI_HDRC_IMX_IS_HSIC) {
if (ci->platdata->notify_event)
ci->platdata->notify_event(ci,
@@ -295,6 +307,14 @@ static int ci_ehci_hub_control(
ehci_writel(ehci, temp, status_reg);
}
+ spin_unlock_irqrestore(&ehci->lock, flags);
+ if (ehci_port_speed(ehci, temp) ==
+ USB_PORT_STAT_HIGH_SPEED && hcd->usb_phy) {
+ /* notify the USB PHY */
+ usb_phy_notify_suspend(hcd->usb_phy, USB_SPEED_HIGH);
+ }
+ spin_lock_irqsave(&ehci->lock, flags);
+
set_bit(port_index, &ehci->suspended_ports);
goto done;
}
@@ -308,6 +328,14 @@ static int ci_ehci_hub_control(
/* Make sure the resume has finished, it should be finished */
if (ehci_handshake(ehci, status_reg, PORT_RESUME, 0, 25000))
ehci_err(ehci, "timeout waiting for resume\n");
+
+ temp = ehci_readl(ehci, status_reg);
+
+ if (ehci_port_speed(ehci, temp) ==
+ USB_PORT_STAT_HIGH_SPEED && hcd->usb_phy) {
+ /* notify the USB PHY */
+ usb_phy_notify_resume(hcd->usb_phy, USB_SPEED_HIGH);
+ }
}
spin_unlock_irqrestore(&ehci->lock, flags);
@@ -355,6 +383,15 @@ static int ci_ehci_bus_suspend(struct usb_hcd *hcd)
*/
usleep_range(150, 200);
/*
+ * If a transaction is in progress, there may be
+ * a delay in suspending the port. Poll until the
+ * port is suspended.
+ */
+ if (test_bit(port, &ehci->bus_suspended) &&
+ ehci_handshake(ehci, reg, PORT_SUSPEND,
+ PORT_SUSPEND, 5000))
+ ehci_err(ehci, "timeout waiting for SUSPEND\n");
+ /*
* Need to clear WKCN and WKOC for imx HSIC,
* otherwise, there will be wakeup event.
*/
@@ -364,6 +401,15 @@ static int ci_ehci_bus_suspend(struct usb_hcd *hcd)
ehci_writel(ehci, tmp, reg);
}
+ if (hcd->usb_phy && test_bit(port, &ehci->bus_suspended)
+ && (ehci_port_speed(ehci, portsc) ==
+ USB_PORT_STAT_HIGH_SPEED))
+ /*
+ * notify the USB PHY, it is for global
+ * suspend case.
+ */
+ usb_phy_notify_suspend(hcd->usb_phy,
+ USB_SPEED_HIGH);
break;
}
}
@@ -444,6 +490,119 @@ static void ci_hdrc_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb)
ci_hdrc_free_dma_aligned_buffer(urb, true);
}
+static void ci_hdrc_host_save_for_power_lost(struct ci_hdrc *ci)
+{
+ struct ehci_hcd *ehci;
+
+ if (!ci->hcd)
+ return;
+
+ ehci = hcd_to_ehci(ci->hcd);
+ /* save EHCI registers */
+ ci->pm_usbmode = ehci_readl(ehci, &ehci->regs->usbmode);
+ ci->pm_command = ehci_readl(ehci, &ehci->regs->command);
+ ci->pm_command &= ~CMD_RUN;
+ ci->pm_status = ehci_readl(ehci, &ehci->regs->status);
+ ci->pm_intr_enable = ehci_readl(ehci, &ehci->regs->intr_enable);
+ ci->pm_frame_index = ehci_readl(ehci, &ehci->regs->frame_index);
+ ci->pm_segment = ehci_readl(ehci, &ehci->regs->segment);
+ ci->pm_frame_list = ehci_readl(ehci, &ehci->regs->frame_list);
+ ci->pm_async_next = ehci_readl(ehci, &ehci->regs->async_next);
+ ci->pm_configured_flag =
+ ehci_readl(ehci, &ehci->regs->configured_flag);
+ ci->pm_portsc = ehci_readl(ehci, &ehci->regs->port_status[0]);
+}
+
+static void ci_hdrc_host_restore_from_power_lost(struct ci_hdrc *ci)
+{
+ struct ehci_hcd *ehci;
+ unsigned long flags;
+ u32 tmp;
+ int step_ms;
+ /*
+ * If the vbus is off during system suspend, most of devices will pull
+ * DP up within 200ms when they see vbus, set 1000ms for safety.
+ */
+ int timeout_ms = 1000;
+
+ if (!ci->hcd)
+ return;
+
+ hw_controller_reset(ci);
+
+ ehci = hcd_to_ehci(ci->hcd);
+ spin_lock_irqsave(&ehci->lock, flags);
+ /* Restore EHCI registers */
+ ehci_writel(ehci, ci->pm_usbmode, &ehci->regs->usbmode);
+ ehci_writel(ehci, ci->pm_portsc, &ehci->regs->port_status[0]);
+ ehci_writel(ehci, ci->pm_command, &ehci->regs->command);
+ ehci_writel(ehci, ci->pm_intr_enable, &ehci->regs->intr_enable);
+ ehci_writel(ehci, ci->pm_frame_index, &ehci->regs->frame_index);
+ ehci_writel(ehci, ci->pm_segment, &ehci->regs->segment);
+ ehci_writel(ehci, ci->pm_frame_list, &ehci->regs->frame_list);
+ ehci_writel(ehci, ci->pm_async_next, &ehci->regs->async_next);
+ ehci_writel(ehci, ci->pm_configured_flag,
+ &ehci->regs->configured_flag);
+ /* Restore the PHY's connect notifier setting */
+ if (ci->pm_portsc & PORTSC_HSP)
+ usb_phy_notify_connect(ci->usb_phy, USB_SPEED_HIGH);
+
+ tmp = ehci_readl(ehci, &ehci->regs->command);
+ tmp |= CMD_RUN;
+ ehci_writel(ehci, tmp, &ehci->regs->command);
+ spin_unlock_irqrestore(&ehci->lock, flags);
+
+ if (!(ci->pm_portsc & PORTSC_CCS))
+ return;
+
+ for (step_ms = 0; step_ms < timeout_ms; step_ms += 25) {
+ if (ehci_readl(ehci, &ehci->regs->port_status[0]) & PORTSC_CCS)
+ break;
+ msleep(25);
+ }
+}
+
+static void ci_hdrc_host_suspend(struct ci_hdrc *ci)
+{
+ ci_hdrc_host_save_for_power_lost(ci);
+}
+
+static void ci_hdrc_host_resume(struct ci_hdrc *ci, bool power_lost)
+{
+ if (power_lost)
+ ci_hdrc_host_restore_from_power_lost(ci);
+}
+
+static int ci_ehci_bus_resume(struct usb_hcd *hcd)
+{
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+ int port;
+
+ int ret = orig_bus_resume(hcd);
+
+ if (ret)
+ return ret;
+
+ port = HCS_N_PORTS(ehci->hcs_params);
+ while (port--) {
+ u32 __iomem *reg = &ehci->regs->port_status[port];
+ u32 portsc = ehci_readl(ehci, reg);
+ /*
+ * Notify PHY after resume signal has finished, it is
+ * for global suspend case.
+ */
+ if (hcd->usb_phy
+ && test_bit(port, &ehci->bus_suspended)
+ && (portsc & PORT_CONNECT)
+ && (ehci_port_speed(ehci, portsc) ==
+ USB_PORT_STAT_HIGH_SPEED))
+ /* notify the USB PHY */
+ usb_phy_notify_resume(hcd->usb_phy, USB_SPEED_HIGH);
+ }
+
+ return 0;
+}
+
int ci_hdrc_host_init(struct ci_hdrc *ci)
{
struct ci_role_driver *rdrv;
@@ -457,6 +616,8 @@ int ci_hdrc_host_init(struct ci_hdrc *ci)
rdrv->start = host_start;
rdrv->stop = host_stop;
+ rdrv->suspend = ci_hdrc_host_suspend;
+ rdrv->resume = ci_hdrc_host_resume;
rdrv->irq = host_irq;
rdrv->name = "host";
ci->roles[CI_ROLE_HOST] = rdrv;
@@ -473,6 +634,8 @@ void ci_hdrc_host_driver_init(void)
{
ehci_init_driver(&ci_ehci_hc_driver, &ehci_ci_overrides);
orig_bus_suspend = ci_ehci_hc_driver.bus_suspend;
+ orig_bus_resume = ci_ehci_hc_driver.bus_resume;
+ ci_ehci_hc_driver.bus_resume = ci_ehci_bus_resume;
ci_ehci_hc_driver.bus_suspend = ci_ehci_bus_suspend;
ci_ehci_hc_driver.hub_control = ci_ehci_hub_control;
}
diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c
index 2d9d694eb0bd..e2c62ea2afc9 100644
--- a/drivers/usb/chipidea/otg.c
+++ b/drivers/usb/chipidea/otg.c
@@ -165,7 +165,7 @@ static int hw_wait_vbus_lower_bsv(struct ci_hdrc *ci)
return 0;
}
-static void ci_handle_id_switch(struct ci_hdrc *ci)
+void ci_handle_id_switch(struct ci_hdrc *ci)
{
enum ci_role role;
diff --git a/drivers/usb/chipidea/otg.h b/drivers/usb/chipidea/otg.h
index 5e7a6e571dd2..87629b81e03e 100644
--- a/drivers/usb/chipidea/otg.h
+++ b/drivers/usb/chipidea/otg.h
@@ -14,6 +14,7 @@ int ci_hdrc_otg_init(struct ci_hdrc *ci);
void ci_hdrc_otg_destroy(struct ci_hdrc *ci);
enum ci_role ci_otg_role(struct ci_hdrc *ci);
void ci_handle_vbus_change(struct ci_hdrc *ci);
+void ci_handle_id_switch(struct ci_hdrc *ci);
static inline void ci_otg_queue_work(struct ci_hdrc *ci)
{
disable_irq_nosync(ci->irq);
diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c
index aacc37736db6..fad51dabcbcb 100644
--- a/drivers/usb/chipidea/udc.c
+++ b/drivers/usb/chipidea/udc.c
@@ -1697,6 +1697,13 @@ static int ci_udc_vbus_session(struct usb_gadget *_gadget, int is_active)
ret = ci->platdata->notify_event(ci,
CI_HDRC_CONTROLLER_VBUS_EVENT);
+ if (ci->usb_phy) {
+ if (is_active)
+ usb_phy_set_event(ci->usb_phy, USB_EVENT_VBUS);
+ else
+ usb_phy_set_event(ci->usb_phy, USB_EVENT_NONE);
+ }
+
if (ci->driver)
ci_hdrc_gadget_connect(_gadget, is_active);
@@ -2012,6 +2019,9 @@ static irqreturn_t udc_irq(struct ci_hdrc *ci)
if (USBi_PCI & intr) {
ci->gadget.speed = hw_port_is_high_speed(ci) ?
USB_SPEED_HIGH : USB_SPEED_FULL;
+ if (ci->usb_phy)
+ usb_phy_set_event(ci->usb_phy,
+ USB_EVENT_ENUMERATED);
if (ci->suspended) {
if (ci->driver->resume) {
spin_unlock(&ci->lock);
@@ -2159,6 +2169,44 @@ static void udc_id_switch_for_host(struct ci_hdrc *ci)
ci->platdata->pins_default);
}
+static void udc_suspend_for_power_lost(struct ci_hdrc *ci)
+{
+ /*
+ * Set OP_ENDPTLISTADDR to be non-zero for
+ * checking if controller resume from power lost
+ * in non-host mode.
+ */
+ if (hw_read(ci, OP_ENDPTLISTADDR, ~0) == 0)
+ hw_write(ci, OP_ENDPTLISTADDR, ~0, ~0);
+}
+
+/* Power lost with device mode */
+static void udc_resume_from_power_lost(struct ci_hdrc *ci)
+{
+ if (ci->is_otg)
+ hw_write_otgsc(ci, OTGSC_BSVIS | OTGSC_BSVIE,
+ OTGSC_BSVIS | OTGSC_BSVIE);
+}
+
+static void udc_suspend(struct ci_hdrc *ci)
+{
+ udc_suspend_for_power_lost(ci);
+
+ if (ci->driver && ci->vbus_active &&
+ (ci->gadget.state != USB_STATE_SUSPENDED))
+ usb_gadget_disconnect(&ci->gadget);
+}
+
+static void udc_resume(struct ci_hdrc *ci, bool power_lost)
+{
+ if (power_lost) {
+ udc_resume_from_power_lost(ci);
+ } else {
+ if (ci->driver && ci->vbus_active)
+ usb_gadget_connect(&ci->gadget);
+ }
+}
+
/**
* ci_hdrc_gadget_init - initialize device related bits
* @ci: the controller
@@ -2179,6 +2227,8 @@ int ci_hdrc_gadget_init(struct ci_hdrc *ci)
rdrv->start = udc_id_switch_for_device;
rdrv->stop = udc_id_switch_for_host;
+ rdrv->suspend = udc_suspend;
+ rdrv->resume = udc_resume;
rdrv->irq = udc_irq;
rdrv->name = "gadget";
diff --git a/drivers/usb/chipidea/usbmisc_imx.c b/drivers/usb/chipidea/usbmisc_imx.c
index a2cb4f48c84c..f0768424c946 100644
--- a/drivers/usb/chipidea/usbmisc_imx.c
+++ b/drivers/usb/chipidea/usbmisc_imx.c
@@ -113,7 +113,6 @@
#define MX7D_USBNC_USB_CTRL2_DP_DM_MASK (BIT(12) | BIT(13) | \
BIT(14) | BIT(15))
-#define MX7D_USB_OTG_PHY_CFG1 0x30
#define MX7D_USB_OTG_PHY_CFG2_CHRG_CHRGSEL BIT(0)
#define MX7D_USB_OTG_PHY_CFG2_CHRG_VDATDETENB0 BIT(1)
#define MX7D_USB_OTG_PHY_CFG2_CHRG_VDATSRCENB0 BIT(2)
@@ -150,6 +149,9 @@ struct usbmisc_ops {
int (*hsic_set_clk)(struct imx_usbmisc_data *data, bool enabled);
/* usb charger detection */
int (*charger_detection)(struct imx_usbmisc_data *data);
+ /* It's called when system resume from usb power lost */
+ int (*power_lost_check)(struct imx_usbmisc_data *data);
+ void (*vbus_comparator_on)(struct imx_usbmisc_data *data, bool on);
};
struct imx_usbmisc {
@@ -875,6 +877,33 @@ static int imx7d_charger_detection(struct imx_usbmisc_data *data)
return ret;
}
+static void usbmisc_imx7d_vbus_comparator_on(struct imx_usbmisc_data *data,
+ bool on)
+{
+ unsigned long flags;
+ struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev);
+ u32 val;
+
+ if (data->hsic)
+ return;
+
+ spin_lock_irqsave(&usbmisc->lock, flags);
+ /*
+ * Disable VBUS valid comparator when in suspend mode,
+ * when OTG is disabled and DRVVBUS0 is asserted case
+ * the Bandgap circuitry and VBUS Valid comparator are
+ * still powered, even in Suspend or Sleep mode.
+ */
+ val = readl(usbmisc->base + MX7D_USB_OTG_PHY_CFG2);
+ if (on)
+ val |= MX7D_USB_OTG_PHY_CFG2_DRVVBUS0;
+ else
+ val &= ~MX7D_USB_OTG_PHY_CFG2_DRVVBUS0;
+
+ writel(val, usbmisc->base + MX7D_USB_OTG_PHY_CFG2);
+ spin_unlock_irqrestore(&usbmisc->lock, flags);
+}
+
static int usbmisc_imx7ulp_init(struct imx_usbmisc_data *data)
{
struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev);
@@ -939,6 +968,44 @@ static int usbmisc_imx7ulp_init(struct imx_usbmisc_data *data)
return 0;
}
+static int usbmisc_imx7d_power_lost_check(struct imx_usbmisc_data *data)
+{
+ struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev);
+ unsigned long flags;
+ u32 val;
+
+ spin_lock_irqsave(&usbmisc->lock, flags);
+ val = readl(usbmisc->base);
+ spin_unlock_irqrestore(&usbmisc->lock, flags);
+ /*
+ * Here use a power on reset value to judge
+ * if the controller experienced a power lost
+ */
+ if (val == 0x30001000)
+ return 1;
+ else
+ return 0;
+}
+
+static int usbmisc_imx6sx_power_lost_check(struct imx_usbmisc_data *data)
+{
+ struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev);
+ unsigned long flags;
+ u32 val;
+
+ spin_lock_irqsave(&usbmisc->lock, flags);
+ val = readl(usbmisc->base + data->index * 4);
+ spin_unlock_irqrestore(&usbmisc->lock, flags);
+ /*
+ * Here use a power on reset value to judge
+ * if the controller experienced a power lost
+ */
+ if (val == 0x30001000)
+ return 1;
+ else
+ return 0;
+}
+
static const struct usbmisc_ops imx25_usbmisc_ops = {
.init = usbmisc_imx25_init,
.post = usbmisc_imx25_post,
@@ -972,12 +1039,15 @@ static const struct usbmisc_ops imx6sx_usbmisc_ops = {
.init = usbmisc_imx6sx_init,
.hsic_set_connect = usbmisc_imx6_hsic_set_connect,
.hsic_set_clk = usbmisc_imx6_hsic_set_clk,
+ .power_lost_check = usbmisc_imx6sx_power_lost_check,
};
static const struct usbmisc_ops imx7d_usbmisc_ops = {
.init = usbmisc_imx7d_init,
.set_wakeup = usbmisc_imx7d_set_wakeup,
.charger_detection = imx7d_charger_detection,
+ .power_lost_check = usbmisc_imx7d_power_lost_check,
+ .vbus_comparator_on = usbmisc_imx7d_vbus_comparator_on,
};
static const struct usbmisc_ops imx7ulp_usbmisc_ops = {
@@ -985,6 +1055,7 @@ static const struct usbmisc_ops imx7ulp_usbmisc_ops = {
.set_wakeup = usbmisc_imx7d_set_wakeup,
.hsic_set_connect = usbmisc_imx6_hsic_set_connect,
.hsic_set_clk = usbmisc_imx6_hsic_set_clk,
+ .power_lost_check = usbmisc_imx7d_power_lost_check,
};
static inline bool is_imx53_usbmisc(struct imx_usbmisc_data *data)
@@ -1011,30 +1082,29 @@ EXPORT_SYMBOL_GPL(imx_usbmisc_init);
int imx_usbmisc_init_post(struct imx_usbmisc_data *data)
{
struct imx_usbmisc *usbmisc;
+ int ret = 0;
if (!data)
return 0;
usbmisc = dev_get_drvdata(data->dev);
- if (!usbmisc->ops->post)
- return 0;
- return usbmisc->ops->post(data);
-}
-EXPORT_SYMBOL_GPL(imx_usbmisc_init_post);
-
-int imx_usbmisc_set_wakeup(struct imx_usbmisc_data *data, bool enabled)
-{
- struct imx_usbmisc *usbmisc;
+ if (usbmisc->ops->post)
+ ret = usbmisc->ops->post(data);
+ if (ret) {
+ dev_err(data->dev, "post init failed, ret=%d\n", ret);
+ return ret;
+ }
- if (!data)
- return 0;
+ if (usbmisc->ops->set_wakeup)
+ ret = usbmisc->ops->set_wakeup(data, false);
+ if (ret) {
+ dev_err(data->dev, "set_wakeup failed, ret=%d\n", ret);
+ return ret;
+ }
- usbmisc = dev_get_drvdata(data->dev);
- if (!usbmisc->ops->set_wakeup)
- return 0;
- return usbmisc->ops->set_wakeup(data, enabled);
+ return 0;
}
-EXPORT_SYMBOL_GPL(imx_usbmisc_set_wakeup);
+EXPORT_SYMBOL_GPL(imx_usbmisc_init_post);
int imx_usbmisc_hsic_set_connect(struct imx_usbmisc_data *data)
{
@@ -1050,20 +1120,6 @@ int imx_usbmisc_hsic_set_connect(struct imx_usbmisc_data *data)
}
EXPORT_SYMBOL_GPL(imx_usbmisc_hsic_set_connect);
-int imx_usbmisc_hsic_set_clk(struct imx_usbmisc_data *data, bool on)
-{
- struct imx_usbmisc *usbmisc;
-
- if (!data)
- return 0;
-
- usbmisc = dev_get_drvdata(data->dev);
- if (!usbmisc->ops->hsic_set_clk || !data->hsic)
- return 0;
- return usbmisc->ops->hsic_set_clk(data, on);
-}
-EXPORT_SYMBOL_GPL(imx_usbmisc_hsic_set_clk);
-
int imx_usbmisc_charger_detection(struct imx_usbmisc_data *data, bool connect)
{
struct imx_usbmisc *usbmisc;
@@ -1096,6 +1152,84 @@ int imx_usbmisc_charger_detection(struct imx_usbmisc_data *data, bool connect)
}
EXPORT_SYMBOL_GPL(imx_usbmisc_charger_detection);
+int imx_usbmisc_suspend(struct imx_usbmisc_data *data, bool wakeup)
+{
+ struct imx_usbmisc *usbmisc;
+ int ret = 0;
+
+ if (!data)
+ return 0;
+
+ usbmisc = dev_get_drvdata(data->dev);
+
+ if (usbmisc->ops->vbus_comparator_on)
+ usbmisc->ops->vbus_comparator_on(data, false);
+
+ if (wakeup && usbmisc->ops->set_wakeup)
+ ret = usbmisc->ops->set_wakeup(data, true);
+ if (ret) {
+ dev_err(data->dev, "set_wakeup failed, ret=%d\n", ret);
+ return ret;
+ }
+
+ if (usbmisc->ops->hsic_set_clk && data->hsic)
+ ret = usbmisc->ops->hsic_set_clk(data, false);
+ if (ret) {
+ dev_err(data->dev, "set_wakeup failed, ret=%d\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(imx_usbmisc_suspend);
+
+int imx_usbmisc_resume(struct imx_usbmisc_data *data, bool wakeup)
+{
+ struct imx_usbmisc *usbmisc;
+ int ret = 0;
+
+ if (!data)
+ return 0;
+
+ usbmisc = dev_get_drvdata(data->dev);
+
+ if (usbmisc->ops->power_lost_check)
+ ret = usbmisc->ops->power_lost_check(data);
+ if (ret > 0) {
+ /* re-init if resume from power lost */
+ ret = imx_usbmisc_init(data);
+ if (ret) {
+ dev_err(data->dev, "re-init failed, ret=%d\n", ret);
+ return ret;
+ }
+ }
+
+ if (wakeup && usbmisc->ops->set_wakeup)
+ ret = usbmisc->ops->set_wakeup(data, false);
+ if (ret) {
+ dev_err(data->dev, "set_wakeup failed, ret=%d\n", ret);
+ return ret;
+ }
+
+ if (usbmisc->ops->hsic_set_clk && data->hsic)
+ ret = usbmisc->ops->hsic_set_clk(data, true);
+ if (ret) {
+ dev_err(data->dev, "set_wakeup failed, ret=%d\n", ret);
+ goto hsic_set_clk_fail;
+ }
+
+ if (usbmisc->ops->vbus_comparator_on)
+ usbmisc->ops->vbus_comparator_on(data, true);
+
+ return 0;
+
+hsic_set_clk_fail:
+ if (wakeup && usbmisc->ops->set_wakeup)
+ usbmisc->ops->set_wakeup(data, true);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(imx_usbmisc_resume);
+
static const struct of_device_id usbmisc_imx_dt_ids[] = {
{
.compatible = "fsl,imx25-usbmisc",
diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c
index 8cbabc39f818..6c5934dbe9b3 100644
--- a/drivers/usb/core/hcd.c
+++ b/drivers/usb/core/hcd.c
@@ -983,7 +983,6 @@ static int register_root_hub(struct usb_hcd *hcd)
{
struct device *parent_dev = hcd->self.controller;
struct usb_device *usb_dev = hcd->self.root_hub;
- struct usb_device_descriptor *descr;
const int devnum = 1;
int retval;
@@ -995,16 +994,13 @@ static int register_root_hub(struct usb_hcd *hcd)
mutex_lock(&usb_bus_idr_lock);
usb_dev->ep0.desc.wMaxPacketSize = cpu_to_le16(64);
- descr = usb_get_device_descriptor(usb_dev);
- if (IS_ERR(descr)) {
- retval = PTR_ERR(descr);
+ retval = usb_get_device_descriptor(usb_dev, USB_DT_DEVICE_SIZE);
+ if (retval != sizeof usb_dev->descriptor) {
mutex_unlock(&usb_bus_idr_lock);
dev_dbg (parent_dev, "can't read %s device descriptor %d\n",
dev_name(&usb_dev->dev), retval);
- return retval;
+ return (retval < 0) ? retval : -EMSGSIZE;
}
- usb_dev->descriptor = *descr;
- kfree(descr);
if (le16_to_cpu(usb_dev->descriptor.bcdUSB) >= 0x0201) {
retval = usb_get_bos_descriptor(usb_dev);
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 4bed41ca6b0f..1f315c87c72c 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -2657,17 +2657,12 @@ int usb_authorize_device(struct usb_device *usb_dev)
}
if (usb_dev->wusb) {
- struct usb_device_descriptor *descr;
-
- descr = usb_get_device_descriptor(usb_dev);
- if (IS_ERR(descr)) {
- result = PTR_ERR(descr);
+ result = usb_get_device_descriptor(usb_dev, sizeof(usb_dev->descriptor));
+ if (result < 0) {
dev_err(&usb_dev->dev, "can't re-read device descriptor for "
"authorization: %d\n", result);
goto error_device_descriptor;
}
- usb_dev->descriptor = *descr;
- kfree(descr);
}
usb_dev->authorized = 1;
@@ -4690,67 +4685,6 @@ static int hub_enable_device(struct usb_device *udev)
return hcd->driver->enable_device(hcd, udev);
}
-/*
- * Get the bMaxPacketSize0 value during initialization by reading the
- * device's device descriptor. Since we don't already know this value,
- * the transfer is unsafe and it ignores I/O errors, only testing for
- * reasonable received values.
- *
- * For "old scheme" initialization, size will be 8 so we read just the
- * start of the device descriptor, which should work okay regardless of
- * the actual bMaxPacketSize0 value. For "new scheme" initialization,
- * size will be 64 (and buf will point to a sufficiently large buffer),
- * which might not be kosher according to the USB spec but it's what
- * Windows does and what many devices expect.
- *
- * Returns: bMaxPacketSize0 or a negative error code.
- */
-static int get_bMaxPacketSize0(struct usb_device *udev,
- struct usb_device_descriptor *buf, int size, bool first_time)
-{
- int i, rc;
-
- /*
- * Retry on all errors; some devices are flakey.
- * 255 is for WUSB devices, we actually need to use
- * 512 (WUSB1.0[4.8.1]).
- */
- for (i = 0; i < GET_MAXPACKET0_TRIES; ++i) {
- /* Start with invalid values in case the transfer fails */
- buf->bDescriptorType = buf->bMaxPacketSize0 = 0;
- rc = usb_control_msg(udev, usb_rcvaddr0pipe(),
- USB_REQ_GET_DESCRIPTOR, USB_DIR_IN,
- USB_DT_DEVICE << 8, 0,
- buf, size,
- initial_descriptor_timeout);
- switch (buf->bMaxPacketSize0) {
- case 8: case 16: case 32: case 64: case 9:
- if (buf->bDescriptorType == USB_DT_DEVICE) {
- rc = buf->bMaxPacketSize0;
- break;
- }
- fallthrough;
- default:
- if (rc >= 0)
- rc = -EPROTO;
- break;
- }
-
- /*
- * Some devices time out if they are powered on
- * when already connected. They need a second
- * reset, so return early. But only on the first
- * attempt, lest we get into a time-out/reset loop.
- */
- if (rc > 0 || (rc == -ETIMEDOUT && first_time &&
- udev->speed > USB_SPEED_FULL))
- break;
- }
- return rc;
-}
-
-#define GET_DESCRIPTOR_BUFSIZE 64
-
/* Reset device, (re)assign address, get device descriptor.
* Device connection must be stable, no more debouncing needed.
* Returns device in USB_STATE_ADDRESS, except on error.
@@ -4760,17 +4694,10 @@ static int get_bMaxPacketSize0(struct usb_device *udev,
* the port lock. For a newly detected device that is not accessible
* through any global pointers, it's not necessary to lock the device,
* but it is still necessary to lock the port.
- *
- * For a newly detected device, @dev_descr must be NULL. The device
- * descriptor retrieved from the device will then be stored in
- * @udev->descriptor. For an already existing device, @dev_descr
- * must be non-NULL. The device descriptor will be stored there,
- * not in @udev->descriptor, because descriptors for registered
- * devices are meant to be immutable.
*/
static int
hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1,
- int retry_counter, struct usb_device_descriptor *dev_descr)
+ int retry_counter)
{
struct usb_device *hdev = hub->hdev;
struct usb_hcd *hcd = bus_to_hcd(hdev->bus);
@@ -4782,13 +4709,6 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1,
int devnum = udev->devnum;
const char *driver_name;
bool do_new_scheme;
- const bool initial = !dev_descr;
- int maxp0;
- struct usb_device_descriptor *buf, *descr;
-
- buf = kmalloc(GET_DESCRIPTOR_BUFSIZE, GFP_NOIO);
- if (!buf)
- return -ENOMEM;
/* root hub ports have a slightly longer reset period
* (from USB 2.0 spec, section 7.1.7.5)
@@ -4821,34 +4741,32 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1,
}
oldspeed = udev->speed;
- if (initial) {
- /* USB 2.0 section 5.5.3 talks about ep0 maxpacket ...
- * it's fixed size except for full speed devices.
- * For Wireless USB devices, ep0 max packet is always 512 (tho
- * reported as 0xff in the device descriptor). WUSB1.0[4.8.1].
+ /* USB 2.0 section 5.5.3 talks about ep0 maxpacket ...
+ * it's fixed size except for full speed devices.
+ * For Wireless USB devices, ep0 max packet is always 512 (tho
+ * reported as 0xff in the device descriptor). WUSB1.0[4.8.1].
+ */
+ switch (udev->speed) {
+ case USB_SPEED_SUPER_PLUS:
+ case USB_SPEED_SUPER:
+ case USB_SPEED_WIRELESS: /* fixed at 512 */
+ udev->ep0.desc.wMaxPacketSize = cpu_to_le16(512);
+ break;
+ case USB_SPEED_HIGH: /* fixed at 64 */
+ udev->ep0.desc.wMaxPacketSize = cpu_to_le16(64);
+ break;
+ case USB_SPEED_FULL: /* 8, 16, 32, or 64 */
+ /* to determine the ep0 maxpacket size, try to read
+ * the device descriptor to get bMaxPacketSize0 and
+ * then correct our initial guess.
*/
- switch (udev->speed) {
- case USB_SPEED_SUPER_PLUS:
- case USB_SPEED_SUPER:
- case USB_SPEED_WIRELESS: /* fixed at 512 */
- udev->ep0.desc.wMaxPacketSize = cpu_to_le16(512);
- break;
- case USB_SPEED_HIGH: /* fixed at 64 */
- udev->ep0.desc.wMaxPacketSize = cpu_to_le16(64);
- break;
- case USB_SPEED_FULL: /* 8, 16, 32, or 64 */
- /* to determine the ep0 maxpacket size, try to read
- * the device descriptor to get bMaxPacketSize0 and
- * then correct our initial guess.
- */
- udev->ep0.desc.wMaxPacketSize = cpu_to_le16(64);
- break;
- case USB_SPEED_LOW: /* fixed at 8 */
- udev->ep0.desc.wMaxPacketSize = cpu_to_le16(8);
- break;
- default:
- goto fail;
- }
+ udev->ep0.desc.wMaxPacketSize = cpu_to_le16(64);
+ break;
+ case USB_SPEED_LOW: /* fixed at 8 */
+ udev->ep0.desc.wMaxPacketSize = cpu_to_le16(8);
+ break;
+ default:
+ goto fail;
}
if (udev->speed == USB_SPEED_WIRELESS)
@@ -4871,24 +4789,22 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1,
if (udev->speed < USB_SPEED_SUPER)
dev_info(&udev->dev,
"%s %s USB device number %d using %s\n",
- (initial ? "new" : "reset"), speed,
+ (udev->config) ? "reset" : "new", speed,
devnum, driver_name);
- if (initial) {
- /* Set up TT records, if needed */
- if (hdev->tt) {
- udev->tt = hdev->tt;
- udev->ttport = hdev->ttport;
- } else if (udev->speed != USB_SPEED_HIGH
- && hdev->speed == USB_SPEED_HIGH) {
- if (!hub->tt.hub) {
- dev_err(&udev->dev, "parent hub has no TT\n");
- retval = -EINVAL;
- goto fail;
- }
- udev->tt = &hub->tt;
- udev->ttport = port1;
+ /* Set up TT records, if needed */
+ if (hdev->tt) {
+ udev->tt = hdev->tt;
+ udev->ttport = hdev->ttport;
+ } else if (udev->speed != USB_SPEED_HIGH
+ && hdev->speed == USB_SPEED_HIGH) {
+ if (!hub->tt.hub) {
+ dev_err(&udev->dev, "parent hub has no TT\n");
+ retval = -EINVAL;
+ goto fail;
}
+ udev->tt = &hub->tt;
+ udev->ttport = port1;
}
/* Why interleave GET_DESCRIPTOR and SET_ADDRESS this way?
@@ -4907,6 +4823,9 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1,
for (retries = 0; retries < GET_DESCRIPTOR_TRIES; (++retries, msleep(100))) {
if (do_new_scheme) {
+ struct usb_device_descriptor *buf;
+ int r = 0;
+
retval = hub_enable_device(udev);
if (retval < 0) {
dev_err(&udev->dev,
@@ -4915,14 +4834,52 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1,
goto fail;
}
- maxp0 = get_bMaxPacketSize0(udev, buf,
- GET_DESCRIPTOR_BUFSIZE, retries == 0);
- if (maxp0 > 0 && !initial &&
- maxp0 != udev->descriptor.bMaxPacketSize0) {
- dev_err(&udev->dev, "device reset changed ep0 maxpacket size!\n");
- retval = -ENODEV;
- goto fail;
+#define GET_DESCRIPTOR_BUFSIZE 64
+ buf = kmalloc(GET_DESCRIPTOR_BUFSIZE, GFP_NOIO);
+ if (!buf) {
+ retval = -ENOMEM;
+ continue;
+ }
+
+ /* Retry on all errors; some devices are flakey.
+ * 255 is for WUSB devices, we actually need to use
+ * 512 (WUSB1.0[4.8.1]).
+ */
+ for (operations = 0; operations < GET_MAXPACKET0_TRIES;
+ ++operations) {
+ buf->bMaxPacketSize0 = 0;
+ r = usb_control_msg(udev, usb_rcvaddr0pipe(),
+ USB_REQ_GET_DESCRIPTOR, USB_DIR_IN,
+ USB_DT_DEVICE << 8, 0,
+ buf, GET_DESCRIPTOR_BUFSIZE,
+ initial_descriptor_timeout);
+ switch (buf->bMaxPacketSize0) {
+ case 8: case 16: case 32: case 64: case 255:
+ if (buf->bDescriptorType ==
+ USB_DT_DEVICE) {
+ r = 0;
+ break;
+ }
+ fallthrough;
+ default:
+ if (r == 0)
+ r = -EPROTO;
+ break;
+ }
+ /*
+ * Some devices time out if they are powered on
+ * when already connected. They need a second
+ * reset. But only on the first attempt,
+ * lest we get into a time out/reset loop
+ */
+ if (r == 0 || (r == -ETIMEDOUT &&
+ retries == 0 &&
+ udev->speed > USB_SPEED_FULL))
+ break;
}
+ udev->descriptor.bMaxPacketSize0 =
+ buf->bMaxPacketSize0;
+ kfree(buf);
retval = hub_port_reset(hub, port1, udev, delay, false);
if (retval < 0) /* error or disconnect */
@@ -4933,13 +4890,15 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1,
retval = -ENODEV;
goto fail;
}
- if (maxp0 < 0) {
- if (maxp0 != -ENODEV)
- dev_err(&udev->dev, "device descriptor read/64, error %d\n",
- maxp0);
- retval = maxp0;
+ if (r) {
+ if (r != -ENODEV)
+ dev_err(&udev->dev,
+ "device no response, device descriptor read/64, error %d\n",
+ r);
+ retval = -EMSGSIZE;
continue;
}
+#undef GET_DESCRIPTOR_BUFSIZE
}
/*
@@ -4985,22 +4944,18 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1,
break;
}
- /* !do_new_scheme || wusb */
- maxp0 = get_bMaxPacketSize0(udev, buf, 8, retries == 0);
- if (maxp0 < 0) {
- retval = maxp0;
+ retval = usb_get_device_descriptor(udev, 8);
+ if (retval < 8) {
if (retval != -ENODEV)
dev_err(&udev->dev,
"device descriptor read/8, error %d\n",
retval);
+ if (retval >= 0)
+ retval = -EMSGSIZE;
} else {
u32 delay;
- if (!initial && maxp0 != udev->descriptor.bMaxPacketSize0) {
- dev_err(&udev->dev, "device reset changed ep0 maxpacket size!\n");
- retval = -ENODEV;
- goto fail;
- }
+ retval = 0;
delay = udev->parent->hub_delay;
udev->hub_delay = min_t(u32, delay,
@@ -5019,61 +4974,48 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1,
goto fail;
/*
- * Check the ep0 maxpacket guess and correct it if necessary.
- * maxp0 is the value stored in the device descriptor;
- * i is the value it encodes (logarithmic for SuperSpeed or greater).
+ * Some superspeed devices have finished the link training process
+ * and attached to a superspeed hub port, but the device descriptor
+ * got from those devices show they aren't superspeed devices. Warm
+ * reset the port attached by the devices can fix them.
*/
- i = maxp0;
- if (udev->speed >= USB_SPEED_SUPER) {
- if (maxp0 <= 16)
- i = 1 << maxp0;
- else
- i = 0; /* Invalid */
- }
- if (usb_endpoint_maxp(&udev->ep0.desc) == i) {
- ; /* Initial ep0 maxpacket guess is right */
- } else if ((udev->speed == USB_SPEED_FULL ||
- udev->speed == USB_SPEED_HIGH) &&
- (i == 8 || i == 16 || i == 32 || i == 64)) {
- /* Initial guess is wrong; use the descriptor's value */
+ if ((udev->speed >= USB_SPEED_SUPER) &&
+ (le16_to_cpu(udev->descriptor.bcdUSB) < 0x0300)) {
+ dev_err(&udev->dev, "got a wrong device descriptor, "
+ "warm reset device\n");
+ hub_port_reset(hub, port1, udev,
+ HUB_BH_RESET_TIME, true);
+ retval = -EINVAL;
+ goto fail;
+ }
+
+ if (udev->descriptor.bMaxPacketSize0 == 0xff ||
+ udev->speed >= USB_SPEED_SUPER)
+ i = 512;
+ else
+ i = udev->descriptor.bMaxPacketSize0;
+ if (usb_endpoint_maxp(&udev->ep0.desc) != i) {
+ if (udev->speed == USB_SPEED_LOW ||
+ !(i == 8 || i == 16 || i == 32 || i == 64)) {
+ dev_err(&udev->dev, "Invalid ep0 maxpacket: %d\n", i);
+ retval = -EMSGSIZE;
+ goto fail;
+ }
if (udev->speed == USB_SPEED_FULL)
dev_dbg(&udev->dev, "ep0 maxpacket = %d\n", i);
else
dev_warn(&udev->dev, "Using ep0 maxpacket: %d\n", i);
udev->ep0.desc.wMaxPacketSize = cpu_to_le16(i);
usb_ep0_reinit(udev);
- } else {
- /* Initial guess is wrong and descriptor's value is invalid */
- dev_err(&udev->dev, "Invalid ep0 maxpacket: %d\n", maxp0);
- retval = -EMSGSIZE;
- goto fail;
}
- descr = usb_get_device_descriptor(udev);
- if (IS_ERR(descr)) {
- retval = PTR_ERR(descr);
+ retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE);
+ if (retval < (signed)sizeof(udev->descriptor)) {
if (retval != -ENODEV)
dev_err(&udev->dev, "device descriptor read/all, error %d\n",
retval);
- goto fail;
- }
- if (initial)
- udev->descriptor = *descr;
- else
- *dev_descr = *descr;
- kfree(descr);
-
- /*
- * Some superspeed devices have finished the link training process
- * and attached to a superspeed hub port, but the device descriptor
- * got from those devices show they aren't superspeed devices. Warm
- * reset the port attached by the devices can fix them.
- */
- if ((udev->speed >= USB_SPEED_SUPER) &&
- (le16_to_cpu(udev->descriptor.bcdUSB) < 0x0300)) {
- dev_err(&udev->dev, "got a wrong device descriptor, warm reset device\n");
- hub_port_reset(hub, port1, udev, HUB_BH_RESET_TIME, true);
- retval = -EINVAL;
+ if (retval >= 0)
+ retval = -ENOMSG;
goto fail;
}
@@ -5097,7 +5039,6 @@ fail:
hub_port_disable(hub, port1, 0);
update_devnum(udev, devnum); /* for disconnect processing */
}
- kfree(buf);
return retval;
}
@@ -5178,7 +5119,7 @@ hub_power_remaining(struct usb_hub *hub)
static int descriptors_changed(struct usb_device *udev,
- struct usb_device_descriptor *new_device_descriptor,
+ struct usb_device_descriptor *old_device_descriptor,
struct usb_host_bos *old_bos)
{
int changed = 0;
@@ -5189,8 +5130,8 @@ static int descriptors_changed(struct usb_device *udev,
int length;
char *buf;
- if (memcmp(&udev->descriptor, new_device_descriptor,
- sizeof(*new_device_descriptor)) != 0)
+ if (memcmp(&udev->descriptor, old_device_descriptor,
+ sizeof(*old_device_descriptor)) != 0)
return 1;
if ((old_bos && !udev->bos) || (!old_bos && udev->bos))
@@ -5363,7 +5304,7 @@ static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus,
}
/* reset (non-USB 3.0 devices) and get descriptor */
- status = hub_port_init(hub, udev, port1, i, NULL);
+ status = hub_port_init(hub, udev, port1, i);
if (status < 0)
goto loop;
@@ -5510,8 +5451,9 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
{
struct usb_port *port_dev = hub->ports[port1 - 1];
struct usb_device *udev = port_dev->child;
- struct usb_device_descriptor *descr;
+ struct usb_device_descriptor descriptor;
int status = -ENODEV;
+ int retval;
dev_dbg(&port_dev->dev, "status %04x, change %04x, %s\n", portstatus,
portchange, portspeed(hub, portstatus));
@@ -5538,20 +5480,23 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
* changed device descriptors before resuscitating the
* device.
*/
- descr = usb_get_device_descriptor(udev);
- if (IS_ERR(descr)) {
+ descriptor = udev->descriptor;
+ retval = usb_get_device_descriptor(udev,
+ sizeof(udev->descriptor));
+ if (retval < 0) {
dev_dbg(&udev->dev,
- "can't read device descriptor %ld\n",
- PTR_ERR(descr));
+ "can't read device descriptor %d\n",
+ retval);
} else {
- if (descriptors_changed(udev, descr,
+ if (descriptors_changed(udev, &descriptor,
udev->bos)) {
dev_dbg(&udev->dev,
"device descriptor has changed\n");
+ /* for disconnect() calls */
+ udev->descriptor = descriptor;
} else {
status = 0; /* Nothing to do */
}
- kfree(descr);
}
#ifdef CONFIG_PM
} else if (udev->state == USB_STATE_SUSPENDED &&
@@ -5979,7 +5924,7 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
struct usb_device *parent_hdev = udev->parent;
struct usb_hub *parent_hub;
struct usb_hcd *hcd = bus_to_hcd(udev->bus);
- struct usb_device_descriptor descriptor;
+ struct usb_device_descriptor descriptor = udev->descriptor;
struct usb_host_bos *bos;
int i, j, ret = 0;
int port1 = udev->portnum;
@@ -6021,7 +5966,7 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
/* ep0 maxpacket size may change; let the HCD know about it.
* Other endpoints will be handled by re-enumeration. */
usb_ep0_reinit(udev);
- ret = hub_port_init(parent_hub, udev, port1, i, &descriptor);
+ ret = hub_port_init(parent_hub, udev, port1, i);
if (ret >= 0 || ret == -ENOTCONN || ret == -ENODEV)
break;
}
@@ -6033,6 +5978,7 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
/* Device might have changed firmware (DFU or similar) */
if (descriptors_changed(udev, &descriptor, bos)) {
dev_info(&udev->dev, "device firmware changed\n");
+ udev->descriptor = descriptor; /* for disconnect() calls */
goto re_enumerate;
}
diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c
index 1673e5d08926..4d59d927ae3e 100644
--- a/drivers/usb/core/message.c
+++ b/drivers/usb/core/message.c
@@ -1039,35 +1039,40 @@ char *usb_cache_string(struct usb_device *udev, int index)
}
/*
- * usb_get_device_descriptor - read the device descriptor
- * @udev: the device whose device descriptor should be read
+ * usb_get_device_descriptor - (re)reads the device descriptor (usbcore)
+ * @dev: the device whose device descriptor is being updated
+ * @size: how much of the descriptor to read
*
* Context: task context, might sleep.
*
+ * Updates the copy of the device descriptor stored in the device structure,
+ * which dedicates space for this purpose.
+ *
* Not exported, only for use by the core. If drivers really want to read
* the device descriptor directly, they can call usb_get_descriptor() with
* type = USB_DT_DEVICE and index = 0.
*
- * Returns: a pointer to a dynamically allocated usb_device_descriptor
- * structure (which the caller must deallocate), or an ERR_PTR value.
+ * This call is synchronous, and may not be used in an interrupt context.
+ *
+ * Return: The number of bytes received on success, or else the status code
+ * returned by the underlying usb_control_msg() call.
*/
-struct usb_device_descriptor *usb_get_device_descriptor(struct usb_device *udev)
+int usb_get_device_descriptor(struct usb_device *dev, unsigned int size)
{
struct usb_device_descriptor *desc;
int ret;
+ if (size > sizeof(*desc))
+ return -EINVAL;
desc = kmalloc(sizeof(*desc), GFP_NOIO);
if (!desc)
- return ERR_PTR(-ENOMEM);
-
- ret = usb_get_descriptor(udev, USB_DT_DEVICE, 0, desc, sizeof(*desc));
- if (ret == sizeof(*desc))
- return desc;
+ return -ENOMEM;
+ ret = usb_get_descriptor(dev, USB_DT_DEVICE, 0, desc, size);
if (ret >= 0)
- ret = -EMSGSIZE;
+ memcpy(&dev->descriptor, desc, size);
kfree(desc);
- return ERR_PTR(ret);
+ return ret;
}
/*
diff --git a/drivers/usb/core/otg_productlist.h b/drivers/usb/core/otg_productlist.h
index db67df29fb2b..40c0901c30eb 100644
--- a/drivers/usb/core/otg_productlist.h
+++ b/drivers/usb/core/otg_productlist.h
@@ -9,35 +9,62 @@
*/
static struct usb_device_id productlist_table[] = {
-
-/* hubs are optional in OTG, but very handy ... */
-{ USB_DEVICE_INFO(USB_CLASS_HUB, 0, 0), },
-{ USB_DEVICE_INFO(USB_CLASS_HUB, 0, 1), },
-
-#ifdef CONFIG_USB_PRINTER /* ignoring nonstatic linkage! */
-/* FIXME actually, printers are NOT supposed to use device classes;
- * they're supposed to use interface classes...
- */
-{ USB_DEVICE_INFO(7, 1, 1) },
-{ USB_DEVICE_INFO(7, 1, 2) },
-{ USB_DEVICE_INFO(7, 1, 3) },
+/* Add FSL i.mx whitelist, the default list is for USB Compliance Test */
+#if defined(CONFIG_USB_EHSET_TEST_FIXTURE) \
+ || defined(CONFIG_USB_EHSET_TEST_FIXTURE_MODULE)
+#define TEST_SE0_NAK_PID 0x0101
+#define TEST_J_PID 0x0102
+#define TEST_K_PID 0x0103
+#define TEST_PACKET_PID 0x0104
+#define TEST_HS_HOST_PORT_SUSPEND_RESUME 0x0106
+#define TEST_SINGLE_STEP_GET_DEV_DESC 0x0107
+#define TEST_SINGLE_STEP_SET_FEATURE 0x0108
+{ USB_DEVICE(0x1a0a, TEST_SE0_NAK_PID) },
+{ USB_DEVICE(0x1a0a, TEST_J_PID) },
+{ USB_DEVICE(0x1a0a, TEST_K_PID) },
+{ USB_DEVICE(0x1a0a, TEST_PACKET_PID) },
+{ USB_DEVICE(0x1a0a, TEST_HS_HOST_PORT_SUSPEND_RESUME) },
+{ USB_DEVICE(0x1a0a, TEST_SINGLE_STEP_GET_DEV_DESC) },
+{ USB_DEVICE(0x1a0a, TEST_SINGLE_STEP_SET_FEATURE) },
#endif
-#ifdef CONFIG_USB_NET_CDCETHER
-/* Linux-USB CDC Ethernet gadget */
-{ USB_DEVICE(0x0525, 0xa4a1), },
-/* Linux-USB CDC Ethernet + RNDIS gadget */
-{ USB_DEVICE(0x0525, 0xa4a2), },
-#endif
+#define USB_INTERFACE_CLASS_INFO(cl) \
+ .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS, \
+ .bInterfaceClass = (cl)
-#if IS_ENABLED(CONFIG_USB_TEST)
-/* gadget zero, for testing */
-{ USB_DEVICE(0x0525, 0xa4a0), },
+{USB_INTERFACE_CLASS_INFO(USB_CLASS_HUB) },
+#if defined(CONFIG_USB_STORAGE) || defined(CONFIG_USB_STORAGE_MODULE)
+{USB_INTERFACE_CLASS_INFO(USB_CLASS_MASS_STORAGE) },
+#endif
+#if defined(CONFIG_USB_HID) || defined(CONFIG_USB_HID_MODULE)
+{USB_INTERFACE_CLASS_INFO(USB_CLASS_HID) },
#endif
{ } /* Terminating entry */
};
+static bool match_int_class(struct usb_device_id *id, struct usb_device *udev)
+{
+ struct usb_host_config *c;
+ int num_configs, i;
+
+ /* Copy the code from generic.c */
+ c = udev->config;
+ num_configs = udev->descriptor.bNumConfigurations;
+ for (i = 0; i < num_configs; (i++, c++)) {
+ struct usb_interface_descriptor *desc = NULL;
+
+ /* It's possible that a config has no interfaces! */
+ if (c->desc.bNumInterfaces > 0)
+ desc = &c->intf_cache[0]->altsetting->desc;
+
+ if (desc && (desc->bInterfaceClass == id->bInterfaceClass))
+ return true;
+ }
+
+ return false;
+}
+
static int is_targeted(struct usb_device *dev)
{
struct usb_device_id *id = productlist_table;
@@ -86,6 +113,10 @@ static int is_targeted(struct usb_device *dev)
(id->bDeviceProtocol != dev->descriptor.bDeviceProtocol))
continue;
+ if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_CLASS) &&
+ (!match_int_class(id, dev)))
+ continue;
+
return 1;
}
diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h
index 3bb2e1db42b5..82538daac8b8 100644
--- a/drivers/usb/core/usb.h
+++ b/drivers/usb/core/usb.h
@@ -42,8 +42,8 @@ extern bool usb_endpoint_is_ignored(struct usb_device *udev,
struct usb_endpoint_descriptor *epd);
extern int usb_remove_device(struct usb_device *udev);
-extern struct usb_device_descriptor *usb_get_device_descriptor(
- struct usb_device *udev);
+extern int usb_get_device_descriptor(struct usb_device *dev,
+ unsigned int size);
extern int usb_set_isoch_delay(struct usb_device *dev);
extern int usb_get_bos_descriptor(struct usb_device *dev);
extern void usb_release_bos_descriptor(struct usb_device *dev);
diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index 9e42023e2962..5b2046ee3a84 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -104,11 +104,17 @@ static int dwc3_get_dr_mode(struct dwc3 *dwc)
void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode)
{
- u32 reg;
+ u32 reg, reg_mode;
+
+ /* Set PRTCAPDIR to be device mode for disconnect */
+ if (mode == DWC3_GCTL_PRTCAP_NONE)
+ reg_mode = DWC3_GCTL_PRTCAP_DEVICE;
+ else
+ reg_mode = mode;
reg = dwc3_readl(dwc->regs, DWC3_GCTL);
reg &= ~(DWC3_GCTL_PRTCAPDIR(DWC3_GCTL_PRTCAP_OTG));
- reg |= DWC3_GCTL_PRTCAPDIR(mode);
+ reg |= DWC3_GCTL_PRTCAPDIR(reg_mode);
dwc3_writel(dwc->regs, DWC3_GCTL, reg);
dwc->current_dr_role = mode;
@@ -117,6 +123,7 @@ void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode)
static void __dwc3_set_mode(struct work_struct *work)
{
struct dwc3 *dwc = work_to_dwc(work);
+ struct dwc3_platform_data *dwc3_pdata;
unsigned long flags;
int ret;
u32 reg;
@@ -229,6 +236,10 @@ static void __dwc3_set_mode(struct work_struct *work)
break;
}
+ dwc3_pdata = (struct dwc3_platform_data *)dev_get_platdata(dwc->dev);
+ if (dwc3_pdata && dwc3_pdata->set_role_post)
+ dwc3_pdata->set_role_post(dwc, dwc->desired_dr_role);
+
out:
pm_runtime_mark_last_busy(dwc->dev);
pm_runtime_put_autosuspend(dwc->dev);
@@ -362,9 +373,64 @@ done:
*/
static void dwc3_frame_length_adjustment(struct dwc3 *dwc)
{
+ struct dwc3_platform_data *dwc3_pdata;
u32 reg;
u32 dft;
+ dwc3_pdata = (struct dwc3_platform_data *)dev_get_platdata(dwc->dev);
+ if (dwc3_pdata && dwc3_pdata->quirks & DWC3_SOFT_ITP_SYNC) {
+ u32 ref_clk_hz, ref_clk_period_integer;
+ unsigned long long temp;
+ struct device_node *node = dwc->dev->of_node;
+ struct clk *ref_clk;
+
+ reg = dwc3_readl(dwc->regs, DWC3_GCTL);
+ reg |= DWC3_GCTL_SOFITPSYNC;
+ dwc3_writel(dwc->regs, DWC3_GCTL, reg);
+
+ /*
+ * if GCTL.SOFITPSYNC is set to '1':
+ * FLADJ_REF_CLK_FLADJ=
+ * ((125000/ref_clk_period_integer)-(125000/ref_clk_period)) *
+ * ref_clk_period
+ * where
+ * - the ref_clk_period_integer is the integer value of
+ * the ref_clk period got by truncating the decimal
+ * (fractional) value that is programmed in the
+ * GUCTL.REF_CLK_PERIOD field.
+ * - the ref_clk_period is the ref_clk period including
+ * the fractional value.
+ */
+ ref_clk = of_clk_get_by_name(node, "ref");
+ if (IS_ERR(ref_clk)) {
+ dev_err(dwc->dev, "Can't get ref clock for fladj\n");
+ return;
+ }
+ reg = dwc3_readl(dwc->regs, DWC3_GFLADJ);
+ ref_clk_hz = clk_get_rate(ref_clk);
+ clk_put(ref_clk);
+ if (ref_clk_hz == 0) {
+ dev_err(dwc->dev, "ref clk is 0, can't set fladj\n");
+ return;
+ }
+
+ /* nano seconds the period of ref_clk */
+ ref_clk_period_integer = DIV_ROUND_DOWN_ULL(1000000000, ref_clk_hz);
+ temp = 125000ULL * 1000000000ULL;
+ temp = DIV_ROUND_DOWN_ULL(temp, ref_clk_hz);
+ temp = DIV_ROUND_DOWN_ULL(temp, ref_clk_period_integer);
+ temp = temp - 125000;
+ temp = temp << GFLADJ_REFCLK_FLADJ_SHIFT;
+ reg &= ~GFLADJ_REFCLK_FLADJ_MASK;
+ reg |= temp;
+ dwc3_writel(dwc->regs, DWC3_GFLADJ, reg);
+
+ reg = dwc3_readl(dwc->regs, DWC3_GUCTL);
+ reg &= ~DWC3_GUCTL_REFCLKPER_MASK;
+ reg |= ref_clk_period_integer << DWC3_GUCTL_REFCLKPER_SHIFT;
+ dwc3_writel(dwc->regs, DWC3_GUCTL, reg);
+ }
+
if (DWC3_VER_IS_PRIOR(DWC3, 250A))
return;
@@ -782,6 +848,8 @@ static void dwc3_core_exit(struct dwc3 *dwc)
clk_bulk_disable_unprepare(dwc->num_clks, dwc->clks);
reset_control_assert(dwc->reset);
+
+ dwc->core_inited = false;
}
static bool dwc3_core_is_valid(struct dwc3 *dwc)
@@ -974,6 +1042,79 @@ static void dwc3_set_incr_burst_type(struct dwc3 *dwc)
dwc3_writel(dwc->regs, DWC3_GSBUSCFG0, cfg);
}
+static void dwc3_set_power_down_clk_scale(struct dwc3 *dwc)
+{
+ struct device *dev = dwc->dev;
+ struct device_node *node = dev->of_node;
+ struct clk *suspend_clk;
+ u32 reg, scale;
+
+ if (dwc->num_clks == 0)
+ return;
+
+ /*
+ * The power down scale field specifies how many suspend_clk
+ * periods fit into a 16KHz clock period. When performing
+ * the division, round up the remainder.
+ */
+ suspend_clk = of_clk_get_by_name(node, "suspend");
+ if (IS_ERR(suspend_clk))
+ return;
+
+ scale = DIV_ROUND_UP(clk_get_rate(suspend_clk), 16384);
+ reg = dwc3_readl(dwc->regs, DWC3_GCTL);
+ reg &= ~(DWC3_GCTL_PWRDNSCALE_MASK);
+ reg |= DWC3_GCTL_PWRDNSCALE(scale);
+ dwc3_writel(dwc->regs, DWC3_GCTL, reg);
+}
+
+#ifdef CONFIG_OF
+struct dwc3_cache_type {
+ u8 transfer_type_datard;
+ u8 transfer_type_descrd;
+ u8 transfer_type_datawr;
+ u8 transfer_type_descwr;
+};
+
+static const struct dwc3_cache_type ls1088a_dwc3_cache_type = {
+ .transfer_type_datard = 2,
+ .transfer_type_descrd = 2,
+ .transfer_type_datawr = 2,
+ .transfer_type_descwr = 2,
+};
+
+/**
+ * dwc3_set_cache_type - Configure cache type registers
+ * @dwc: Pointer to our controller context structure
+ */
+static void dwc3_set_cache_type(struct dwc3 *dwc)
+{
+ u32 tmp, reg;
+ const struct dwc3_cache_type *cache_type =
+ device_get_match_data(dwc->dev);
+
+ if (cache_type) {
+ reg = dwc3_readl(dwc->regs, DWC3_GSBUSCFG0);
+ tmp = reg;
+
+ reg &= ~DWC3_GSBUSCFG0_DATARD(~0);
+ reg |= DWC3_GSBUSCFG0_DATARD(cache_type->transfer_type_datard);
+
+ reg &= ~DWC3_GSBUSCFG0_DESCRD(~0);
+ reg |= DWC3_GSBUSCFG0_DESCRD(cache_type->transfer_type_descrd);
+
+ reg &= ~DWC3_GSBUSCFG0_DATAWR(~0);
+ reg |= DWC3_GSBUSCFG0_DATAWR(cache_type->transfer_type_datawr);
+
+ reg &= ~DWC3_GSBUSCFG0_DESCWR(~0);
+ reg |= DWC3_GSBUSCFG0_DESCWR(cache_type->transfer_type_descwr);
+
+ if (tmp != reg)
+ dwc3_writel(dwc->regs, DWC3_GSBUSCFG0, reg);
+ }
+}
+#endif
+
/**
* dwc3_core_init - Low-level initialization of DWC3 Core
* @dwc: Pointer to our controller context structure
@@ -994,6 +1135,8 @@ static int dwc3_core_init(struct dwc3 *dwc)
*/
dwc3_writel(dwc->regs, DWC3_GUID, LINUX_VERSION_CODE);
+ dwc3_set_power_down_clk_scale(dwc);
+
ret = dwc3_phy_setup(dwc);
if (ret)
goto err0;
@@ -1060,6 +1203,10 @@ static int dwc3_core_init(struct dwc3 *dwc)
dwc3_set_incr_burst_type(dwc);
+#ifdef CONFIG_OF
+ dwc3_set_cache_type(dwc);
+#endif
+
usb_phy_set_suspend(dwc->usb2_phy, 0);
usb_phy_set_suspend(dwc->usb3_phy, 0);
ret = phy_power_on(dwc->usb2_generic_phy);
@@ -1167,6 +1314,8 @@ static int dwc3_core_init(struct dwc3 *dwc)
}
}
+ dwc->core_inited = true;
+
return 0;
err4:
@@ -1249,6 +1398,7 @@ static int dwc3_core_get_phy(struct dwc3 *dwc)
static int dwc3_core_init_mode(struct dwc3 *dwc)
{
+ struct dwc3_platform_data *dwc3_pdata;
struct device *dev = dwc->dev;
int ret;
@@ -1288,6 +1438,10 @@ static int dwc3_core_init_mode(struct dwc3 *dwc)
return -EINVAL;
}
+ dwc3_pdata = (struct dwc3_platform_data *)dev_get_platdata(dwc->dev);
+ if (dwc3_pdata && dwc3_pdata->set_role_post)
+ dwc3_pdata->set_role_post(dwc, dwc->current_dr_role);
+
return 0;
}
@@ -1348,6 +1502,17 @@ static void dwc3_get_properties(struct dwc3 *dwc)
dwc->maximum_speed = usb_get_maximum_speed(dev);
dwc->max_ssp_rate = usb_get_maximum_ssp_rate(dev);
dwc->dr_mode = usb_get_dr_mode(dev);
+ if (dwc->dr_mode == USB_DR_MODE_OTG) {
+ dwc->otg_caps.otg_rev = 0x0300;
+ dwc->otg_caps.hnp_support = true;
+ dwc->otg_caps.srp_support = true;
+ dwc->otg_caps.adp_support = true;
+
+ /* Update otg capabilities by DT properties */
+ of_usb_update_otg_caps(dev->of_node,
+ &dwc->otg_caps);
+ }
+
dwc->hsphy_mode = of_usb_get_phy_mode(dev->of_node);
dwc->sysdev_is_parent = device_property_read_bool(dev,
@@ -1448,6 +1613,9 @@ static void dwc3_get_properties(struct dwc3 *dwc)
dwc->dis_split_quirk = device_property_read_bool(dev,
"snps,dis-split-quirk");
+ dwc->host_vbus_glitches = device_property_read_bool(dev,
+ "snps,host-vbus-glitches");
+
dwc->lpm_nyet_threshold = lpm_nyet_threshold;
dwc->tx_de_emphasis = tx_de_emphasis;
@@ -1807,6 +1975,11 @@ static int dwc3_suspend_common(struct dwc3 *dwc, pm_message_t msg)
u32 reg;
switch (dwc->current_dr_role) {
+ case DWC3_GCTL_PRTCAP_NONE:
+ if (pm_runtime_suspended(dwc->dev))
+ break;
+ dwc3_core_exit(dwc);
+ break;
case DWC3_GCTL_PRTCAP_DEVICE:
if (pm_runtime_suspended(dwc->dev))
break;
@@ -1865,7 +2038,24 @@ static int dwc3_resume_common(struct dwc3 *dwc, pm_message_t msg)
u32 reg;
switch (dwc->current_dr_role) {
+ case DWC3_GCTL_PRTCAP_NONE:
+ if (dwc->core_inited)
+ break;
+
+ ret = dwc3_core_init_for_resume(dwc);
+ if (ret)
+ return ret;
+ break;
case DWC3_GCTL_PRTCAP_DEVICE:
+ /*
+ * system resume may come after runtime resume
+ * e.g. rpm suspend -> pm suspend -> wakeup
+ * -> rpm resume -> system resume, so if already
+ * runtime resumed, system resume should skip it.
+ */
+ if (dwc->core_inited)
+ break;
+
ret = dwc3_core_init_for_resume(dwc);
if (ret)
return ret;
@@ -1951,8 +2141,6 @@ static int dwc3_runtime_suspend(struct device *dev)
if (ret)
return ret;
- device_init_wakeup(dev, true);
-
return 0;
}
@@ -1961,8 +2149,6 @@ static int dwc3_runtime_resume(struct device *dev)
struct dwc3 *dwc = dev_get_drvdata(dev);
int ret;
- device_init_wakeup(dev, false);
-
ret = dwc3_resume_common(dwc, PMSG_AUTO_RESUME);
if (ret)
return ret;
@@ -2062,12 +2248,16 @@ static const struct dev_pm_ops dwc3_dev_pm_ops = {
#ifdef CONFIG_OF
static const struct of_device_id of_dwc3_match[] = {
- {
- .compatible = "snps,dwc3"
- },
- {
- .compatible = "synopsys,dwc3"
- },
+ { .compatible = "fsl,ls1012a-dwc3", .data = &ls1088a_dwc3_cache_type, },
+ { .compatible = "fsl,ls1021a-dwc3", .data = &ls1088a_dwc3_cache_type, },
+ { .compatible = "fsl,ls1028a-dwc3", .data = &ls1088a_dwc3_cache_type, },
+ { .compatible = "fsl,ls1043a-dwc3", .data = &ls1088a_dwc3_cache_type, },
+ { .compatible = "fsl,ls1046a-dwc3", .data = &ls1088a_dwc3_cache_type, },
+ { .compatible = "fsl,ls1088a-dwc3", .data = &ls1088a_dwc3_cache_type, },
+ { .compatible = "fsl,ls2088a-dwc3", .data = &ls1088a_dwc3_cache_type, },
+ { .compatible = "fsl,lx2160a-dwc3", .data = &ls1088a_dwc3_cache_type, },
+ { .compatible = "snps,dwc3" },
+ { .compatible = "synopsys,dwc3" },
{ },
};
MODULE_DEVICE_TABLE(of, of_dwc3_match);
diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
index 3dcb5b744f7c..d5ec930db5c7 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -30,6 +30,7 @@
#include <linux/ulpi/interface.h>
#include <linux/phy/phy.h>
+#include "../host/xhci-plat.h"
#include <linux/power_supply.h>
@@ -172,6 +173,21 @@
/* Bit fields */
/* Global SoC Bus Configuration INCRx Register 0 */
+#ifdef CONFIG_OF
+#define DWC3_GSBUSCFG0_DATARD_SHIFT 28
+#define DWC3_GSBUSCFG0_DATARD(n) (((n) & 0xf) \
+ << DWC3_GSBUSCFG0_DATARD_SHIFT)
+#define DWC3_GSBUSCFG0_DESCRD_SHIFT 24
+#define DWC3_GSBUSCFG0_DESCRD(n) (((n) & 0xf) \
+ << DWC3_GSBUSCFG0_DESCRD_SHIFT)
+#define DWC3_GSBUSCFG0_DATAWR_SHIFT 20
+#define DWC3_GSBUSCFG0_DATAWR(n) (((n) & 0xf) \
+ << DWC3_GSBUSCFG0_DATAWR_SHIFT)
+#define DWC3_GSBUSCFG0_DESCWR_SHIFT 16
+#define DWC3_GSBUSCFG0_DESCWR(n) (((n) & 0xf) \
+ << DWC3_GSBUSCFG0_DESCWR_SHIFT)
+#endif
+
#define DWC3_GSBUSCFG0_INCR256BRSTENA (1 << 7) /* INCR256 burst */
#define DWC3_GSBUSCFG0_INCR128BRSTENA (1 << 6) /* INCR128 burst */
#define DWC3_GSBUSCFG0_INCR64BRSTENA (1 << 5) /* INCR64 burst */
@@ -230,6 +246,7 @@
/* Global Configuration Register */
#define DWC3_GCTL_PWRDNSCALE(n) ((n) << 19)
+#define DWC3_GCTL_PWRDNSCALE_MASK DWC3_GCTL_PWRDNSCALE(0x1fff)
#define DWC3_GCTL_U2RSTECN BIT(16)
#define DWC3_GCTL_RAMCLKSEL(x) (((x) & DWC3_GCTL_CLK_MASK) << 6)
#define DWC3_GCTL_CLK_BUS (0)
@@ -239,6 +256,7 @@
#define DWC3_GCTL_PRTCAP(n) (((n) & (3 << 12)) >> 12)
#define DWC3_GCTL_PRTCAPDIR(n) ((n) << 12)
+#define DWC3_GCTL_PRTCAP_NONE 0
#define DWC3_GCTL_PRTCAP_HOST 1
#define DWC3_GCTL_PRTCAP_DEVICE 2
#define DWC3_GCTL_PRTCAP_OTG 3
@@ -252,6 +270,11 @@
#define DWC3_GCTL_GBLHIBERNATIONEN BIT(1)
#define DWC3_GCTL_DSBLCLKGTNG BIT(0)
+/* Global User Control Register */
+#define DWC3_GUCTL_HSTINAUTORETRY BIT(14)
+#define DWC3_GUCTL_REFCLKPER_MASK GENMASK(31, 22)
+#define DWC3_GUCTL_REFCLKPER_SHIFT 22
+
/* Global User Control 1 Register */
#define DWC3_GUCTL1_DEV_DECOUPLE_L1L2_EVT BIT(31)
#define DWC3_GUCTL1_TX_IPGAP_LINECHECK_DIS BIT(28)
@@ -384,6 +407,8 @@
/* Global Frame Length Adjustment Register */
#define DWC3_GFLADJ_30MHZ_SDBND_SEL BIT(7)
#define DWC3_GFLADJ_30MHZ_MASK 0x3f
+#define GFLADJ_REFCLK_FLADJ_MASK GENMASK(21, 8)
+#define GFLADJ_REFCLK_FLADJ_SHIFT 8
/* Global User Control Register 2 */
#define DWC3_GUCTL2_RST_ACTBITLATER BIT(14)
@@ -941,6 +966,13 @@ struct dwc3_scratchpad_array {
__le64 dma_adr[DWC3_MAX_HIBER_SCRATCHBUFS];
};
+struct dwc3_platform_data {
+ struct xhci_plat_priv *xhci_priv;
+ void (*set_role_post)(struct dwc3 *dwc, u32 role);
+ unsigned long long quirks;
+#define DWC3_SOFT_ITP_SYNC BIT(0)
+};
+
/**
* struct dwc3 - representation of our controller
* @drd_work: workqueue used for role swapping
@@ -984,6 +1016,7 @@ struct dwc3_scratchpad_array {
* @ip: controller's ID
* @revision: controller's version of an IP
* @version_type: VERSIONTYPE register contents, a sub release of a revision
+ * @otg_caps: the OTG capabilities from hardware point
* @dr_mode: requested mode of operation
* @current_dr_role: current role of operation when in dual-role mode
* @desired_dr_role: desired role of operation when in dual-role mode
@@ -1082,6 +1115,8 @@ struct dwc3_scratchpad_array {
* 3 - Reserved
* @dis_metastability_quirk: set to disable metastability quirk.
* @dis_split_quirk: set to disable split boundary.
+ * @host_vbus_glitches: set to avoid vbus glitch during
+ * xhci reset.
* @suspended: set to track suspend event due to U3/L2.
* @imod_interval: set the interrupt moderation interval in 250ns
* increments or 0 to disable.
@@ -1125,6 +1160,7 @@ struct dwc3 {
struct clk_bulk_data *clks;
int num_clks;
+ bool core_inited;
struct reset_control *reset;
struct usb_phy *usb2_phy;
@@ -1164,6 +1200,7 @@ struct dwc3 {
u32 gadget_max_speed;
enum usb_ssp_rate max_ssp_rate;
enum usb_ssp_rate gadget_ssp_rate;
+ struct usb_otg_caps otg_caps;
u32 ip;
@@ -1293,6 +1330,7 @@ struct dwc3 {
unsigned tx_de_emphasis:2;
unsigned dis_metastability_quirk:1;
+ unsigned host_vbus_glitches:1;
unsigned dis_split_quirk:1;
unsigned async_callbacks:1;
diff --git a/drivers/usb/dwc3/drd.c b/drivers/usb/dwc3/drd.c
index ba37bc72a220..b97b28cffd4d 100644
--- a/drivers/usb/dwc3/drd.c
+++ b/drivers/usb/dwc3/drd.c
@@ -496,6 +496,8 @@ static int dwc3_usb_role_switch_set(struct usb_role_switch *sw,
default:
if (dwc->role_switch_default_mode == USB_DR_MODE_HOST)
mode = DWC3_GCTL_PRTCAP_HOST;
+ else if (dwc->role_switch_default_mode == USB_DR_MODE_UNKNOWN)
+ mode = DWC3_GCTL_PRTCAP_NONE;
else
mode = DWC3_GCTL_PRTCAP_DEVICE;
break;
@@ -525,6 +527,8 @@ static enum usb_role dwc3_usb_role_switch_get(struct usb_role_switch *sw)
default:
if (dwc->role_switch_default_mode == USB_DR_MODE_HOST)
role = USB_ROLE_HOST;
+ else if (dwc->role_switch_default_mode == USB_DR_MODE_UNKNOWN)
+ role = USB_ROLE_NONE;
else
role = USB_ROLE_DEVICE;
break;
@@ -539,11 +543,16 @@ static int dwc3_setup_role_switch(struct dwc3 *dwc)
u32 mode;
dwc->role_switch_default_mode = usb_get_role_switch_default_mode(dwc->dev);
- if (dwc->role_switch_default_mode == USB_DR_MODE_HOST) {
+ switch (dwc->role_switch_default_mode) {
+ case USB_DR_MODE_HOST:
mode = DWC3_GCTL_PRTCAP_HOST;
- } else {
- dwc->role_switch_default_mode = USB_DR_MODE_PERIPHERAL;
+ break;
+ case USB_DR_MODE_UNKNOWN:
+ mode = DWC3_GCTL_PRTCAP_NONE;
+ break;
+ default:
mode = DWC3_GCTL_PRTCAP_DEVICE;
+ break;
}
dwc3_set_mode(dwc, mode);
diff --git a/drivers/usb/dwc3/dwc3-imx8mp.c b/drivers/usb/dwc3/dwc3-imx8mp.c
index d328d20abfbc..503bf4a3fdef 100644
--- a/drivers/usb/dwc3/dwc3-imx8mp.c
+++ b/drivers/usb/dwc3/dwc3-imx8mp.c
@@ -5,6 +5,7 @@
* Copyright (c) 2020 NXP.
*/
+#include <linux/busfreq-imx.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/io.h>
@@ -95,6 +96,46 @@ static irqreturn_t dwc3_imx8mp_interrupt(int irq, void *_dwc3_imx)
return IRQ_HANDLED;
}
+static void dwc3_imx8mp_set_role_post(struct dwc3 *dwc, u32 role)
+{
+ switch (role) {
+ case DWC3_GCTL_PRTCAP_HOST:
+ /*
+ * For xhci host, we need disable dwc core auto
+ * suspend, because during this auto suspend delay(5s),
+ * xhci host RUN_STOP is cleared and wakeup is not
+ * enabled, if device is inserted, xhci host can't
+ * response the connection.
+ */
+ pm_runtime_dont_use_autosuspend(dwc->dev);
+ break;
+ case DWC3_GCTL_PRTCAP_DEVICE:
+ pm_runtime_use_autosuspend(dwc->dev);
+ break;
+ default:
+ break;
+ }
+}
+
+static struct xhci_plat_priv dwc3_imx8mp_xhci_priv = {
+ .quirks = XHCI_MISSING_CAS |
+ XHCI_SKIP_PHY_INIT,
+};
+
+static struct dwc3_platform_data dwc3_imx8mp_pdata = {
+ .xhci_priv = &dwc3_imx8mp_xhci_priv,
+ .set_role_post = dwc3_imx8mp_set_role_post,
+ .quirks = DWC3_SOFT_ITP_SYNC,
+};
+
+static struct of_dev_auxdata dwc3_imx8mp_auxdata[] = {
+ {
+ .compatible = "snps,dwc3",
+ .platform_data = &dwc3_imx8mp_pdata,
+ },
+ {},
+};
+
static int dwc3_imx8mp_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -119,11 +160,12 @@ static int dwc3_imx8mp_probe(struct platform_device *pdev)
if (IS_ERR(dwc3_imx->glue_base))
return PTR_ERR(dwc3_imx->glue_base);
+ request_bus_freq(BUS_FREQ_HIGH);
dwc3_imx->hsio_clk = devm_clk_get(dev, "hsio");
if (IS_ERR(dwc3_imx->hsio_clk)) {
err = PTR_ERR(dwc3_imx->hsio_clk);
dev_err(dev, "Failed to get hsio clk, err=%d\n", err);
- return err;
+ goto rel_high_bus;
}
err = clk_prepare_enable(dwc3_imx->hsio_clk);
@@ -165,7 +207,7 @@ static int dwc3_imx8mp_probe(struct platform_device *pdev)
goto disable_rpm;
}
- err = of_platform_populate(node, NULL, NULL, dev);
+ err = of_platform_populate(node, NULL, dwc3_imx8mp_auxdata, dev);
if (err) {
dev_err(&pdev->dev, "failed to create dwc3 core\n");
goto err_node_put;
@@ -202,6 +244,8 @@ disable_clks:
clk_disable_unprepare(dwc3_imx->suspend_clk);
disable_hsio_clk:
clk_disable_unprepare(dwc3_imx->hsio_clk);
+rel_high_bus:
+ release_bus_freq(BUS_FREQ_HIGH);
return err;
}
@@ -216,7 +260,7 @@ static int dwc3_imx8mp_remove(struct platform_device *pdev)
clk_disable_unprepare(dwc3_imx->suspend_clk);
clk_disable_unprepare(dwc3_imx->hsio_clk);
-
+ release_bus_freq(BUS_FREQ_HIGH);
pm_runtime_disable(dev);
pm_runtime_put_noidle(dev);
platform_set_drvdata(pdev, NULL);
@@ -234,6 +278,7 @@ static int __maybe_unused dwc3_imx8mp_suspend(struct dwc3_imx8mp *dwc3_imx,
if (PMSG_IS_AUTO(msg) || device_may_wakeup(dwc3_imx->dev))
dwc3_imx8mp_wakeup_enable(dwc3_imx);
+ release_bus_freq(BUS_FREQ_HIGH);
dwc3_imx->pm_suspended = true;
return 0;
@@ -248,6 +293,7 @@ static int __maybe_unused dwc3_imx8mp_resume(struct dwc3_imx8mp *dwc3_imx,
if (!dwc3_imx->pm_suspended)
return 0;
+ request_bus_freq(BUS_FREQ_HIGH);
/* Wakeup disable */
dwc3_imx8mp_wakeup_disable(dwc3_imx);
dwc3_imx->pm_suspended = false;
diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c
index 8ada601901cf..fc0a344b05f5 100644
--- a/drivers/usb/dwc3/gadget.c
+++ b/drivers/usb/dwc3/gadget.c
@@ -4323,6 +4323,10 @@ int dwc3_gadget_init(struct dwc3 *dwc)
dwc->gadget->sg_supported = true;
dwc->gadget->name = "dwc3-gadget";
dwc->gadget->lpm_capable = !dwc->usb2_gadget_lpm_disable;
+ dwc->gadget->is_otg = (dwc->dr_mode == USB_DR_MODE_OTG) &&
+ (dwc->otg_caps.hnp_support ||
+ dwc->otg_caps.srp_support ||
+ dwc->otg_caps.adp_support);
/*
* FIXME We might be setting max_speed to <SUPER, however versions
diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c
index 2078e9d70292..abccf84a4808 100644
--- a/drivers/usb/dwc3/host.c
+++ b/drivers/usb/dwc3/host.c
@@ -10,8 +10,49 @@
#include <linux/acpi.h>
#include <linux/platform_device.h>
+#include "../host/xhci.h"
+
#include "core.h"
+
+#define XHCI_HCSPARAMS1 0x4
+#define XHCI_PORTSC_BASE 0x400
+
+/*
+ * dwc3_power_off_all_roothub_ports - Power off all Root hub ports
+ * @dwc3: Pointer to our controller context structure
+ */
+static void dwc3_power_off_all_roothub_ports(struct dwc3 *dwc)
+{
+ int i, port_num;
+ u32 reg, op_regs_base, offset;
+ void __iomem *xhci_regs;
+
+ /* xhci regs is not mapped yet, do it temperary here */
+ if (dwc->xhci_resources[0].start) {
+ xhci_regs = ioremap(dwc->xhci_resources[0].start,
+ DWC3_XHCI_REGS_END);
+ if (IS_ERR(xhci_regs)) {
+ dev_err(dwc->dev, "Failed to ioremap xhci_regs\n");
+ return;
+ }
+
+ op_regs_base = HC_LENGTH(readl(xhci_regs));
+ reg = readl(xhci_regs + XHCI_HCSPARAMS1);
+ port_num = HCS_MAX_PORTS(reg);
+
+ for (i = 1; i <= port_num; i++) {
+ offset = op_regs_base + XHCI_PORTSC_BASE + 0x10*(i-1);
+ reg = readl(xhci_regs + offset);
+ reg &= ~PORT_POWER;
+ writel(reg, xhci_regs + offset);
+ }
+
+ iounmap(xhci_regs);
+ } else
+ dev_err(dwc->dev, "xhci base reg invalid\n");
+}
+
static int dwc3_host_get_irq(struct dwc3 *dwc)
{
struct platform_device *dwc3_pdev = to_platform_device(dwc->dev);
@@ -46,11 +87,19 @@ int dwc3_host_init(struct dwc3 *dwc)
{
struct property_entry props[4];
struct platform_device *xhci;
+ struct dwc3_platform_data *dwc3_pdata;
int ret, irq;
struct resource *res;
struct platform_device *dwc3_pdev = to_platform_device(dwc->dev);
int prop_idx = 0;
+ /*
+ * We have to power off all Root hub ports immediately after DWC3 set
+ * to host mode to avoid VBUS glitch happen when xhci get reset later.
+ */
+ if (dwc->host_vbus_glitches)
+ dwc3_power_off_all_roothub_ports(dwc);
+
irq = dwc3_host_get_irq(dwc);
if (irq < 0)
return irq;
@@ -115,6 +164,14 @@ int dwc3_host_init(struct dwc3 *dwc)
}
}
+ dwc3_pdata = (struct dwc3_platform_data *)dev_get_platdata(dwc->dev);
+ if (dwc3_pdata && dwc3_pdata->xhci_priv) {
+ ret = platform_device_add_data(xhci, dwc3_pdata->xhci_priv,
+ sizeof(struct xhci_plat_priv));
+ if (ret)
+ goto err;
+ }
+
ret = platform_device_add(xhci);
if (ret) {
dev_err(dwc->dev, "failed to register xHCI device\n");
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
index 02044d45edde..65198db330b4 100644
--- a/drivers/usb/host/ehci-hcd.c
+++ b/drivers/usb/host/ehci-hcd.c
@@ -82,7 +82,7 @@ module_param (log2_irq_thresh, int, S_IRUGO);
MODULE_PARM_DESC (log2_irq_thresh, "log2 IRQ latency, 1-64 microframes");
/* initial park setting: slower than hw default */
-static unsigned park;
+static unsigned park = 3;
module_param (park, uint, S_IRUGO);
MODULE_PARM_DESC (park, "park setting; 1-3 back-to-back async packets");
diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c
index b9754784161d..010819e736c9 100644
--- a/drivers/usb/host/xhci-hub.c
+++ b/drivers/usb/host/xhci-hub.c
@@ -1516,6 +1516,16 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
/* 4.19.6 Port Test Modes (USB2 Test Mode) */
if (hcd->speed != HCD_USB2)
goto error;
+
+#ifdef CONFIG_USB_HCD_TEST_MODE
+ if (test_mode == EHSET_TEST_SINGLE_STEP_SET_FEATURE) {
+ spin_unlock_irqrestore(&xhci->lock, flags);
+ retval = ehset_single_step_set_feature(hcd,
+ wIndex + 1);
+ spin_lock_irqsave(&xhci->lock, flags);
+ break;
+ }
+#endif
if (test_mode > USB_TEST_FORCE_ENABLE ||
test_mode < USB_TEST_J)
goto error;
@@ -1840,7 +1850,8 @@ static bool xhci_port_missing_cas_quirk(struct xhci_port *port)
return false;
if (((portsc & PORT_PLS_MASK) != XDEV_POLLING) &&
- ((portsc & PORT_PLS_MASK) != XDEV_COMP_MODE))
+ ((portsc & PORT_PLS_MASK) != XDEV_COMP_MODE) &&
+ ((portsc & PORT_PLS_MASK) != XDEV_RXDETECT))
return false;
/* clear wakeup/change bits, and do a warm port reset */
diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c
index e56a1fb9715a..4e813829afa6 100644
--- a/drivers/usb/host/xhci-plat.c
+++ b/drivers/usb/host/xhci-plat.c
@@ -325,6 +325,10 @@ static int xhci_plat_probe(struct platform_device *pdev)
device_property_read_u32(tmpdev, "imod-interval-ns",
&xhci->imod_interval);
+
+ if (device_property_read_bool(tmpdev,
+ "usb3-resume-missing-cas"))
+ xhci->quirks |= XHCI_MISSING_CAS;
}
hcd->usb_phy = devm_usb_get_phy_by_phandle(sysdev, "usb-phy", 0);
@@ -372,7 +376,8 @@ static int xhci_plat_probe(struct platform_device *pdev)
* Prevent runtime pm from being on as default, users should enable
* runtime pm using power/control in sysfs.
*/
- pm_runtime_forbid(&pdev->dev);
+ if (!(xhci->quirks & XHCI_DEFAULT_PM_RUNTIME_ALLOW))
+ pm_runtime_forbid(&pdev->dev);
return 0;
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index 945ed5f3e858..432d11e5526d 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -2325,12 +2325,9 @@ static int process_ctrl_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep,
switch (trb_comp_code) {
case COMP_SUCCESS:
- if (trb_type != TRB_STATUS) {
- xhci_warn(xhci, "WARN: Success on ctrl %s TRB without IOC set?\n",
+ if (trb_type != TRB_STATUS)
+ xhci_dbg(xhci, "Success on ctrl %s TRB without IOC set?\n",
(trb_type == TRB_DATA) ? "data" : "setup");
- td->status = -ESHUTDOWN;
- break;
- }
td->status = 0;
break;
case COMP_SHORT_PACKET:
@@ -3883,6 +3880,129 @@ int xhci_queue_ctrl_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
return 0;
}
+#ifdef CONFIG_USB_HCD_TEST_MODE
+/*
+ * This function prepare TRBs and submits them for the
+ * SINGLE_STEP_SET_FEATURE Test.
+ * This is done in two parts: first SETUP req for GetDesc is sent then
+ * 15 seconds later, the IN stage for GetDesc starts to req data from dev
+ *
+ * is_setup : argument decides which of the two stage needs to be
+ * performed; TRUE - SETUP and FALSE - IN+STATUS
+ * Returns 0 if success
+ */
+int xhci_submit_single_step_set_feature(struct usb_hcd *hcd,
+ struct urb *urb, int is_setup)
+{
+ int slot_id;
+ unsigned int ep_index;
+ struct xhci_ring *ep_ring;
+ int ret;
+ struct usb_ctrlrequest *setup;
+ struct xhci_generic_trb *start_trb;
+ int start_cycle;
+ u32 field, length_field, remainder;
+ struct urb_priv *urb_priv;
+ struct xhci_td *td;
+ struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+
+ /* urb_priv will be free after transcation has completed */
+ urb_priv = kzalloc(sizeof(struct urb_priv) +
+ sizeof(struct xhci_td), GFP_KERNEL);
+ if (!urb_priv)
+ return -ENOMEM;
+
+ td = &urb_priv->td[0];
+ urb_priv->num_tds = 1;
+ urb_priv->num_tds_done = 0;
+ urb->hcpriv = urb_priv;
+
+ ep_ring = xhci_urb_to_transfer_ring(xhci, urb);
+ if (!ep_ring) {
+ ret = -EINVAL;
+ goto free_priv;
+ }
+
+ slot_id = urb->dev->slot_id;
+ ep_index = xhci_get_endpoint_index(&urb->ep->desc);
+
+ setup = (struct usb_ctrlrequest *) urb->setup_packet;
+ if (is_setup) {
+ ret = prepare_transfer(xhci, xhci->devs[slot_id],
+ ep_index, urb->stream_id,
+ 1, urb, 0, GFP_KERNEL);
+ if (ret < 0)
+ goto free_priv;
+
+ start_trb = &ep_ring->enqueue->generic;
+ start_cycle = ep_ring->cycle_state;
+ /* Save the DMA address of the last TRB in the TD */
+ td->last_trb = ep_ring->enqueue;
+ field = TRB_IOC | TRB_IDT | TRB_TYPE(TRB_SETUP) | start_cycle;
+ /* xHCI 1.0/1.1 6.4.1.2.1: Transfer Type field */
+ if ((xhci->hci_version >= 0x100) ||
+ (xhci->quirks & XHCI_MTK_HOST))
+ field |= TRB_TX_TYPE(TRB_DATA_IN);
+
+ queue_trb(xhci, ep_ring, false,
+ setup->bRequestType | setup->bRequest << 8 |
+ le16_to_cpu(setup->wValue) << 16,
+ le16_to_cpu(setup->wIndex) |
+ le16_to_cpu(setup->wLength) << 16,
+ TRB_LEN(8) | TRB_INTR_TARGET(0),
+ /* Immediate data in pointer */
+ field);
+ giveback_first_trb(xhci, slot_id, ep_index, urb->stream_id,
+ start_cycle, start_trb);
+ return 0;
+ }
+
+ ret = prepare_transfer(xhci, xhci->devs[slot_id],
+ ep_index, urb->stream_id,
+ 2, urb, 0, GFP_KERNEL);
+ if (ret < 0)
+ goto free_priv;
+
+ start_trb = &ep_ring->enqueue->generic;
+ start_cycle = ep_ring->cycle_state;
+ field = TRB_ISP | TRB_TYPE(TRB_DATA);
+
+ remainder = xhci_td_remainder(xhci, 0,
+ urb->transfer_buffer_length,
+ urb->transfer_buffer_length,
+ urb, 1);
+
+ length_field = TRB_LEN(urb->transfer_buffer_length) |
+ TRB_TD_SIZE(remainder) |
+ TRB_INTR_TARGET(0);
+
+ if (urb->transfer_buffer_length > 0) {
+ field |= TRB_DIR_IN;
+ queue_trb(xhci, ep_ring, true,
+ lower_32_bits(urb->transfer_dma),
+ upper_32_bits(urb->transfer_dma),
+ length_field,
+ field | ep_ring->cycle_state);
+ }
+
+ td->last_trb = ep_ring->enqueue;
+ field = TRB_IOC | TRB_TYPE(TRB_STATUS) | ep_ring->cycle_state;
+ queue_trb(xhci, ep_ring, false,
+ 0,
+ 0,
+ TRB_INTR_TARGET(0),
+ field);
+
+ giveback_first_trb(xhci, slot_id, ep_index, 0,
+ start_cycle, start_trb);
+
+ return 0;
+free_priv:
+ xhci_urb_free_priv(urb_priv);
+ return ret;
+}
+#endif /* CONFIG_USB_HCD_TEST_MODE */
+
/*
* The transfer burst count field of the isochronous TRB defines the number of
* bursts that are required to move all packets in this TD. Only SuperSpeed
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index 5c9d3be136d2..7dc10129f51a 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -195,7 +195,7 @@ int xhci_reset(struct xhci_hcd *xhci, u64 timeout_us)
* Without this delay, the subsequent HC register access,
* may result in a system hang very rarely.
*/
- if (xhci->quirks & XHCI_INTEL_HOST)
+ if (xhci->quirks & (XHCI_INTEL_HOST | XHCI_CDNS_HOST))
udelay(1000);
ret = xhci_handshake(&xhci->op_regs->command, CMD_RESET, 0, timeout_us);
@@ -5498,6 +5498,7 @@ static const struct hc_driver xhci_hc_driver = {
.disable_usb3_lpm_timeout = xhci_disable_usb3_lpm_timeout,
.find_raw_port_number = xhci_find_raw_port_number,
.clear_tt_buffer_complete = xhci_clear_tt_buffer_complete,
+ .submit_single_step_set_feature = xhci_submit_single_step_set_feature,
};
void xhci_init_driver(struct hc_driver *drv,
@@ -5522,6 +5523,8 @@ void xhci_init_driver(struct hc_driver *drv,
drv->check_bandwidth = over->check_bandwidth;
if (over->reset_bandwidth)
drv->reset_bandwidth = over->reset_bandwidth;
+ if (over->bus_suspend)
+ drv->bus_suspend = over->bus_suspend;
if (over->update_hub_device)
drv->update_hub_device = over->update_hub_device;
}
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index 64278cd77f98..9736534ee64c 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -1904,6 +1904,7 @@ struct xhci_hcd {
#define XHCI_NO_SOFT_RETRY BIT_ULL(40)
#define XHCI_BROKEN_D3COLD_S2I BIT_ULL(41)
#define XHCI_EP_CTX_BROKEN_DCS BIT_ULL(42)
+#define XHCI_CDNS_HOST BIT_ULL(43)
#define XHCI_SUSPEND_RESUME_CLKS BIT_ULL(43)
#define XHCI_RESET_TO_DEFAULT BIT_ULL(44)
#define XHCI_ZHAOXIN_TRB_FETCH BIT_ULL(45)
@@ -1951,6 +1952,7 @@ struct xhci_driver_overrides {
struct usb_host_endpoint *ep);
int (*check_bandwidth)(struct usb_hcd *, struct usb_device *);
void (*reset_bandwidth)(struct usb_hcd *, struct usb_device *);
+ int (*bus_suspend)(struct usb_hcd *hcd);
int (*update_hub_device)(struct usb_hcd *hcd, struct usb_device *hdev,
struct usb_tt *tt, gfp_t mem_flags);
};
@@ -2185,6 +2187,16 @@ int xhci_find_raw_port_number(struct usb_hcd *hcd, int port1);
struct xhci_hub *xhci_get_rhub(struct usb_hcd *hcd);
void xhci_hc_died(struct xhci_hcd *xhci);
+#ifdef CONFIG_USB_HCD_TEST_MODE
+int xhci_submit_single_step_set_feature(struct usb_hcd *hcd,
+ struct urb *urb, int is_setup);
+#else
+static inline int xhci_submit_single_step_set_feature(struct usb_hcd *hcd,
+ struct urb *urb, int is_setup)
+{
+ return 0;
+}
+#endif
#ifdef CONFIG_PM
int xhci_bus_suspend(struct usb_hcd *hcd);
diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig
index 52eebcb88c1f..1d62bc321966 100644
--- a/drivers/usb/phy/Kconfig
+++ b/drivers/usb/phy/Kconfig
@@ -152,7 +152,7 @@ config USB_MV_OTG
config USB_MXS_PHY
tristate "Freescale MXS USB PHY support"
- depends on ARCH_MXC || ARCH_MXS
+ depends on ARCH_MXC || ARCH_MXS || ARCH_MXC_ARM64
select STMP_DEVICE
select USB_PHY
help
diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c
index 7a7eb8af6044..7095b3025aac 100644
--- a/drivers/usb/phy/phy-mxs-usb.c
+++ b/drivers/usb/phy/phy-mxs-usb.c
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0+
/*
- * Copyright 2012-2014 Freescale Semiconductor, Inc.
+ * Copyright 2012-2016 Freescale Semiconductor, Inc.
+ * Copyright 2017 NXP
* Copyright (C) 2012 Marek Vasut <marex@denx.de>
* on behalf of DENX Software Engineering GmbH
*/
@@ -18,6 +19,8 @@
#include <linux/regmap.h>
#include <linux/mfd/syscon.h>
#include <linux/iopoll.h>
+#include <linux/regulator/consumer.h>
+#include <linux/pm_runtime.h>
#define DRIVER_NAME "mxs_phy"
@@ -70,6 +73,12 @@
#define BM_USBPHY_PLL_EN_USB_CLKS BIT(6)
/* Anatop Registers */
+#define ANADIG_PLL_USB2 0x20
+#define ANADIG_PLL_USB2_SET 0x24
+#define ANADIG_PLL_USB2_CLR 0x28
+#define ANADIG_REG_1P1_SET 0x114
+#define ANADIG_REG_1P1_CLR 0x118
+
#define ANADIG_ANA_MISC0 0x150
#define ANADIG_ANA_MISC0_SET 0x154
#define ANADIG_ANA_MISC0_CLR 0x158
@@ -117,6 +126,46 @@
#define BM_ANADIG_USB2_MISC_RX_VPIN_FS BIT(29)
#define BM_ANADIG_USB2_MISC_RX_VMIN_FS BIT(28)
+/* System Integration Module (SIM) Registers */
+#define SIM_GPR1 0x30
+
+#define USB_PHY_VLLS_WAKEUP_EN BIT(0)
+
+#define BM_ANADIG_REG_1P1_ENABLE_WEAK_LINREG BIT(18)
+#define BM_ANADIG_REG_1P1_TRACK_VDD_SOC_CAP BIT(19)
+
+#define BM_ANADIG_PLL_USB2_HOLD_RING_OFF BIT(11)
+
+/* DCD module, the offset is 0x800 */
+#define DCD_CONTROL 0x800
+#define DCD_CLOCK (DCD_CONTROL + 0x4)
+#define DCD_STATUS (DCD_CONTROL + 0x8)
+#define DCD_TIMER1 (DCD_CONTROL + 0x14)
+
+#define DCD_CONTROL_SR BIT(25)
+#define DCD_CONTROL_START BIT(24)
+#define DCD_CONTROL_BC12 BIT(17)
+#define DCD_CONTROL_IE BIT(16)
+#define DCD_CONTROL_IF BIT(8)
+#define DCD_CONTROL_IACK BIT(0)
+
+#define DCD_CLOCK_MHZ BIT(0)
+
+#define DCD_STATUS_ACTIVE BIT(22)
+#define DCD_STATUS_TO BIT(21)
+#define DCD_STATUS_ERR BIT(20)
+#define DCD_STATUS_SEQ_STAT (BIT(18) | BIT(19))
+#define DCD_CHG_PORT BIT(19)
+#define DCD_CHG_DET (BIT(18) | BIT(19))
+#define DCD_CHG_DPIN BIT(18)
+#define DCD_STATUS_SEQ_RES (BIT(16) | BIT(17))
+#define DCD_SDP_PORT BIT(16)
+#define DCD_CDP_PORT BIT(17)
+#define DCD_DCP_PORT (BIT(16) | BIT(17))
+
+#define DCD_TVDPSRC_ON_MASK GENMASK(9, 0)
+#define DCD_TVDPSRC_ON_VALUE 0xf0 /* 240ms */
+
#define to_mxs_phy(p) container_of((p), struct mxs_phy, phy)
/* Do disconnection between PHY and controller without vbus */
@@ -149,6 +198,19 @@
#define MXS_PHY_TX_D_CAL_MIN 79
#define MXS_PHY_TX_D_CAL_MAX 119
+/*
+ * At some versions, the PHY2's clock is controlled by hardware directly,
+ * eg, according to PHY's suspend status. In these PHYs, we only need to
+ * open the clock at the initialization and close it at its shutdown routine.
+ * It will be benefit for remote wakeup case which needs to send resume
+ * signal as soon as possible, and in this case, the resume signal can be sent
+ * out without software interfere.
+ */
+#define MXS_PHY_HARDWARE_CONTROL_PHY2_CLK BIT(4)
+
+/* The MXS PHYs which have DCD module for charger detection */
+#define MXS_PHY_HAS_DCD BIT(5)
+
struct mxs_phy_data {
unsigned int flags;
};
@@ -160,12 +222,14 @@ static const struct mxs_phy_data imx23_phy_data = {
static const struct mxs_phy_data imx6q_phy_data = {
.flags = MXS_PHY_SENDING_SOF_TOO_FAST |
MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS |
- MXS_PHY_NEED_IP_FIX,
+ MXS_PHY_NEED_IP_FIX |
+ MXS_PHY_HARDWARE_CONTROL_PHY2_CLK,
};
static const struct mxs_phy_data imx6sl_phy_data = {
.flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS |
- MXS_PHY_NEED_IP_FIX,
+ MXS_PHY_NEED_IP_FIX |
+ MXS_PHY_HARDWARE_CONTROL_PHY2_CLK,
};
static const struct mxs_phy_data vf610_phy_data = {
@@ -174,14 +238,21 @@ static const struct mxs_phy_data vf610_phy_data = {
};
static const struct mxs_phy_data imx6sx_phy_data = {
- .flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS,
+ .flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS |
+ MXS_PHY_HARDWARE_CONTROL_PHY2_CLK,
};
static const struct mxs_phy_data imx6ul_phy_data = {
- .flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS,
+ .flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS |
+ MXS_PHY_HARDWARE_CONTROL_PHY2_CLK,
};
static const struct mxs_phy_data imx7ulp_phy_data = {
+ .flags = MXS_PHY_HAS_DCD,
+};
+
+static const struct mxs_phy_data imx8ulp_phy_data = {
+ .flags = MXS_PHY_HAS_DCD,
};
static const struct of_device_id mxs_phy_dt_ids[] = {
@@ -192,6 +263,7 @@ static const struct of_device_id mxs_phy_dt_ids[] = {
{ .compatible = "fsl,vf610-usbphy", .data = &vf610_phy_data, },
{ .compatible = "fsl,imx6ul-usbphy", .data = &imx6ul_phy_data, },
{ .compatible = "fsl,imx7ulp-usbphy", .data = &imx7ulp_phy_data, },
+ { .compatible = "fsl,imx8ulp-usbphy", .data = &imx8ulp_phy_data, },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, mxs_phy_dt_ids);
@@ -201,9 +273,14 @@ struct mxs_phy {
struct clk *clk;
const struct mxs_phy_data *data;
struct regmap *regmap_anatop;
+ struct regmap *regmap_sim;
int port_id;
u32 tx_reg_set;
u32 tx_reg_mask;
+ struct regulator *phy_3p0;
+ bool hardware_control_phy2_clk;
+ enum usb_current_mode mode;
+ unsigned long clk_rate;
};
static inline bool is_imx6q_phy(struct mxs_phy *mxs_phy)
@@ -221,6 +298,16 @@ static inline bool is_imx7ulp_phy(struct mxs_phy *mxs_phy)
return mxs_phy->data == &imx7ulp_phy_data;
}
+static inline bool is_imx8ulp_phy(struct mxs_phy *mxs_phy)
+{
+ return mxs_phy->data == &imx8ulp_phy_data;
+}
+
+static inline bool is_imx6ul_phy(struct mxs_phy *mxs_phy)
+{
+ return mxs_phy->data == &imx6ul_phy_data;
+}
+
/*
* PHY needs some 32K cycles to switch from 32K clock to
* bus (such as AHB/AXI, etc) clock.
@@ -273,12 +360,29 @@ static int mxs_phy_pll_enable(void __iomem *base, bool enable)
return ret;
}
+/*
+ * The imx8ulp phy registers are not properly reset after a warm
+ * reset (ERR051269). Using the following steps to reset DEBUG and
+ * PLL_SIC regs. CTRL and PWD regs are reset by "SFT" bit in
+ * stmp_reset_block().
+ */
+static void mxs_phy_regs_reset(void __iomem *base)
+{
+ writel(0x7f180000, base + HW_USBPHY_DEBUG_SET);
+ writel(~0x7f180000, base + HW_USBPHY_DEBUG_CLR);
+ writel(0x00d12000, base + HW_USBPHY_PLL_SIC_SET);
+ writel(~0x00d12000, base + HW_USBPHY_PLL_SIC_CLR);
+}
+
static int mxs_phy_hw_init(struct mxs_phy *mxs_phy)
{
int ret;
void __iomem *base = mxs_phy->phy.io_priv;
- if (is_imx7ulp_phy(mxs_phy)) {
+ if (is_imx8ulp_phy(mxs_phy))
+ mxs_phy_regs_reset(base);
+
+ if (is_imx7ulp_phy(mxs_phy) || is_imx8ulp_phy(mxs_phy)) {
ret = mxs_phy_pll_enable(base, true);
if (ret)
return ret;
@@ -288,6 +392,16 @@ static int mxs_phy_hw_init(struct mxs_phy *mxs_phy)
if (ret)
goto disable_pll;
+ if (mxs_phy->phy_3p0) {
+ ret = regulator_enable(mxs_phy->phy_3p0);
+ if (ret) {
+ dev_err(mxs_phy->phy.dev,
+ "Failed to enable 3p0 regulator, ret=%d\n",
+ ret);
+ return ret;
+ }
+ }
+
/* Power up the PHY */
writel(0, base + HW_USBPHY_PWD);
@@ -326,7 +440,7 @@ static int mxs_phy_hw_init(struct mxs_phy *mxs_phy)
return 0;
disable_pll:
- if (is_imx7ulp_phy(mxs_phy))
+ if (is_imx7ulp_phy(mxs_phy) || is_imx8ulp_phy(mxs_phy))
mxs_phy_pll_enable(base, false);
return ret;
}
@@ -386,15 +500,10 @@ static void __mxs_phy_disconnect_line(struct mxs_phy *mxs_phy, bool disconnect)
usleep_range(500, 1000);
}
-static bool mxs_phy_is_otg_host(struct mxs_phy *mxs_phy)
-{
- return IS_ENABLED(CONFIG_USB_OTG) &&
- mxs_phy->phy.last_event == USB_EVENT_ID;
-}
-
static void mxs_phy_disconnect_line(struct mxs_phy *mxs_phy, bool on)
{
bool vbus_is_on = false;
+ enum usb_phy_events last_event = mxs_phy->phy.last_event;
/* If the SoCs don't need to disconnect line without vbus, quit */
if (!(mxs_phy->data->flags & MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS))
@@ -406,7 +515,8 @@ static void mxs_phy_disconnect_line(struct mxs_phy *mxs_phy, bool on)
vbus_is_on = mxs_phy_get_vbus_status(mxs_phy);
- if (on && !vbus_is_on && !mxs_phy_is_otg_host(mxs_phy))
+ if (on && ((!vbus_is_on && mxs_phy->mode != CUR_USB_MODE_HOST) ||
+ (last_event == USB_EVENT_VBUS)))
__mxs_phy_disconnect_line(mxs_phy, true);
else
__mxs_phy_disconnect_line(mxs_phy, false);
@@ -444,9 +554,12 @@ static void mxs_phy_shutdown(struct usb_phy *phy)
writel(BM_USBPHY_CTRL_CLKGATE,
phy->io_priv + HW_USBPHY_CTRL_SET);
- if (is_imx7ulp_phy(mxs_phy))
+ if (is_imx7ulp_phy(mxs_phy) || is_imx8ulp_phy(mxs_phy))
mxs_phy_pll_enable(phy->io_priv, false);
+ if (mxs_phy->phy_3p0)
+ regulator_disable(mxs_phy->phy_3p0);
+
clk_disable_unprepare(mxs_phy->clk);
}
@@ -500,14 +613,51 @@ static int mxs_phy_suspend(struct usb_phy *x, int suspend)
} else {
writel(0xffffffff, x->io_priv + HW_USBPHY_PWD);
}
+
+ /*
+ * USB2 PLL use ring VCO, when the PLL power up, the ring
+ * VCO’s supply also ramp up. There is a possibility that
+ * the ring VCO start oscillation at multi nodes in this
+ * phase, especially for VCO which has many stages, then
+ * the multiwave will be kept until PLL power down. the bit
+ * hold_ring_off can force the VCO in one determined state
+ * to avoid the multiwave issue when VCO supply start ramp
+ * up.
+ */
+ if (mxs_phy->port_id == 1 && mxs_phy->regmap_anatop)
+ regmap_write(mxs_phy->regmap_anatop,
+ ANADIG_PLL_USB2_SET,
+ BM_ANADIG_PLL_USB2_HOLD_RING_OFF);
+
writel(BM_USBPHY_CTRL_CLKGATE,
x->io_priv + HW_USBPHY_CTRL_SET);
- clk_disable_unprepare(mxs_phy->clk);
+ if (!(mxs_phy->port_id == 1 &&
+ mxs_phy->hardware_control_phy2_clk))
+ clk_disable_unprepare(mxs_phy->clk);
+ pm_runtime_put(x->dev);
} else {
+ pm_runtime_get_sync(x->dev);
mxs_phy_clock_switch_delay();
- ret = clk_prepare_enable(mxs_phy->clk);
- if (ret)
- return ret;
+ if (!(mxs_phy->port_id == 1 &&
+ mxs_phy->hardware_control_phy2_clk)) {
+ ret = clk_prepare_enable(mxs_phy->clk);
+ if (ret)
+ return ret;
+ }
+
+ /*
+ * Per IC design's requirement, hold_ring_off bit can be
+ * cleared 25us after PLL power up and 25us before any USB
+ * TX/RX.
+ */
+ if (mxs_phy->port_id == 1 && mxs_phy->regmap_anatop) {
+ udelay(25);
+ regmap_write(mxs_phy->regmap_anatop,
+ ANADIG_PLL_USB2_CLR,
+ BM_ANADIG_PLL_USB2_HOLD_RING_OFF);
+ udelay(25);
+ }
+
writel(BM_USBPHY_CTRL_CLKGATE,
x->io_priv + HW_USBPHY_CTRL_CLR);
writel(0, x->io_priv + HW_USBPHY_PWD);
@@ -702,6 +852,174 @@ static enum usb_charger_type mxs_phy_charger_detect(struct usb_phy *phy)
return chgr_type;
}
+static int mxs_phy_on_suspend(struct usb_phy *phy,
+ enum usb_device_speed speed)
+{
+ struct mxs_phy *mxs_phy = to_mxs_phy(phy);
+
+ dev_dbg(phy->dev, "%s device has suspended\n",
+ (speed == USB_SPEED_HIGH) ? "HS" : "FS/LS");
+
+ /* delay 4ms to wait bus entering idle */
+ usleep_range(4000, 5000);
+
+ if (mxs_phy->data->flags & MXS_PHY_ABNORMAL_IN_SUSPEND) {
+ writel_relaxed(0xffffffff, phy->io_priv + HW_USBPHY_PWD);
+ writel_relaxed(0, phy->io_priv + HW_USBPHY_PWD);
+ }
+
+ if (speed == USB_SPEED_HIGH)
+ writel_relaxed(BM_USBPHY_CTRL_ENHOSTDISCONDETECT,
+ phy->io_priv + HW_USBPHY_CTRL_CLR);
+
+ return 0;
+}
+
+/*
+ * The resume signal must be finished here.
+ */
+static int mxs_phy_on_resume(struct usb_phy *phy,
+ enum usb_device_speed speed)
+{
+ dev_dbg(phy->dev, "%s device has resumed\n",
+ (speed == USB_SPEED_HIGH) ? "HS" : "FS/LS");
+
+ if (speed == USB_SPEED_HIGH) {
+ /* Make sure the device has switched to High-Speed mode */
+ udelay(500);
+ writel_relaxed(BM_USBPHY_CTRL_ENHOSTDISCONDETECT,
+ phy->io_priv + HW_USBPHY_CTRL_SET);
+ }
+
+ return 0;
+}
+
+/*
+ * Set the usb current role for phy.
+ */
+static int mxs_phy_set_mode(struct usb_phy *phy,
+ enum usb_current_mode mode)
+{
+ struct mxs_phy *mxs_phy = to_mxs_phy(phy);
+
+ mxs_phy->mode = mode;
+
+ return 0;
+}
+
+static int mxs_phy_dcd_start(struct mxs_phy *mxs_phy)
+{
+ void __iomem *base = mxs_phy->phy.io_priv;
+ u32 value;
+
+ value = readl(base + DCD_CONTROL);
+ writel(value | DCD_CONTROL_SR, base + DCD_CONTROL);
+
+ if (!mxs_phy->clk_rate)
+ return -EINVAL;
+
+ value = readl(base + DCD_CONTROL);
+ writel(((mxs_phy->clk_rate / 1000000) << 2) | DCD_CLOCK_MHZ,
+ base + DCD_CLOCK);
+
+ value = readl(base + DCD_TIMER1);
+ value &= ~DCD_TVDPSRC_ON_MASK;
+ value |= DCD_TVDPSRC_ON_VALUE;
+ writel(value, base + DCD_TIMER1);
+
+ value = readl(base + DCD_CONTROL);
+ value &= ~DCD_CONTROL_IE;
+ writel(value | DCD_CONTROL_BC12, base + DCD_CONTROL);
+
+ value = readl(base + DCD_CONTROL);
+ writel(value | DCD_CONTROL_START, base + DCD_CONTROL);
+
+ return 0;
+}
+
+#define DCD_CHARGING_DURTION 1000 /* One second according to BC 1.2 */
+static enum usb_charger_type mxs_phy_dcd_flow(struct usb_phy *phy)
+{
+ struct mxs_phy *mxs_phy = to_mxs_phy(phy);
+ void __iomem *base = mxs_phy->phy.io_priv;
+ u32 value;
+ int i = 0;
+ enum usb_charger_type chgr_type;
+
+ if (mxs_phy_dcd_start(mxs_phy))
+ return UNKNOWN_TYPE;
+
+ while (i++ <= (DCD_CHARGING_DURTION / 50)) {
+ value = readl(base + DCD_CONTROL);
+ if (value & DCD_CONTROL_IF) {
+ value = readl(base + DCD_STATUS);
+ if (value & DCD_STATUS_ACTIVE) {
+ dev_err(phy->dev, "still detecting\n");
+ chgr_type = UNKNOWN_TYPE;
+ break;
+ }
+
+ if (value & DCD_STATUS_TO) {
+ dev_err(phy->dev, "detect timeout\n");
+ chgr_type = UNKNOWN_TYPE;
+ break;
+ }
+
+ if (value & DCD_STATUS_ERR) {
+ dev_err(phy->dev, "detect error\n");
+ chgr_type = UNKNOWN_TYPE;
+ break;
+ }
+
+ if ((value & DCD_STATUS_SEQ_STAT) <= DCD_CHG_DPIN) {
+ dev_err(phy->dev, "error occurs\n");
+ chgr_type = UNKNOWN_TYPE;
+ break;
+ }
+
+ /* SDP */
+ if (((value & DCD_STATUS_SEQ_STAT) == DCD_CHG_PORT) &&
+ ((value & DCD_STATUS_SEQ_RES)
+ == DCD_SDP_PORT)) {
+ dev_dbg(phy->dev, "SDP\n");
+ chgr_type = SDP_TYPE;
+ break;
+ }
+
+ if ((value & DCD_STATUS_SEQ_STAT) == DCD_CHG_DET) {
+ if ((value & DCD_STATUS_SEQ_RES) ==
+ DCD_CDP_PORT) {
+ dev_dbg(phy->dev, "CDP\n");
+ chgr_type = CDP_TYPE;
+ break;
+ }
+
+ if ((value & DCD_STATUS_SEQ_RES) ==
+ DCD_DCP_PORT) {
+ dev_dbg(phy->dev, "DCP\n");
+ chgr_type = DCP_TYPE;
+ break;
+ }
+ }
+ dev_err(phy->dev, "unknown error occurs\n");
+ chgr_type = UNKNOWN_TYPE;
+ break;
+ }
+ msleep(50);
+ }
+
+ if (i > 20) {
+ dev_err(phy->dev, "charger detecting timeout\n");
+ chgr_type = UNKNOWN_TYPE;
+ }
+
+ /* disable dcd module */
+ readl(base + DCD_STATUS);
+ writel(DCD_CONTROL_IACK, base + DCD_CONTROL);
+ writel(DCD_CONTROL_SR, base + DCD_CONTROL);
+ return chgr_type;
+}
+
static int mxs_phy_probe(struct platform_device *pdev)
{
void __iomem *base;
@@ -716,16 +1034,15 @@ static int mxs_phy_probe(struct platform_device *pdev)
return PTR_ERR(base);
clk = devm_clk_get(&pdev->dev, NULL);
- if (IS_ERR(clk)) {
- dev_err(&pdev->dev,
- "can't get the clock, err=%ld", PTR_ERR(clk));
- return PTR_ERR(clk);
- }
+ if (IS_ERR(clk))
+ return dev_err_probe(&pdev->dev, PTR_ERR(clk),
+ "failed to get clock\n");
mxs_phy = devm_kzalloc(&pdev->dev, sizeof(*mxs_phy), GFP_KERNEL);
if (!mxs_phy)
return -ENOMEM;
+ mxs_phy->clk_rate = clk_get_rate(clk);
/* Some SoCs don't have anatop registers */
if (of_get_property(np, "fsl,anatop", NULL)) {
mxs_phy->regmap_anatop = syscon_regmap_lookup_by_phandle
@@ -737,6 +1054,17 @@ static int mxs_phy_probe(struct platform_device *pdev)
}
}
+ /* Currently, only imx7ulp has SIM module */
+ if (of_get_property(np, "nxp,sim", NULL)) {
+ mxs_phy->regmap_sim = syscon_regmap_lookup_by_phandle
+ (np, "nxp,sim");
+ if (IS_ERR(mxs_phy->regmap_sim)) {
+ dev_dbg(&pdev->dev,
+ "failed to find regmap for sim\n");
+ return PTR_ERR(mxs_phy->regmap_sim);
+ }
+ }
+
/* Precompute which bits of the TX register are to be updated, if any */
if (!of_property_read_u32(np, "fsl,tx-cal-45-dn-ohms", &val) &&
val >= MXS_PHY_TX_CAL45_MIN && val <= MXS_PHY_TX_CAL45_MAX) {
@@ -783,15 +1111,42 @@ static int mxs_phy_probe(struct platform_device *pdev)
mxs_phy->phy.notify_disconnect = mxs_phy_on_disconnect;
mxs_phy->phy.type = USB_PHY_TYPE_USB2;
mxs_phy->phy.set_wakeup = mxs_phy_set_wakeup;
- mxs_phy->phy.charger_detect = mxs_phy_charger_detect;
-
mxs_phy->clk = clk;
mxs_phy->data = of_device_get_match_data(&pdev->dev);
+ mxs_phy->phy.set_mode = mxs_phy_set_mode;
+
+ if (mxs_phy->data->flags & MXS_PHY_HAS_DCD)
+ mxs_phy->phy.charger_detect = mxs_phy_dcd_flow;
+ else
+ mxs_phy->phy.charger_detect = mxs_phy_charger_detect;
+
+ if (mxs_phy->data->flags & MXS_PHY_SENDING_SOF_TOO_FAST) {
+ mxs_phy->phy.notify_suspend = mxs_phy_on_suspend;
+ mxs_phy->phy.notify_resume = mxs_phy_on_resume;
+ }
+
+ mxs_phy->phy_3p0 = devm_regulator_get(&pdev->dev, "phy-3p0");
+ if (PTR_ERR(mxs_phy->phy_3p0) == -ENODEV)
+ /* not exist */
+ mxs_phy->phy_3p0 = NULL;
+ else if (IS_ERR(mxs_phy->phy_3p0))
+ return dev_err_probe(&pdev->dev, PTR_ERR(mxs_phy->phy_3p0),
+ "Getting regulator error\n");
+
+ if (mxs_phy->phy_3p0)
+ regulator_set_voltage(mxs_phy->phy_3p0, 3200000, 3200000);
+
+ if (mxs_phy->data->flags & MXS_PHY_HARDWARE_CONTROL_PHY2_CLK)
+ mxs_phy->hardware_control_phy2_clk = true;
platform_set_drvdata(pdev, mxs_phy);
device_set_wakeup_capable(&pdev->dev, true);
+ pm_runtime_set_active(&pdev->dev);
+ pm_runtime_enable(&pdev->dev);
+ pm_runtime_get_noresume(&pdev->dev);
+
return usb_add_phy_dev(&mxs_phy->phy);
}
@@ -800,33 +1155,68 @@ static int mxs_phy_remove(struct platform_device *pdev)
struct mxs_phy *mxs_phy = platform_get_drvdata(pdev);
usb_remove_phy(&mxs_phy->phy);
+ pm_runtime_get_sync(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+ pm_runtime_put_noidle(&pdev->dev);
return 0;
}
+#ifdef CONFIG_PM
+
#ifdef CONFIG_PM_SLEEP
+static void mxs_phy_wakeup_enable(struct mxs_phy *mxs_phy, bool on)
+{
+ u32 mask = USB_PHY_VLLS_WAKEUP_EN;
+
+ /* If the SoCs don't have SIM, quit */
+ if (!mxs_phy->regmap_sim)
+ return;
+
+ if (on) {
+ regmap_update_bits(mxs_phy->regmap_sim, SIM_GPR1, mask, mask);
+ udelay(500);
+ } else {
+ regmap_update_bits(mxs_phy->regmap_sim, SIM_GPR1, mask, 0);
+ }
+}
+
static void mxs_phy_enable_ldo_in_suspend(struct mxs_phy *mxs_phy, bool on)
{
- unsigned int reg = on ? ANADIG_ANA_MISC0_SET : ANADIG_ANA_MISC0_CLR;
+ unsigned int reg;
+ u32 value;
/* If the SoCs don't have anatop, quit */
if (!mxs_phy->regmap_anatop)
return;
- if (is_imx6q_phy(mxs_phy))
+ if (is_imx6q_phy(mxs_phy)) {
+ reg = on ? ANADIG_ANA_MISC0_SET : ANADIG_ANA_MISC0_CLR;
regmap_write(mxs_phy->regmap_anatop, reg,
BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG);
- else if (is_imx6sl_phy(mxs_phy))
+ } else if (is_imx6sl_phy(mxs_phy)) {
+ reg = on ? ANADIG_ANA_MISC0_SET : ANADIG_ANA_MISC0_CLR;
regmap_write(mxs_phy->regmap_anatop,
reg, BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG_SL);
+ } else if (is_imx6ul_phy(mxs_phy)) {
+ reg = on ? ANADIG_REG_1P1_SET : ANADIG_REG_1P1_CLR;
+ value = BM_ANADIG_REG_1P1_ENABLE_WEAK_LINREG |
+ BM_ANADIG_REG_1P1_TRACK_VDD_SOC_CAP;
+ if (mxs_phy_get_vbus_status(mxs_phy) && on)
+ regmap_write(mxs_phy->regmap_anatop, reg, value);
+ else if (!on)
+ regmap_write(mxs_phy->regmap_anatop, reg, value);
+ }
}
static int mxs_phy_system_suspend(struct device *dev)
{
struct mxs_phy *mxs_phy = dev_get_drvdata(dev);
- if (device_may_wakeup(dev))
+ if (device_may_wakeup(dev)) {
mxs_phy_enable_ldo_in_suspend(mxs_phy, true);
+ mxs_phy_wakeup_enable(mxs_phy, true);
+ }
return 0;
}
@@ -835,15 +1225,34 @@ static int mxs_phy_system_resume(struct device *dev)
{
struct mxs_phy *mxs_phy = dev_get_drvdata(dev);
- if (device_may_wakeup(dev))
+ if (device_may_wakeup(dev)) {
mxs_phy_enable_ldo_in_suspend(mxs_phy, false);
+ mxs_phy_wakeup_enable(mxs_phy, false);
+ }
+
+ pm_runtime_disable(dev);
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
return 0;
}
#endif /* CONFIG_PM_SLEEP */
-static SIMPLE_DEV_PM_OPS(mxs_phy_pm, mxs_phy_system_suspend,
- mxs_phy_system_resume);
+static int mxs_phy_runtime_resume(struct device *dev)
+{
+ return 0;
+}
+
+static int mxs_phy_runtime_suspend(struct device *dev)
+{
+ return 0;
+}
+#endif /* CONFIG_PM */
+
+static const struct dev_pm_ops mxs_phy_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(mxs_phy_system_suspend, mxs_phy_system_resume)
+ SET_RUNTIME_PM_OPS(mxs_phy_runtime_suspend, mxs_phy_runtime_resume, NULL)
+};
static struct platform_driver mxs_phy_driver = {
.probe = mxs_phy_probe,
@@ -851,7 +1260,7 @@ static struct platform_driver mxs_phy_driver = {
.driver = {
.name = DRIVER_NAME,
.of_match_table = mxs_phy_dt_ids,
- .pm = &mxs_phy_pm,
+ .pm = &mxs_phy_pm_ops,
},
};
diff --git a/drivers/usb/typec/mux/Kconfig b/drivers/usb/typec/mux/Kconfig
index edead555835e..c66819e96046 100644
--- a/drivers/usb/typec/mux/Kconfig
+++ b/drivers/usb/typec/mux/Kconfig
@@ -19,4 +19,10 @@ config TYPEC_MUX_INTEL_PMC
control the USB role switch and also the multiplexer/demultiplexer
switches used with USB Type-C Alternate Modes.
+config TYPEC_SWITCH_GPIO
+ tristate "Simple Super Speed Active Switch via GPIO"
+ help
+ Say Y or M if your system has a typec super speed channel
+ switch via a simple GPIO control.
+
endmenu
diff --git a/drivers/usb/typec/mux/Makefile b/drivers/usb/typec/mux/Makefile
index 280a6f553115..319069d30410 100644
--- a/drivers/usb/typec/mux/Makefile
+++ b/drivers/usb/typec/mux/Makefile
@@ -2,3 +2,4 @@
obj-$(CONFIG_TYPEC_MUX_PI3USB30532) += pi3usb30532.o
obj-$(CONFIG_TYPEC_MUX_INTEL_PMC) += intel_pmc_mux.o
+obj-$(CONFIG_TYPEC_SWITCH_GPIO) += gpio-switch.o
diff --git a/drivers/usb/typec/mux/gpio-switch.c b/drivers/usb/typec/mux/gpio-switch.c
new file mode 100644
index 000000000000..6029e224a848
--- /dev/null
+++ b/drivers/usb/typec/mux/gpio-switch.c
@@ -0,0 +1,118 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ * gpio-switch.c - typec switch via a simple GPIO control.
+ *
+ * Copyright 2019 NXP
+ * Author: Jun Li <jun.li@nxp.com>
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/usb/typec_mux.h>
+
+struct gpio_typec_switch {
+ struct typec_switch *sw;
+ struct mutex lock;
+ struct gpio_desc *ss_sel;
+ struct gpio_desc *ss_reset;
+};
+
+static int switch_gpio_set(struct typec_switch *sw,
+ enum typec_orientation orientation)
+{
+ struct gpio_typec_switch *gpio_sw = typec_switch_get_drvdata(sw);
+
+ mutex_lock(&gpio_sw->lock);
+
+ switch (orientation) {
+ case TYPEC_ORIENTATION_NORMAL:
+ gpiod_set_value_cansleep(gpio_sw->ss_sel, 1);
+ break;
+ case TYPEC_ORIENTATION_REVERSE:
+ gpiod_set_value_cansleep(gpio_sw->ss_sel, 0);
+ break;
+ case TYPEC_ORIENTATION_NONE:
+ break;
+ }
+
+ mutex_unlock(&gpio_sw->lock);
+
+ return 0;
+}
+
+static int typec_switch_gpio_probe(struct platform_device *pdev)
+{
+ struct gpio_typec_switch *gpio_sw;
+ struct device *dev = &pdev->dev;
+ struct typec_switch_desc sw_desc;
+
+ gpio_sw = devm_kzalloc(dev, sizeof(*gpio_sw), GFP_KERNEL);
+ if (!gpio_sw)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, gpio_sw);
+
+ sw_desc.drvdata = gpio_sw;
+ sw_desc.fwnode = dev->fwnode;
+ sw_desc.set = switch_gpio_set;
+ sw_desc.name = NULL;
+ mutex_init(&gpio_sw->lock);
+
+ /* Get the super speed mux reset GPIO, it's optional */
+ gpio_sw->ss_reset = devm_gpiod_get_optional(dev, "reset",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(gpio_sw->ss_reset))
+ return PTR_ERR(gpio_sw->ss_reset);
+
+ if (gpio_sw->ss_reset)
+ usleep_range(700, 1000);
+
+ /* Get the super speed active channel selection GPIO */
+ gpio_sw->ss_sel = devm_gpiod_get(dev, "switch", GPIOD_OUT_LOW);
+ if (IS_ERR(gpio_sw->ss_sel))
+ return PTR_ERR(gpio_sw->ss_sel);
+
+ gpio_sw->sw = typec_switch_register(dev, &sw_desc);
+ if (IS_ERR(gpio_sw->sw)) {
+ dev_err(dev, "Error registering typec switch: %ld\n", PTR_ERR(gpio_sw->sw));
+ return PTR_ERR(gpio_sw->sw);
+ }
+
+ return 0;
+}
+
+static int typec_switch_gpio_remove(struct platform_device *pdev)
+{
+ struct gpio_typec_switch *gpio_sw = platform_get_drvdata(pdev);
+
+ typec_switch_unregister(gpio_sw->sw);
+
+ return 0;
+}
+
+static const struct of_device_id of_typec_switch_gpio_match[] = {
+ { .compatible = "nxp,ptn36043" },
+ { .compatible = "nxp,cbtl04gp" },
+ { /* Sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, of_typec_switch_gpio_match);
+
+static struct platform_driver typec_switch_gpio_driver = {
+ .probe = typec_switch_gpio_probe,
+ .remove = typec_switch_gpio_remove,
+ .driver = {
+ .name = "typec-switch-gpio",
+ .of_match_table = of_typec_switch_gpio_match,
+ },
+};
+
+module_platform_driver(typec_switch_gpio_driver);
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("TypeC Super Speed Switch GPIO driver");
+MODULE_AUTHOR("Jun Li <jun.li@nxp.com>");
diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c
index a7b0134d382b..a128df502d5f 100644
--- a/drivers/usb/typec/tcpm/tcpci.c
+++ b/drivers/usb/typec/tcpm/tcpci.c
@@ -10,6 +10,7 @@
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
+#include <linux/irq.h>
#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/usb/pd.h>
@@ -179,9 +180,6 @@ static int tcpci_start_toggling(struct tcpc_dev *tcpc,
struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
unsigned int reg = TCPC_ROLE_CTRL_DRP;
- if (port_type != TYPEC_PORT_DRP)
- return -EOPNOTSUPP;
-
/* Handle vendor drp toggling */
if (tcpci->data->start_drp_toggling) {
ret = tcpci->data->start_drp_toggling(tcpci, tcpci->data, cc);
@@ -493,6 +491,32 @@ static bool tcpci_is_vbus_vsafe0v(struct tcpc_dev *tcpc)
return !!(reg & TCPC_EXTENDED_STATUS_VSAFE0V);
}
+static int tcpci_vbus_force_discharge(struct tcpc_dev *tcpc, bool enable)
+{
+ struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
+ unsigned int reg;
+ int ret;
+
+ if (enable)
+ regmap_write(tcpci->regmap,
+ TCPC_VBUS_VOLTAGE_ALARM_LO_CFG, 0x1c);
+ else
+ regmap_write(tcpci->regmap,
+ TCPC_VBUS_VOLTAGE_ALARM_LO_CFG, 0);
+
+ regmap_read(tcpci->regmap, TCPC_POWER_CTRL, &reg);
+
+ if (enable)
+ reg |= TCPC_POWER_CTRL_FORCEDISCH;
+ else
+ reg &= ~TCPC_POWER_CTRL_FORCEDISCH;
+ ret = regmap_write(tcpci->regmap, TCPC_POWER_CTRL, reg);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
static int tcpci_set_vbus(struct tcpc_dev *tcpc, bool source, bool sink)
{
struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
@@ -521,6 +545,9 @@ static int tcpci_set_vbus(struct tcpc_dev *tcpc, bool source, bool sink)
return ret;
}
+ if (!source && !sink)
+ tcpci_vbus_force_discharge(tcpc, true);
+
if (source) {
ret = regmap_write(tcpci->regmap, TCPC_COMMAND,
TCPC_CMD_SRC_VBUS_DEFAULT);
@@ -631,6 +658,9 @@ static int tcpci_init(struct tcpc_dev *tcpc)
if (ret < 0)
return ret;
+ /* Clear fault condition */
+ regmap_write(tcpci->regmap, TCPC_FAULT_STATUS, 0x80);
+
if (tcpci->controls_vbus)
reg = TCPC_POWER_STATUS_VBUS_PRES;
else
@@ -647,7 +677,8 @@ static int tcpci_init(struct tcpc_dev *tcpc)
reg = TCPC_ALERT_TX_SUCCESS | TCPC_ALERT_TX_FAILED |
TCPC_ALERT_TX_DISCARDED | TCPC_ALERT_RX_STATUS |
- TCPC_ALERT_RX_HARD_RST | TCPC_ALERT_CC_STATUS;
+ TCPC_ALERT_RX_HARD_RST | TCPC_ALERT_CC_STATUS |
+ TCPC_ALERT_V_ALARM_LO | TCPC_ALERT_FAULT;
if (tcpci->controls_vbus)
reg |= TCPC_ALERT_POWER_STATUS;
/* Enable VSAFE0V status interrupt when detecting VSAFE0V is supported */
@@ -681,7 +712,10 @@ irqreturn_t tcpci_irq(struct tcpci *tcpci)
tcpm_cc_change(tcpci->port);
if (status & TCPC_ALERT_POWER_STATUS) {
+ /* Read power status to clear the event */
+ regmap_read(tcpci->regmap, TCPC_POWER_STATUS, &raw);
regmap_read(tcpci->regmap, TCPC_POWER_STATUS_MASK, &raw);
+
/*
* If power status mask has been reset, then the TCPC
* has reset.
@@ -692,6 +726,9 @@ irqreturn_t tcpci_irq(struct tcpci *tcpci)
tcpm_vbus_change(tcpci->port);
}
+ if (status & TCPC_ALERT_V_ALARM_LO)
+ tcpci_vbus_force_discharge(&tcpci->tcpc, false);
+
if (status & TCPC_ALERT_RX_STATUS) {
struct pd_message msg;
unsigned int cnt, payload_cnt;
@@ -731,6 +768,13 @@ irqreturn_t tcpci_irq(struct tcpci *tcpci)
tcpm_vbus_change(tcpci->port);
}
+ /* Clear the fault status anyway */
+ if (status & TCPC_ALERT_FAULT) {
+ regmap_read(tcpci->regmap, TCPC_FAULT_STATUS, &raw);
+ regmap_write(tcpci->regmap, TCPC_FAULT_STATUS,
+ raw | TCPC_FAULT_STATUS_CLEAR);
+ }
+
if (status & TCPC_ALERT_RX_HARD_RST)
tcpm_pd_hard_reset(tcpci->port);
@@ -863,15 +907,18 @@ static int tcpci_probe(struct i2c_client *client,
if (IS_ERR(chip->tcpci))
return PTR_ERR(chip->tcpci);
+ irq_set_status_flags(client->irq, IRQ_DISABLE_UNLAZY);
err = devm_request_threaded_irq(&client->dev, client->irq, NULL,
_tcpci_irq,
- IRQF_ONESHOT | IRQF_TRIGGER_LOW,
+ IRQF_SHARED | IRQF_ONESHOT | IRQF_TRIGGER_LOW,
dev_name(&client->dev), chip);
if (err < 0) {
tcpci_unregister_port(chip->tcpci);
return err;
}
+ device_set_wakeup_capable(chip->tcpci->dev, true);
+
return 0;
}
@@ -886,10 +933,40 @@ static int tcpci_remove(struct i2c_client *client)
dev_warn(&client->dev, "Failed to disable irqs (%pe)\n", ERR_PTR(err));
tcpci_unregister_port(chip->tcpci);
+ irq_clear_status_flags(client->irq, IRQ_DISABLE_UNLAZY);
+
+ return 0;
+}
+
+static int __maybe_unused tcpci_suspend(struct device *dev)
+{
+ struct i2c_client *i2c = to_i2c_client(dev);
+
+ if (device_may_wakeup(dev))
+ enable_irq_wake(i2c->irq);
+ else
+ disable_irq(i2c->irq);
return 0;
}
+
+static int __maybe_unused tcpci_resume(struct device *dev)
+{
+ struct i2c_client *i2c = to_i2c_client(dev);
+
+ if (device_may_wakeup(dev))
+ disable_irq_wake(i2c->irq);
+ else
+ enable_irq(i2c->irq);
+
+ return 0;
+}
+
+static const struct dev_pm_ops tcpci_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(tcpci_suspend, tcpci_resume)
+};
+
static const struct i2c_device_id tcpci_id[] = {
{ "tcpci", 0 },
{ }
@@ -907,6 +984,7 @@ MODULE_DEVICE_TABLE(of, tcpci_of_match);
static struct i2c_driver tcpci_i2c_driver = {
.driver = {
.name = "tcpci",
+ .pm = &tcpci_pm_ops,
.of_match_table = of_match_ptr(tcpci_of_match),
},
.probe = tcpci_probe,
diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c
index 359c9bd7848f..12697238a553 100644
--- a/drivers/usb/typec/tcpm/tcpm.c
+++ b/drivers/usb/typec/tcpm/tcpm.c
@@ -3146,20 +3146,11 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo,
continue;
}
- switch (type) {
- case PDO_TYPE_FIXED:
- case PDO_TYPE_VAR:
+ if (type == PDO_TYPE_FIXED || type == PDO_TYPE_VAR) {
src_ma = pdo_max_current(pdo);
src_mw = src_ma * min_src_mv / 1000;
- break;
- case PDO_TYPE_BATT:
+ } else if (type == PDO_TYPE_BATT) {
src_mw = pdo_max_power(pdo);
- break;
- case PDO_TYPE_APDO:
- continue;
- default:
- tcpm_log(port, "Invalid source PDO type, ignoring");
- continue;
}
for (j = 0; j < port->nr_snk_pdo; j++) {
@@ -3603,6 +3594,8 @@ static int tcpm_src_attach(struct tcpm_port *port)
if (port->attached)
return 0;
+ tcpm_set_cc(port, tcpm_rp_cc(port));
+
ret = tcpm_set_polarity(port, polarity);
if (ret < 0)
return ret;
@@ -3750,6 +3743,8 @@ static int tcpm_snk_attach(struct tcpm_port *port)
if (port->attached)
return 0;
+ tcpm_set_cc(port, TYPEC_CC_RD);
+
ret = tcpm_set_polarity(port, port->cc2 != TYPEC_CC_OPEN ?
TYPEC_POLARITY_CC2 : TYPEC_POLARITY_CC1);
if (ret < 0)
@@ -4220,7 +4215,11 @@ static void run_state_machine(struct tcpm_port *port)
ret = tcpm_snk_attach(port);
if (ret < 0)
tcpm_set_state(port, SNK_UNATTACHED, 0);
- else
+ else if (port->port_type == TYPEC_PORT_SRC &&
+ port->typec_caps.data == TYPEC_PORT_DRD) {
+ tcpm_typec_connect(port);
+ tcpm_log(port, "Keep at SNK_ATTACHED for USB data.");
+ } else
tcpm_set_state(port, SNK_STARTUP, 0);
break;
case SNK_STARTUP:
@@ -6144,7 +6143,7 @@ static enum power_supply_property tcpm_psy_props[] = {
static int tcpm_psy_get_online(struct tcpm_port *port,
union power_supply_propval *val)
{
- if (port->vbus_charge) {
+ if (port->vbus_present && tcpm_port_is_sink(port)) {
if (port->pps_data.active)
val->intval = TCPM_PSY_PROG_ONLINE;
else
@@ -6267,7 +6266,7 @@ static int tcpm_psy_set_prop(struct power_supply *psy,
const union power_supply_propval *val)
{
struct tcpm_port *port = power_supply_get_drvdata(psy);
- int ret;
+ int ret = 0;
/*
* All the properties below are related to USB PD. The check needs to be
@@ -6293,6 +6292,9 @@ static int tcpm_psy_set_prop(struct power_supply *psy,
else
ret = tcpm_pps_set_op_curr(port, val->intval / 1000);
break;
+ case POWER_SUPPLY_PROP_USB_TYPE:
+ port->usb_type = val->intval;
+ break;
default:
ret = -EINVAL;
break;
@@ -6315,6 +6317,10 @@ static int tcpm_psy_prop_writeable(struct power_supply *psy,
}
static enum power_supply_usb_type tcpm_psy_usb_types[] = {
+ POWER_SUPPLY_USB_TYPE_SDP,
+ POWER_SUPPLY_USB_TYPE_DCP,
+ POWER_SUPPLY_USB_TYPE_CDP,
+ POWER_SUPPLY_USB_TYPE_ACA,
POWER_SUPPLY_USB_TYPE_C,
POWER_SUPPLY_USB_TYPE_PD,
POWER_SUPPLY_USB_TYPE_PD_PPS,