diff options
Diffstat (limited to 'drivers/usb')
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, ®); + + 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, |