diff options
Diffstat (limited to 'drivers/usb/chipidea')
-rw-r--r-- | drivers/usb/chipidea/Kconfig | 11 | ||||
-rw-r--r-- | drivers/usb/chipidea/Makefile | 9 | ||||
-rw-r--r-- | drivers/usb/chipidea/bits.h | 11 | ||||
-rw-r--r-- | drivers/usb/chipidea/ci.h | 69 | ||||
-rw-r--r-- | drivers/usb/chipidea/ci_hdrc_imx.c | 153 | ||||
-rw-r--r-- | drivers/usb/chipidea/ci_hdrc_imx.h | 1 | ||||
-rw-r--r-- | drivers/usb/chipidea/ci_hdrc_pci.c | 31 | ||||
-rw-r--r-- | drivers/usb/chipidea/ci_hdrc_zevio.c | 2 | ||||
-rw-r--r-- | drivers/usb/chipidea/core.c | 185 | ||||
-rw-r--r-- | drivers/usb/chipidea/debug.c | 4 | ||||
-rw-r--r-- | drivers/usb/chipidea/host.c | 74 | ||||
-rw-r--r-- | drivers/usb/chipidea/otg.c | 2 | ||||
-rw-r--r-- | drivers/usb/chipidea/otg_fsm.c | 389 | ||||
-rw-r--r-- | drivers/usb/chipidea/otg_fsm.h | 27 | ||||
-rw-r--r-- | drivers/usb/chipidea/udc.c | 41 | ||||
-rw-r--r-- | drivers/usb/chipidea/usbmisc_imx.c | 129 |
16 files changed, 816 insertions, 322 deletions
diff --git a/drivers/usb/chipidea/Kconfig b/drivers/usb/chipidea/Kconfig index 77b47d82c9a6..5ce3f1d6a6ed 100644 --- a/drivers/usb/chipidea/Kconfig +++ b/drivers/usb/chipidea/Kconfig @@ -10,6 +10,17 @@ config USB_CHIPIDEA if USB_CHIPIDEA +config USB_CHIPIDEA_OF + tristate + depends on OF + default USB_CHIPIDEA + +config USB_CHIPIDEA_PCI + tristate + depends on PCI + depends on NOP_USB_XCEIV + default USB_CHIPIDEA + config USB_CHIPIDEA_UDC bool "ChipIdea device controller" depends on USB_GADGET diff --git a/drivers/usb/chipidea/Makefile b/drivers/usb/chipidea/Makefile index 1fc86a2ca22d..4decb12f2578 100644 --- a/drivers/usb/chipidea/Makefile +++ b/drivers/usb/chipidea/Makefile @@ -14,11 +14,6 @@ obj-$(CONFIG_USB_CHIPIDEA) += ci_hdrc_usb2.o obj-$(CONFIG_USB_CHIPIDEA) += ci_hdrc_msm.o obj-$(CONFIG_USB_CHIPIDEA) += ci_hdrc_zevio.o -# PCI doesn't provide stubs, need to check -ifneq ($(CONFIG_PCI),) - obj-$(CONFIG_USB_CHIPIDEA) += ci_hdrc_pci.o -endif +obj-$(CONFIG_USB_CHIPIDEA_PCI) += ci_hdrc_pci.o -ifneq ($(CONFIG_OF),) - obj-$(CONFIG_USB_CHIPIDEA) += usbmisc_imx.o ci_hdrc_imx.o -endif +obj-$(CONFIG_USB_CHIPIDEA_OF) += usbmisc_imx.o ci_hdrc_imx.o diff --git a/drivers/usb/chipidea/bits.h b/drivers/usb/chipidea/bits.h index ca57e3dcd3d5..3cb9bda51ddf 100644 --- a/drivers/usb/chipidea/bits.h +++ b/drivers/usb/chipidea/bits.h @@ -15,6 +15,16 @@ #include <linux/usb/ehci_def.h> +/* + * ID + * For 1.x revision, bit24 - bit31 are reserved + * For 2.x revision, bit25 - bit28 are 0x2 + */ +#define TAG (0x1F << 16) +#define REVISION (0xF << 21) +#define VERSION (0xF << 25) +#define CIVERSION (0x7 << 29) + /* HCCPARAMS */ #define HCCPARAMS_LEN BIT(17) @@ -53,6 +63,7 @@ #define PORTSC_HSP BIT(9) #define PORTSC_PP BIT(12) #define PORTSC_PTC (0x0FUL << 16) +#define PORTSC_WKCN BIT(20) #define PORTSC_PHCD(d) ((d) ? BIT(22) : BIT(23)) /* PTS and PTW for non lpm version only */ #define PORTSC_PFSC BIT(24) diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h index 65913d48f0c8..6d6200e37b71 100644 --- a/drivers/usb/chipidea/ci.h +++ b/drivers/usb/chipidea/ci.h @@ -29,6 +29,15 @@ /****************************************************************************** * REGISTERS *****************************************************************************/ +/* Identification Registers */ +#define ID_ID 0x0 +#define ID_HWGENERAL 0x4 +#define ID_HWHOST 0x8 +#define ID_HWDEVICE 0xc +#define ID_HWTXBUF 0x10 +#define ID_HWRXBUF 0x14 +#define ID_SBUSCFG 0x90 + /* register indices */ enum ci_hw_regs { CAP_CAPLENGTH, @@ -97,6 +106,18 @@ enum ci_role { CI_ROLE_END, }; +enum ci_revision { + CI_REVISION_1X = 10, /* Revision 1.x */ + CI_REVISION_20 = 20, /* Revision 2.0 */ + CI_REVISION_21, /* Revision 2.1 */ + CI_REVISION_22, /* Revision 2.2 */ + CI_REVISION_23, /* Revision 2.3 */ + CI_REVISION_24, /* Revision 2.4 */ + CI_REVISION_25, /* Revision 2.5 */ + CI_REVISION_25_PLUS, /* Revision above than 2.5 */ + CI_REVISION_UNKNOWN = 99, /* Unknown Revision */ +}; + /** * struct ci_role_driver - host/gadget role driver * @start: start this role @@ -141,7 +162,10 @@ struct hw_bank { * @role: current role * @is_otg: if the device is otg-capable * @fsm: otg finite state machine - * @fsm_timer: pointer to timer list of otg fsm + * @otg_fsm_hrtimer: hrtimer for otg fsm timers + * @hr_timeouts: time out list for active otg fsm timers + * @enabled_otg_timer_bits: bits of enabled otg timers + * @next_otg_timer: next nearest enabled timer to be expired * @work: work for role changing * @wq: workqueue thread * @qh_pool: allocation pool for queue heads @@ -169,6 +193,10 @@ struct hw_bank { * @b_sess_valid_event: indicates there is a vbus event, and handled * at ci_otg_work * @imx28_write_fix: Freescale imx28 needs swp instruction for writing + * @supports_runtime_pm: if runtime pm is supported + * @in_lpm: if the core in low power mode + * @wakeup_int: if wakeup interrupt occur + * @rev: The revision number for controller */ struct ci_hdrc { struct device *dev; @@ -180,7 +208,10 @@ struct ci_hdrc { bool is_otg; struct usb_otg otg; struct otg_fsm fsm; - struct ci_otg_fsm_timer_list *fsm_timer; + struct hrtimer otg_fsm_hrtimer; + ktime_t hr_timeouts[NUM_OTG_FSM_TIMERS]; + unsigned enabled_otg_timer_bits; + enum otg_fsm_timer next_otg_timer; struct work_struct work; struct workqueue_struct *wq; @@ -211,6 +242,10 @@ struct ci_hdrc { bool id_event; bool b_sess_valid_event; bool imx28_write_fix; + bool supports_runtime_pm; + bool in_lpm; + bool wakeup_int; + enum ci_revision rev; }; static inline struct ci_role_driver *ci_role(struct ci_hdrc *ci) @@ -248,6 +283,36 @@ static inline void ci_role_stop(struct ci_hdrc *ci) } /** + * hw_read_id_reg: reads from a identification register + * @ci: the controller + * @offset: offset from the beginning of identification registers region + * @mask: bitfield mask + * + * This function returns register contents + */ +static inline u32 hw_read_id_reg(struct ci_hdrc *ci, u32 offset, u32 mask) +{ + return ioread32(ci->hw_bank.abs + offset) & mask; +} + +/** + * hw_write_id_reg: writes to a identification register + * @ci: the controller + * @offset: offset from the beginning of identification registers region + * @mask: bitfield mask + * @data: new value + */ +static inline void hw_write_id_reg(struct ci_hdrc *ci, u32 offset, + u32 mask, u32 data) +{ + if (~mask) + data = (ioread32(ci->hw_bank.abs + offset) & ~mask) + | (data & mask); + + iowrite32(data, ci->hw_bank.abs + offset); +} + +/** * hw_read: reads from a hw register * @ci: the controller * @reg: register index diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c index 0f05de7c6b6c..389f0e034259 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.c +++ b/drivers/usb/chipidea/ci_hdrc_imx.c @@ -23,22 +23,40 @@ #include "ci.h" #include "ci_hdrc_imx.h" -#define CI_HDRC_IMX_IMX28_WRITE_FIX BIT(0) - struct ci_hdrc_imx_platform_flag { unsigned int flags; + bool runtime_pm; }; static const struct ci_hdrc_imx_platform_flag imx27_usb_data = { }; static const struct ci_hdrc_imx_platform_flag imx28_usb_data = { - .flags = CI_HDRC_IMX_IMX28_WRITE_FIX, + .flags = CI_HDRC_IMX28_WRITE_FIX | + CI_HDRC_TURN_VBUS_EARLY_ON, +}; + +static const struct ci_hdrc_imx_platform_flag imx6q_usb_data = { + .flags = CI_HDRC_SUPPORTS_RUNTIME_PM | + CI_HDRC_TURN_VBUS_EARLY_ON, +}; + +static const struct ci_hdrc_imx_platform_flag imx6sl_usb_data = { + .flags = CI_HDRC_SUPPORTS_RUNTIME_PM | + CI_HDRC_TURN_VBUS_EARLY_ON, +}; + +static const struct ci_hdrc_imx_platform_flag imx6sx_usb_data = { + .flags = CI_HDRC_SUPPORTS_RUNTIME_PM | + CI_HDRC_TURN_VBUS_EARLY_ON, }; static const struct of_device_id ci_hdrc_imx_dt_ids[] = { { .compatible = "fsl,imx28-usb", .data = &imx28_usb_data}, { .compatible = "fsl,imx27-usb", .data = &imx27_usb_data}, + { .compatible = "fsl,imx6q-usb", .data = &imx6q_usb_data}, + { .compatible = "fsl,imx6sl-usb", .data = &imx6sl_usb_data}, + { .compatible = "fsl,imx6sx-usb", .data = &imx6sl_usb_data}, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, ci_hdrc_imx_dt_ids); @@ -48,6 +66,8 @@ struct ci_hdrc_imx_data { struct platform_device *ci_pdev; struct clk *clk; struct imx_usbmisc_data *usbmisc_data; + bool supports_runtime_pm; + bool in_lpm; }; /* Common functions shared by usbmisc drivers */ @@ -145,21 +165,18 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev) } pdata.usb_phy = data->phy; - - if (imx_platform_flag->flags & CI_HDRC_IMX_IMX28_WRITE_FIX) - pdata.flags |= CI_HDRC_IMX28_WRITE_FIX; + pdata.flags |= imx_platform_flag->flags; + if (pdata.flags & CI_HDRC_SUPPORTS_RUNTIME_PM) + data->supports_runtime_pm = true; ret = dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); if (ret) goto err_clk; - if (data->usbmisc_data) { - ret = imx_usbmisc_init(data->usbmisc_data); - if (ret) { - dev_err(&pdev->dev, "usbmisc init failed, ret=%d\n", - ret); - goto err_clk; - } + ret = imx_usbmisc_init(data->usbmisc_data); + if (ret) { + dev_err(&pdev->dev, "usbmisc init failed, ret=%d\n", ret); + goto err_clk; } data->ci_pdev = ci_hdrc_add_device(&pdev->dev, @@ -173,19 +190,20 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev) goto err_clk; } - if (data->usbmisc_data) { - ret = imx_usbmisc_init_post(data->usbmisc_data); - if (ret) { - dev_err(&pdev->dev, "usbmisc post failed, ret=%d\n", - ret); - goto disable_device; - } + ret = imx_usbmisc_init_post(data->usbmisc_data); + if (ret) { + dev_err(&pdev->dev, "usbmisc post failed, ret=%d\n", ret); + goto disable_device; } platform_set_drvdata(pdev, data); - pm_runtime_no_callbacks(&pdev->dev); - pm_runtime_enable(&pdev->dev); + if (data->supports_runtime_pm) { + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + } + + device_set_wakeup_capable(&pdev->dev, true); return 0; @@ -200,14 +218,18 @@ static int ci_hdrc_imx_remove(struct platform_device *pdev) { struct ci_hdrc_imx_data *data = platform_get_drvdata(pdev); - pm_runtime_disable(&pdev->dev); + if (data->supports_runtime_pm) { + pm_runtime_get_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + pm_runtime_put_noidle(&pdev->dev); + } ci_hdrc_remove_device(data->ci_pdev); clk_disable_unprepare(data->clk); return 0; } -#ifdef CONFIG_PM_SLEEP +#ifdef CONFIG_PM static int imx_controller_suspend(struct device *dev) { struct ci_hdrc_imx_data *data = dev_get_drvdata(dev); @@ -215,6 +237,7 @@ static int imx_controller_suspend(struct device *dev) dev_dbg(dev, "at %s\n", __func__); clk_disable_unprepare(data->clk); + data->in_lpm = true; return 0; } @@ -222,25 +245,103 @@ static int imx_controller_suspend(struct device *dev) static int imx_controller_resume(struct device *dev) { struct ci_hdrc_imx_data *data = dev_get_drvdata(dev); + int ret = 0; dev_dbg(dev, "at %s\n", __func__); - return clk_prepare_enable(data->clk); + if (!data->in_lpm) { + WARN_ON(1); + return 0; + } + + ret = clk_prepare_enable(data->clk); + if (ret) + return ret; + + data->in_lpm = false; + + ret = imx_usbmisc_set_wakeup(data->usbmisc_data, false); + if (ret) { + dev_err(dev, "usbmisc set_wakeup failed, ret=%d\n", ret); + goto clk_disable; + } + + return 0; + +clk_disable: + clk_disable_unprepare(data->clk); + return ret; } +#ifdef CONFIG_PM_SLEEP static int ci_hdrc_imx_suspend(struct device *dev) { + int ret; + + struct ci_hdrc_imx_data *data = dev_get_drvdata(dev); + + if (data->in_lpm) + /* 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; + } + } + return imx_controller_suspend(dev); } static int ci_hdrc_imx_resume(struct device *dev) { - return imx_controller_resume(dev); + struct ci_hdrc_imx_data *data = dev_get_drvdata(dev); + int ret; + + ret = imx_controller_resume(dev); + if (!ret && data->supports_runtime_pm) { + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + } + + return ret; } #endif /* CONFIG_PM_SLEEP */ +static int 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); + 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); +} + +static int ci_hdrc_imx_runtime_resume(struct device *dev) +{ + return imx_controller_resume(dev); +} + +#endif /* CONFIG_PM */ + static const struct dev_pm_ops ci_hdrc_imx_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(ci_hdrc_imx_suspend, ci_hdrc_imx_resume) + SET_RUNTIME_PM_OPS(ci_hdrc_imx_runtime_suspend, + ci_hdrc_imx_runtime_resume, NULL) }; static struct platform_driver ci_hdrc_imx_driver = { .probe = ci_hdrc_imx_probe, diff --git a/drivers/usb/chipidea/ci_hdrc_imx.h b/drivers/usb/chipidea/ci_hdrc_imx.h index 4ed828f75a1e..635717e9354a 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.h +++ b/drivers/usb/chipidea/ci_hdrc_imx.h @@ -22,5 +22,6 @@ struct imx_usbmisc_data { int imx_usbmisc_init(struct imx_usbmisc_data *); int imx_usbmisc_init_post(struct imx_usbmisc_data *); +int imx_usbmisc_set_wakeup(struct imx_usbmisc_data *, bool); #endif /* __DRIVER_USB_CHIPIDEA_CI_HDRC_IMX_H */ diff --git a/drivers/usb/chipidea/ci_hdrc_pci.c b/drivers/usb/chipidea/ci_hdrc_pci.c index 4df669437211..773d150512fa 100644 --- a/drivers/usb/chipidea/ci_hdrc_pci.c +++ b/drivers/usb/chipidea/ci_hdrc_pci.c @@ -16,10 +16,16 @@ #include <linux/interrupt.h> #include <linux/usb/gadget.h> #include <linux/usb/chipidea.h> +#include <linux/usb/usb_phy_generic.h> /* driver name */ #define UDC_DRIVER_NAME "ci_hdrc_pci" +struct ci_hdrc_pci { + struct platform_device *ci; + struct platform_device *phy; +}; + /****************************************************************************** * PCI block *****************************************************************************/ @@ -52,7 +58,7 @@ static int ci_hdrc_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) { struct ci_hdrc_platform_data *platdata = (void *)id->driver_data; - struct platform_device *plat_ci; + struct ci_hdrc_pci *ci; struct resource res[3]; int retval = 0, nres = 2; @@ -61,6 +67,10 @@ static int ci_hdrc_pci_probe(struct pci_dev *pdev, return -ENODEV; } + ci = devm_kzalloc(&pdev->dev, sizeof(*ci), GFP_KERNEL); + if (!ci) + return -ENOMEM; + retval = pcim_enable_device(pdev); if (retval) return retval; @@ -73,6 +83,11 @@ static int ci_hdrc_pci_probe(struct pci_dev *pdev, pci_set_master(pdev); pci_try_set_mwi(pdev); + /* register a nop PHY */ + ci->phy = usb_phy_generic_register(); + if (!ci->phy) + return -ENOMEM; + memset(res, 0, sizeof(res)); res[0].start = pci_resource_start(pdev, 0); res[0].end = pci_resource_end(pdev, 0); @@ -80,13 +95,14 @@ static int ci_hdrc_pci_probe(struct pci_dev *pdev, res[1].start = pdev->irq; res[1].flags = IORESOURCE_IRQ; - plat_ci = ci_hdrc_add_device(&pdev->dev, res, nres, platdata); - if (IS_ERR(plat_ci)) { + ci->ci = ci_hdrc_add_device(&pdev->dev, res, nres, platdata); + if (IS_ERR(ci->ci)) { dev_err(&pdev->dev, "ci_hdrc_add_device failed!\n"); - return PTR_ERR(plat_ci); + usb_phy_generic_unregister(ci->phy); + return PTR_ERR(ci->ci); } - pci_set_drvdata(pdev, plat_ci); + pci_set_drvdata(pdev, ci); return 0; } @@ -101,9 +117,10 @@ static int ci_hdrc_pci_probe(struct pci_dev *pdev, */ static void ci_hdrc_pci_remove(struct pci_dev *pdev) { - struct platform_device *plat_ci = pci_get_drvdata(pdev); + struct ci_hdrc_pci *ci = pci_get_drvdata(pdev); - ci_hdrc_remove_device(plat_ci); + ci_hdrc_remove_device(ci->ci); + usb_phy_generic_unregister(ci->phy); } /** diff --git a/drivers/usb/chipidea/ci_hdrc_zevio.c b/drivers/usb/chipidea/ci_hdrc_zevio.c index d976fc1db73a..1264de505527 100644 --- a/drivers/usb/chipidea/ci_hdrc_zevio.c +++ b/drivers/usb/chipidea/ci_hdrc_zevio.c @@ -18,7 +18,7 @@ static struct ci_hdrc_platform_data ci_hdrc_zevio_platdata = { .name = "ci_hdrc_zevio", - .flags = CI_HDRC_REGS_SHARED, + .flags = CI_HDRC_REGS_SHARED | CI_HDRC_FORCE_FULLSPEED, .capoffset = DEF_CAPOFFSET, }; diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index a57dc8866fc5..74fea4fa41b1 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -137,6 +137,22 @@ static int hw_alloc_regmap(struct ci_hdrc *ci, bool is_lpm) return 0; } +static enum ci_revision ci_get_revision(struct ci_hdrc *ci) +{ + int ver = hw_read_id_reg(ci, ID_ID, VERSION) >> __ffs(VERSION); + enum ci_revision rev = CI_REVISION_UNKNOWN; + + if (ver == 0x2) { + rev = hw_read_id_reg(ci, ID_ID, REVISION) + >> __ffs(REVISION); + rev += CI_REVISION_20; + } else if (ver == 0x0) { + rev = CI_REVISION_1X; + } + + return rev; +} + /** * hw_read_intr_enable: returns interrupt enable register * @@ -251,8 +267,11 @@ static int hw_device_init(struct ci_hdrc *ci, void __iomem *base) /* Clear all interrupts status bits*/ hw_write(ci, OP_USBSTS, 0xffffffff, 0xffffffff); - dev_dbg(ci->dev, "ChipIdea HDRC found, lpm: %d; cap: %p op: %p\n", - ci->hw_bank.lpm, ci->hw_bank.cap, ci->hw_bank.op); + ci->rev = ci_get_revision(ci); + + dev_dbg(ci->dev, + "ChipIdea HDRC found, revision: %d, lpm: %d; cap: %p op: %p\n", + ci->rev, ci->hw_bank.lpm, ci->hw_bank.cap, ci->hw_bank.op); /* setup lock mode ? */ @@ -491,6 +510,13 @@ static irqreturn_t ci_irq(int irq, void *data) irqreturn_t ret = IRQ_NONE; u32 otgsc = 0; + if (ci->in_lpm) { + disable_irq_nosync(irq); + ci->wakeup_int = true; + pm_runtime_get(ci->dev); + return IRQ_HANDLED; + } + if (ci->is_otg) { otgsc = hw_read_otgsc(ci, ~0); if (ci_otg_is_fsm_mode(ci)) { @@ -642,8 +668,12 @@ static void ci_get_otg_capable(struct ci_hdrc *ci) ci->is_otg = (hw_read(ci, CAP_DCCPARAMS, DCCPARAMS_DC | DCCPARAMS_HC) == (DCCPARAMS_DC | DCCPARAMS_HC)); - if (ci->is_otg) + if (ci->is_otg) { dev_dbg(ci->dev, "It is OTG capable controller\n"); + /* Disable and clear all OTG irq */ + hw_write_otgsc(ci, OTGSC_INT_EN_BITS | OTGSC_INT_STATUS_BITS, + OTGSC_INT_STATUS_BITS); + } } static int ci_hdrc_probe(struct platform_device *pdev) @@ -673,6 +703,8 @@ static int ci_hdrc_probe(struct platform_device *pdev) ci->platdata = dev_get_platdata(dev); ci->imx28_write_fix = !!(ci->platdata->flags & CI_HDRC_IMX28_WRITE_FIX); + ci->supports_runtime_pm = !!(ci->platdata->flags & + CI_HDRC_SUPPORTS_RUNTIME_PM); ret = hw_device_init(ci, base); if (ret < 0) { @@ -740,9 +772,6 @@ static int ci_hdrc_probe(struct platform_device *pdev) } if (ci->is_otg && ci->roles[CI_ROLE_GADGET]) { - /* Disable and clear all OTG irq */ - hw_write_otgsc(ci, OTGSC_INT_EN_BITS | OTGSC_INT_STATUS_BITS, - OTGSC_INT_STATUS_BITS); ret = ci_hdrc_otg_init(ci); if (ret) { dev_err(dev, "init otg fails, ret = %d\n", ret); @@ -769,11 +798,11 @@ static int ci_hdrc_probe(struct platform_device *pdev) : CI_ROLE_GADGET; } - /* only update vbus status for peripheral */ - if (ci->role == CI_ROLE_GADGET) - ci_handle_vbus_change(ci); - if (!ci_otg_is_fsm_mode(ci)) { + /* only update vbus status for peripheral */ + if (ci->role == CI_ROLE_GADGET) + ci_handle_vbus_change(ci); + ret = ci_role_start(ci, ci->role); if (ret) { dev_err(dev, "can't start %s role\n", @@ -788,9 +817,19 @@ static int ci_hdrc_probe(struct platform_device *pdev) if (ret) goto stop; + if (ci->supports_runtime_pm) { + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + pm_runtime_set_autosuspend_delay(&pdev->dev, 2000); + pm_runtime_mark_last_busy(ci->dev); + pm_runtime_use_autosuspend(&pdev->dev); + } + if (ci_otg_is_fsm_mode(ci)) ci_hdrc_otg_fsm_start(ci); + device_set_wakeup_capable(&pdev->dev, true); + ret = dbg_create_files(ci); if (!ret) return 0; @@ -807,6 +846,12 @@ static int ci_hdrc_remove(struct platform_device *pdev) { struct ci_hdrc *ci = platform_get_drvdata(pdev); + if (ci->supports_runtime_pm) { + pm_runtime_get_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + pm_runtime_put_noidle(&pdev->dev); + } + dbg_remove_files(ci); ci_role_destroy(ci); ci_hdrc_enter_lpm(ci, true); @@ -815,13 +860,41 @@ static int ci_hdrc_remove(struct platform_device *pdev) return 0; } -#ifdef CONFIG_PM_SLEEP +#ifdef CONFIG_PM +/* Prepare wakeup by SRP before suspend */ +static void ci_otg_fsm_suspend_for_srp(struct ci_hdrc *ci) +{ + if ((ci->fsm.otg->state == OTG_STATE_A_IDLE) && + !hw_read_otgsc(ci, OTGSC_ID)) { + hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_PP, + PORTSC_PP); + hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_WKCN, + PORTSC_WKCN); + } +} + +/* Handle SRP when wakeup by data pulse */ +static void ci_otg_fsm_wakeup_by_srp(struct ci_hdrc *ci) +{ + if ((ci->fsm.otg->state == OTG_STATE_A_IDLE) && + (ci->fsm.a_bus_drop == 1) && (ci->fsm.a_bus_req == 0)) { + if (!hw_read_otgsc(ci, OTGSC_ID)) { + ci->fsm.a_srp_det = 1; + ci->fsm.a_bus_drop = 0; + } else { + ci->fsm.id = 1; + } + ci_otg_queue_work(ci); + } +} + static void ci_controller_suspend(struct ci_hdrc *ci) { + disable_irq(ci->irq); ci_hdrc_enter_lpm(ci, true); - - if (ci->usb_phy) - usb_phy_set_suspend(ci->usb_phy, 1); + usb_phy_set_suspend(ci->usb_phy, 1); + ci->in_lpm = true; + enable_irq(ci->irq); } static int ci_controller_resume(struct device *dev) @@ -830,23 +903,59 @@ static int ci_controller_resume(struct device *dev) dev_dbg(dev, "at %s\n", __func__); - ci_hdrc_enter_lpm(ci, false); + if (!ci->in_lpm) { + WARN_ON(1); + return 0; + } + ci_hdrc_enter_lpm(ci, false); if (ci->usb_phy) { usb_phy_set_suspend(ci->usb_phy, 0); usb_phy_set_wakeup(ci->usb_phy, false); hw_wait_phy_stable(); } + ci->in_lpm = false; + if (ci->wakeup_int) { + ci->wakeup_int = false; + pm_runtime_mark_last_busy(ci->dev); + pm_runtime_put_autosuspend(ci->dev); + enable_irq(ci->irq); + if (ci_otg_is_fsm_mode(ci)) + ci_otg_fsm_wakeup_by_srp(ci); + } + return 0; } +#ifdef CONFIG_PM_SLEEP static int ci_suspend(struct device *dev) { struct ci_hdrc *ci = dev_get_drvdata(dev); if (ci->wq) flush_workqueue(ci->wq); + /* + * Controller needs to be active during suspend, otherwise the core + * may run resume when the parent is at suspend if other driver's + * suspend fails, it occurs before parent's suspend has not started, + * but the core suspend has finished. + */ + if (ci->in_lpm) + pm_runtime_resume(dev); + + if (ci->in_lpm) { + WARN_ON(1); + return 0; + } + + if (device_may_wakeup(dev)) { + if (ci_otg_is_fsm_mode(ci)) + ci_otg_fsm_suspend_for_srp(ci); + + usb_phy_set_wakeup(ci->usb_phy, true); + enable_irq_wake(ci->irq); + } ci_controller_suspend(ci); @@ -855,13 +964,57 @@ static int ci_suspend(struct device *dev) static int ci_resume(struct device *dev) { - return ci_controller_resume(dev); + struct ci_hdrc *ci = dev_get_drvdata(dev); + int ret; + + if (device_may_wakeup(dev)) + disable_irq_wake(ci->irq); + + ret = ci_controller_resume(dev); + if (ret) + return ret; + + if (ci->supports_runtime_pm) { + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + } + + return ret; } #endif /* CONFIG_PM_SLEEP */ +static int ci_runtime_suspend(struct device *dev) +{ + struct ci_hdrc *ci = dev_get_drvdata(dev); + + dev_dbg(dev, "at %s\n", __func__); + + if (ci->in_lpm) { + WARN_ON(1); + return 0; + } + + if (ci_otg_is_fsm_mode(ci)) + ci_otg_fsm_suspend_for_srp(ci); + + usb_phy_set_wakeup(ci->usb_phy, true); + ci_controller_suspend(ci); + + return 0; +} + +static int ci_runtime_resume(struct device *dev) +{ + return ci_controller_resume(dev); +} + +#endif /* CONFIG_PM */ static const struct dev_pm_ops ci_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(ci_suspend, ci_resume) + SET_RUNTIME_PM_OPS(ci_runtime_suspend, ci_runtime_resume, NULL) }; + static struct platform_driver ci_hdrc_driver = { .probe = ci_hdrc_probe, .remove = ci_hdrc_remove, diff --git a/drivers/usb/chipidea/debug.c b/drivers/usb/chipidea/debug.c index 268e4236e84c..dfb05edcdb96 100644 --- a/drivers/usb/chipidea/debug.c +++ b/drivers/usb/chipidea/debug.c @@ -336,8 +336,8 @@ static int ci_registers_show(struct seq_file *s, void *unused) struct ci_hdrc *ci = s->private; u32 tmp_reg; - if (!ci) - return 0; + if (!ci || ci->in_lpm) + return -EPERM; /* ------ Registers ----- */ tmp_reg = hw_read_intr_enable(ci); diff --git a/drivers/usb/chipidea/host.c b/drivers/usb/chipidea/host.c index 48731d0bab35..21fe1a314313 100644 --- a/drivers/usb/chipidea/host.c +++ b/drivers/usb/chipidea/host.c @@ -33,6 +33,7 @@ #include "host.h" static struct hc_driver __read_mostly ci_ehci_hc_driver; +static int (*orig_bus_suspend)(struct usb_hcd *hcd); struct ehci_ci_priv { struct regulator *reg_vbus; @@ -43,11 +44,10 @@ static int ehci_ci_portpower(struct usb_hcd *hcd, int portnum, bool enable) struct ehci_hcd *ehci = hcd_to_ehci(hcd); struct ehci_ci_priv *priv = (struct ehci_ci_priv *)ehci->priv; struct device *dev = hcd->self.controller; - struct ci_hdrc *ci = dev_get_drvdata(dev); int ret = 0; int port = HCS_N_PORTS(ehci->hcs_params); - if (priv->reg_vbus && !ci_otg_is_fsm_mode(ci)) { + if (priv->reg_vbus) { if (port > 1) { dev_warn(dev, "Not support multi-port regulator control\n"); @@ -113,12 +113,23 @@ static int host_start(struct ci_hdrc *ci) priv = (struct ehci_ci_priv *)ehci->priv; priv->reg_vbus = NULL; - if (ci->platdata->reg_vbus) - priv->reg_vbus = ci->platdata->reg_vbus; + if (ci->platdata->reg_vbus && !ci_otg_is_fsm_mode(ci)) { + if (ci->platdata->flags & CI_HDRC_TURN_VBUS_EARLY_ON) { + ret = regulator_enable(ci->platdata->reg_vbus); + if (ret) { + dev_err(ci->dev, + "Failed to enable vbus regulator, ret=%d\n", + ret); + goto put_hcd; + } + } else { + priv->reg_vbus = ci->platdata->reg_vbus; + } + } ret = usb_add_hcd(hcd, 0, 0); if (ret) { - goto put_hcd; + goto disable_reg; } else { struct usb_otg *otg = &ci->otg; @@ -133,8 +144,15 @@ static int host_start(struct ci_hdrc *ci) if (ci->platdata->flags & CI_HDRC_DISABLE_STREAMING) hw_write(ci, OP_USBMODE, USBMODE_CI_SDIS, USBMODE_CI_SDIS); + if (ci->platdata->flags & CI_HDRC_FORCE_FULLSPEED) + hw_write(ci, OP_PORTSC, PORTSC_PFSC, PORTSC_PFSC); + return ret; +disable_reg: + if (ci->platdata->reg_vbus && !ci_otg_is_fsm_mode(ci) && + (ci->platdata->flags & CI_HDRC_TURN_VBUS_EARLY_ON)) + regulator_disable(ci->platdata->reg_vbus); put_hcd: usb_put_hcd(hcd); @@ -148,6 +166,9 @@ static void host_stop(struct ci_hdrc *ci) if (hcd) { usb_remove_hcd(hcd); usb_put_hcd(hcd); + if (ci->platdata->reg_vbus && !ci_otg_is_fsm_mode(ci) && + (ci->platdata->flags & CI_HDRC_TURN_VBUS_EARLY_ON)) + regulator_disable(ci->platdata->reg_vbus); } } @@ -158,6 +179,47 @@ void ci_hdrc_host_destroy(struct ci_hdrc *ci) host_stop(ci); } +static int ci_ehci_bus_suspend(struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + int port; + u32 tmp; + + int ret = orig_bus_suspend(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); + + if (portsc & PORT_CONNECT) { + /* + * For chipidea, the resume signal will be ended + * automatically, so for remote wakeup case, the + * usbcmd.rs may not be set before the resume has + * ended if other resume paths consumes too much + * time (~24ms), in that case, the SOF will not + * send out within 3ms after resume ends, then the + * high speed device will enter full speed mode. + */ + + tmp = ehci_readl(ehci, &ehci->regs->command); + tmp |= CMD_RUN; + ehci_writel(ehci, tmp, &ehci->regs->command); + /* + * It needs a short delay between set RS bit and PHCD. + */ + usleep_range(150, 200); + break; + } + } + + return 0; +} + int ci_hdrc_host_init(struct ci_hdrc *ci) { struct ci_role_driver *rdrv; @@ -176,6 +238,8 @@ int ci_hdrc_host_init(struct ci_hdrc *ci) ci->roles[CI_ROLE_HOST] = rdrv; ehci_init_driver(&ci_ehci_hc_driver, &ehci_ci_overrides); + orig_bus_suspend = ci_ehci_hc_driver.bus_suspend; + ci_ehci_hc_driver.bus_suspend = ci_ehci_bus_suspend; return 0; } diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c index a048b08b9d4d..ad6c87a4653c 100644 --- a/drivers/usb/chipidea/otg.c +++ b/drivers/usb/chipidea/otg.c @@ -96,6 +96,7 @@ static void ci_otg_work(struct work_struct *work) return; } + pm_runtime_get_sync(ci->dev); if (ci->id_event) { ci->id_event = false; ci_handle_id_switch(ci); @@ -104,6 +105,7 @@ static void ci_otg_work(struct work_struct *work) ci_handle_vbus_change(ci); } else dev_err(ci->dev, "unexpected event occurs at %s\n", __func__); + pm_runtime_put_sync(ci->dev); enable_irq(ci->irq); } diff --git a/drivers/usb/chipidea/otg_fsm.c b/drivers/usb/chipidea/otg_fsm.c index 562e581f6765..083acf45ad5a 100644 --- a/drivers/usb/chipidea/otg_fsm.c +++ b/drivers/usb/chipidea/otg_fsm.c @@ -30,22 +30,6 @@ #include "otg.h" #include "otg_fsm.h" -static struct ci_otg_fsm_timer *otg_timer_initializer -(struct ci_hdrc *ci, void (*function)(void *, unsigned long), - unsigned long expires, unsigned long data) -{ - struct ci_otg_fsm_timer *timer; - - timer = devm_kzalloc(ci->dev, sizeof(struct ci_otg_fsm_timer), - GFP_KERNEL); - if (!timer) - return NULL; - timer->function = function; - timer->expires = expires; - timer->data = data; - return timer; -} - /* Add for otg: interact with user space app */ static ssize_t get_a_bus_req(struct device *dev, struct device_attribute *attr, char *buf) @@ -204,229 +188,227 @@ static struct attribute_group inputs_attr_group = { }; /* + * Keep this list in the same order as timers indexed + * by enum otg_fsm_timer in include/linux/usb/otg-fsm.h + */ +static unsigned otg_timer_ms[] = { + TA_WAIT_VRISE, + TA_WAIT_VFALL, + TA_WAIT_BCON, + TA_AIDL_BDIS, + TB_ASE0_BRST, + TA_BIDL_ADIS, + TB_SE0_SRP, + TB_SRP_FAIL, + 0, + TB_DATA_PLS, + TB_SSEND_SRP, +}; + +/* * Add timer to active timer list */ -static void ci_otg_add_timer(struct ci_hdrc *ci, enum ci_otg_fsm_timer_index t) +static void ci_otg_add_timer(struct ci_hdrc *ci, enum otg_fsm_timer t) { - struct ci_otg_fsm_timer *tmp_timer; - struct ci_otg_fsm_timer *timer = ci->fsm_timer->timer_list[t]; - struct list_head *active_timers = &ci->fsm_timer->active_timers; + unsigned long flags, timer_sec, timer_nsec; - if (t >= NUM_CI_OTG_FSM_TIMERS) + if (t >= NUM_OTG_FSM_TIMERS) return; - /* - * Check if the timer is already in the active list, - * if so update timer count - */ - list_for_each_entry(tmp_timer, active_timers, list) - if (tmp_timer == timer) { - timer->count = timer->expires; - return; - } - - timer->count = timer->expires; - list_add_tail(&timer->list, active_timers); - - /* Enable 1ms irq */ - if (!(hw_read_otgsc(ci, OTGSC_1MSIE))) - hw_write_otgsc(ci, OTGSC_1MSIE, OTGSC_1MSIE); + spin_lock_irqsave(&ci->lock, flags); + timer_sec = otg_timer_ms[t] / MSEC_PER_SEC; + timer_nsec = (otg_timer_ms[t] % MSEC_PER_SEC) * NSEC_PER_MSEC; + ci->hr_timeouts[t] = ktime_add(ktime_get(), + ktime_set(timer_sec, timer_nsec)); + ci->enabled_otg_timer_bits |= (1 << t); + if ((ci->next_otg_timer == NUM_OTG_FSM_TIMERS) || + (ci->hr_timeouts[ci->next_otg_timer].tv64 > + ci->hr_timeouts[t].tv64)) { + ci->next_otg_timer = t; + hrtimer_start_range_ns(&ci->otg_fsm_hrtimer, + ci->hr_timeouts[t], NSEC_PER_MSEC, + HRTIMER_MODE_ABS); + } + spin_unlock_irqrestore(&ci->lock, flags); } /* * Remove timer from active timer list */ -static void ci_otg_del_timer(struct ci_hdrc *ci, enum ci_otg_fsm_timer_index t) +static void ci_otg_del_timer(struct ci_hdrc *ci, enum otg_fsm_timer t) { - struct ci_otg_fsm_timer *tmp_timer, *del_tmp; - struct ci_otg_fsm_timer *timer = ci->fsm_timer->timer_list[t]; - struct list_head *active_timers = &ci->fsm_timer->active_timers; + unsigned long flags, enabled_timer_bits; + enum otg_fsm_timer cur_timer, next_timer = NUM_OTG_FSM_TIMERS; - if (t >= NUM_CI_OTG_FSM_TIMERS) + if ((t >= NUM_OTG_FSM_TIMERS) || + !(ci->enabled_otg_timer_bits & (1 << t))) return; - list_for_each_entry_safe(tmp_timer, del_tmp, active_timers, list) - if (tmp_timer == timer) - list_del(&timer->list); - - /* Disable 1ms irq if there is no any active timer */ - if (list_empty(active_timers)) - hw_write_otgsc(ci, OTGSC_1MSIE, 0); -} - -/* - * Reduce timer count by 1, and find timeout conditions. - * Called by otg 1ms timer interrupt - */ -static inline int ci_otg_tick_timer(struct ci_hdrc *ci) -{ - struct ci_otg_fsm_timer *tmp_timer, *del_tmp; - struct list_head *active_timers = &ci->fsm_timer->active_timers; - int expired = 0; - - list_for_each_entry_safe(tmp_timer, del_tmp, active_timers, list) { - tmp_timer->count--; - /* check if timer expires */ - if (!tmp_timer->count) { - list_del(&tmp_timer->list); - tmp_timer->function(ci, tmp_timer->data); - expired = 1; + spin_lock_irqsave(&ci->lock, flags); + ci->enabled_otg_timer_bits &= ~(1 << t); + if (ci->next_otg_timer == t) { + if (ci->enabled_otg_timer_bits == 0) { + /* No enabled timers after delete it */ + hrtimer_cancel(&ci->otg_fsm_hrtimer); + ci->next_otg_timer = NUM_OTG_FSM_TIMERS; + } else { + /* Find the next timer */ + enabled_timer_bits = ci->enabled_otg_timer_bits; + for_each_set_bit(cur_timer, &enabled_timer_bits, + NUM_OTG_FSM_TIMERS) { + if ((next_timer == NUM_OTG_FSM_TIMERS) || + (ci->hr_timeouts[next_timer].tv64 < + ci->hr_timeouts[cur_timer].tv64)) + next_timer = cur_timer; + } } } - - /* disable 1ms irq if there is no any timer active */ - if ((expired == 1) && list_empty(active_timers)) - hw_write_otgsc(ci, OTGSC_1MSIE, 0); - - return expired; + if (next_timer != NUM_OTG_FSM_TIMERS) { + ci->next_otg_timer = next_timer; + hrtimer_start_range_ns(&ci->otg_fsm_hrtimer, + ci->hr_timeouts[next_timer], NSEC_PER_MSEC, + HRTIMER_MODE_ABS); + } + spin_unlock_irqrestore(&ci->lock, flags); } -/* The timeout callback function to set time out bit */ -static void set_tmout(void *ptr, unsigned long indicator) +/* OTG FSM timer handlers */ +static int a_wait_vrise_tmout(struct ci_hdrc *ci) { - *(int *)indicator = 1; + ci->fsm.a_wait_vrise_tmout = 1; + return 0; } -static void set_tmout_and_fsm(void *ptr, unsigned long indicator) +static int a_wait_vfall_tmout(struct ci_hdrc *ci) { - struct ci_hdrc *ci = (struct ci_hdrc *)ptr; - - set_tmout(ci, indicator); - - ci_otg_queue_work(ci); + ci->fsm.a_wait_vfall_tmout = 1; + return 0; } -static void a_wait_vfall_tmout_func(void *ptr, unsigned long indicator) +static int a_wait_bcon_tmout(struct ci_hdrc *ci) { - struct ci_hdrc *ci = (struct ci_hdrc *)ptr; - - set_tmout(ci, indicator); - /* Disable port power */ - hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_PP, 0); - /* Clear existing DP irq */ - hw_write_otgsc(ci, OTGSC_DPIS, OTGSC_DPIS); - /* Enable data pulse irq */ - hw_write_otgsc(ci, OTGSC_DPIE, OTGSC_DPIE); - ci_otg_queue_work(ci); + ci->fsm.a_wait_bcon_tmout = 1; + return 0; } -static void b_ase0_brst_tmout_func(void *ptr, unsigned long indicator) +static int a_aidl_bdis_tmout(struct ci_hdrc *ci) { - struct ci_hdrc *ci = (struct ci_hdrc *)ptr; - - set_tmout(ci, indicator); - if (!hw_read_otgsc(ci, OTGSC_BSV)) - ci->fsm.b_sess_vld = 0; - - ci_otg_queue_work(ci); + ci->fsm.a_aidl_bdis_tmout = 1; + return 0; } -static void b_ssend_srp_tmout_func(void *ptr, unsigned long indicator) +static int b_ase0_brst_tmout(struct ci_hdrc *ci) { - struct ci_hdrc *ci = (struct ci_hdrc *)ptr; - - set_tmout(ci, indicator); - - /* only vbus fall below B_sess_vld in b_idle state */ - if (ci->fsm.otg->state == OTG_STATE_B_IDLE) - ci_otg_queue_work(ci); + ci->fsm.b_ase0_brst_tmout = 1; + return 0; } -static void b_sess_vld_tmout_func(void *ptr, unsigned long indicator) +static int a_bidl_adis_tmout(struct ci_hdrc *ci) { - struct ci_hdrc *ci = (struct ci_hdrc *)ptr; + ci->fsm.a_bidl_adis_tmout = 1; + return 0; +} - /* Check if A detached */ - if (!(hw_read_otgsc(ci, OTGSC_BSV))) { - ci->fsm.b_sess_vld = 0; - ci_otg_add_timer(ci, B_SSEND_SRP); - ci_otg_queue_work(ci); - } +static int b_se0_srp_tmout(struct ci_hdrc *ci) +{ + ci->fsm.b_se0_srp = 1; + return 0; } -static void b_data_pulse_end(void *ptr, unsigned long indicator) +static int b_srp_fail_tmout(struct ci_hdrc *ci) { - struct ci_hdrc *ci = (struct ci_hdrc *)ptr; + ci->fsm.b_srp_done = 1; + return 1; +} +static int b_data_pls_tmout(struct ci_hdrc *ci) +{ ci->fsm.b_srp_done = 1; ci->fsm.b_bus_req = 0; if (ci->fsm.power_up) ci->fsm.power_up = 0; - hw_write_otgsc(ci, OTGSC_HABA, 0); + pm_runtime_put(ci->dev); + return 0; +} - ci_otg_queue_work(ci); +static int b_ssend_srp_tmout(struct ci_hdrc *ci) +{ + ci->fsm.b_ssend_srp = 1; + /* only vbus fall below B_sess_vld in b_idle state */ + if (ci->fsm.otg->state == OTG_STATE_B_IDLE) + return 0; + else + return 1; +} + +/* + * Keep this list in the same order as timers indexed + * by enum otg_fsm_timer in include/linux/usb/otg-fsm.h + */ +static int (*otg_timer_handlers[])(struct ci_hdrc *) = { + a_wait_vrise_tmout, /* A_WAIT_VRISE */ + a_wait_vfall_tmout, /* A_WAIT_VFALL */ + a_wait_bcon_tmout, /* A_WAIT_BCON */ + a_aidl_bdis_tmout, /* A_AIDL_BDIS */ + b_ase0_brst_tmout, /* B_ASE0_BRST */ + a_bidl_adis_tmout, /* A_BIDL_ADIS */ + b_se0_srp_tmout, /* B_SE0_SRP */ + b_srp_fail_tmout, /* B_SRP_FAIL */ + NULL, /* A_WAIT_ENUM */ + b_data_pls_tmout, /* B_DATA_PLS */ + b_ssend_srp_tmout, /* B_SSEND_SRP */ +}; + +/* + * Enable the next nearest enabled timer if have + */ +static enum hrtimer_restart ci_otg_hrtimer_func(struct hrtimer *t) +{ + struct ci_hdrc *ci = container_of(t, struct ci_hdrc, otg_fsm_hrtimer); + ktime_t now, *timeout; + unsigned long enabled_timer_bits; + unsigned long flags; + enum otg_fsm_timer cur_timer, next_timer = NUM_OTG_FSM_TIMERS; + int ret = -EINVAL; + + spin_lock_irqsave(&ci->lock, flags); + enabled_timer_bits = ci->enabled_otg_timer_bits; + ci->next_otg_timer = NUM_OTG_FSM_TIMERS; + + now = ktime_get(); + for_each_set_bit(cur_timer, &enabled_timer_bits, NUM_OTG_FSM_TIMERS) { + if (now.tv64 >= ci->hr_timeouts[cur_timer].tv64) { + ci->enabled_otg_timer_bits &= ~(1 << cur_timer); + if (otg_timer_handlers[cur_timer]) + ret = otg_timer_handlers[cur_timer](ci); + } else { + if ((next_timer == NUM_OTG_FSM_TIMERS) || + (ci->hr_timeouts[cur_timer].tv64 < + ci->hr_timeouts[next_timer].tv64)) + next_timer = cur_timer; + } + } + /* Enable the next nearest timer */ + if (next_timer < NUM_OTG_FSM_TIMERS) { + timeout = &ci->hr_timeouts[next_timer]; + hrtimer_start_range_ns(&ci->otg_fsm_hrtimer, *timeout, + NSEC_PER_MSEC, HRTIMER_MODE_ABS); + ci->next_otg_timer = next_timer; + } + spin_unlock_irqrestore(&ci->lock, flags); + + if (!ret) + ci_otg_queue_work(ci); + + return HRTIMER_NORESTART; } /* Initialize timers */ static int ci_otg_init_timers(struct ci_hdrc *ci) { - struct otg_fsm *fsm = &ci->fsm; - - /* FSM used timers */ - ci->fsm_timer->timer_list[A_WAIT_VRISE] = - otg_timer_initializer(ci, &set_tmout_and_fsm, TA_WAIT_VRISE, - (unsigned long)&fsm->a_wait_vrise_tmout); - if (ci->fsm_timer->timer_list[A_WAIT_VRISE] == NULL) - return -ENOMEM; - - ci->fsm_timer->timer_list[A_WAIT_VFALL] = - otg_timer_initializer(ci, &a_wait_vfall_tmout_func, - TA_WAIT_VFALL, (unsigned long)&fsm->a_wait_vfall_tmout); - if (ci->fsm_timer->timer_list[A_WAIT_VFALL] == NULL) - return -ENOMEM; - - ci->fsm_timer->timer_list[A_WAIT_BCON] = - otg_timer_initializer(ci, &set_tmout_and_fsm, TA_WAIT_BCON, - (unsigned long)&fsm->a_wait_bcon_tmout); - if (ci->fsm_timer->timer_list[A_WAIT_BCON] == NULL) - return -ENOMEM; - - ci->fsm_timer->timer_list[A_AIDL_BDIS] = - otg_timer_initializer(ci, &set_tmout_and_fsm, TA_AIDL_BDIS, - (unsigned long)&fsm->a_aidl_bdis_tmout); - if (ci->fsm_timer->timer_list[A_AIDL_BDIS] == NULL) - return -ENOMEM; - - ci->fsm_timer->timer_list[A_BIDL_ADIS] = - otg_timer_initializer(ci, &set_tmout_and_fsm, TA_BIDL_ADIS, - (unsigned long)&fsm->a_bidl_adis_tmout); - if (ci->fsm_timer->timer_list[A_BIDL_ADIS] == NULL) - return -ENOMEM; - - ci->fsm_timer->timer_list[B_ASE0_BRST] = - otg_timer_initializer(ci, &b_ase0_brst_tmout_func, TB_ASE0_BRST, - (unsigned long)&fsm->b_ase0_brst_tmout); - if (ci->fsm_timer->timer_list[B_ASE0_BRST] == NULL) - return -ENOMEM; - - ci->fsm_timer->timer_list[B_SE0_SRP] = - otg_timer_initializer(ci, &set_tmout_and_fsm, TB_SE0_SRP, - (unsigned long)&fsm->b_se0_srp); - if (ci->fsm_timer->timer_list[B_SE0_SRP] == NULL) - return -ENOMEM; - - ci->fsm_timer->timer_list[B_SSEND_SRP] = - otg_timer_initializer(ci, &b_ssend_srp_tmout_func, TB_SSEND_SRP, - (unsigned long)&fsm->b_ssend_srp); - if (ci->fsm_timer->timer_list[B_SSEND_SRP] == NULL) - return -ENOMEM; - - ci->fsm_timer->timer_list[B_SRP_FAIL] = - otg_timer_initializer(ci, &set_tmout, TB_SRP_FAIL, - (unsigned long)&fsm->b_srp_done); - if (ci->fsm_timer->timer_list[B_SRP_FAIL] == NULL) - return -ENOMEM; - - ci->fsm_timer->timer_list[B_DATA_PLS] = - otg_timer_initializer(ci, &b_data_pulse_end, TB_DATA_PLS, 0); - if (ci->fsm_timer->timer_list[B_DATA_PLS] == NULL) - return -ENOMEM; - - ci->fsm_timer->timer_list[B_SESS_VLD] = otg_timer_initializer(ci, - &b_sess_vld_tmout_func, TB_SESS_VLD, 0); - if (ci->fsm_timer->timer_list[B_SESS_VLD] == NULL) - return -ENOMEM; + hrtimer_init(&ci->otg_fsm_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); + ci->otg_fsm_hrtimer.function = ci_otg_hrtimer_func; return 0; } @@ -530,6 +512,7 @@ static void ci_otg_start_pulse(struct otg_fsm *fsm) /* Hardware Assistant Data pulse */ hw_write_otgsc(ci, OTGSC_HADP, OTGSC_HADP); + pm_runtime_get(ci->dev); ci_otg_add_timer(ci, B_DATA_PLS); } @@ -585,6 +568,7 @@ int ci_otg_fsm_work(struct ci_hdrc *ci) ci->fsm.otg->state < OTG_STATE_A_IDLE) return 0; + pm_runtime_get_sync(ci->dev); if (otg_statemachine(&ci->fsm)) { if (ci->fsm.otg->state == OTG_STATE_A_IDLE) { /* @@ -596,8 +580,15 @@ int ci_otg_fsm_work(struct ci_hdrc *ci) * a_idle to a_wait_vrise when power up */ if ((ci->fsm.id) || (ci->id_event) || - (ci->fsm.power_up)) + (ci->fsm.power_up)) { ci_otg_queue_work(ci); + } else { + /* Enable data pulse irq */ + hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | + PORTSC_PP, 0); + hw_write_otgsc(ci, OTGSC_DPIS, OTGSC_DPIS); + hw_write_otgsc(ci, OTGSC_DPIE, OTGSC_DPIE); + } if (ci->id_event) ci->id_event = false; } else if (ci->fsm.otg->state == OTG_STATE_B_IDLE) { @@ -609,8 +600,13 @@ int ci_otg_fsm_work(struct ci_hdrc *ci) */ ci_otg_queue_work(ci); } + } else if (ci->fsm.otg->state == OTG_STATE_A_HOST) { + pm_runtime_mark_last_busy(ci->dev); + pm_runtime_put_autosuspend(ci->dev); + return 0; } } + pm_runtime_put_sync(ci->dev); return 0; } @@ -655,7 +651,6 @@ static void ci_otg_fsm_event(struct ci_hdrc *ci) fsm->a_conn = 0; fsm->b_bus_req = 0; ci_otg_queue_work(ci); - ci_otg_add_timer(ci, B_SESS_VLD); } break; case OTG_STATE_A_PERIPHERAL: @@ -725,11 +720,7 @@ irqreturn_t ci_otg_fsm_irq(struct ci_hdrc *ci) fsm->id = (otgsc & OTGSC_ID) ? 1 : 0; if (otg_int_src) { - if (otg_int_src & OTGSC_1MSIS) { - hw_write_otgsc(ci, OTGSC_1MSIS, OTGSC_1MSIS); - retval = ci_otg_tick_timer(ci); - return IRQ_HANDLED; - } else if (otg_int_src & OTGSC_DPIS) { + if (otg_int_src & OTGSC_DPIS) { hw_write_otgsc(ci, OTGSC_DPIS, OTGSC_DPIS); fsm->a_srp_det = 1; fsm->a_bus_drop = 0; @@ -793,17 +784,13 @@ int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci) mutex_init(&ci->fsm.lock); - ci->fsm_timer = devm_kzalloc(ci->dev, - sizeof(struct ci_otg_fsm_timer_list), GFP_KERNEL); - if (!ci->fsm_timer) - return -ENOMEM; - - INIT_LIST_HEAD(&ci->fsm_timer->active_timers); retval = ci_otg_init_timers(ci); if (retval) { dev_err(ci->dev, "Couldn't init OTG timers\n"); return retval; } + ci->enabled_otg_timer_bits = 0; + ci->next_otg_timer = NUM_OTG_FSM_TIMERS; retval = sysfs_create_group(&ci->dev->kobj, &inputs_attr_group); if (retval < 0) { diff --git a/drivers/usb/chipidea/otg_fsm.h b/drivers/usb/chipidea/otg_fsm.h index 94c085f456a9..2689375ae5da 100644 --- a/drivers/usb/chipidea/otg_fsm.h +++ b/drivers/usb/chipidea/otg_fsm.h @@ -62,33 +62,6 @@ /* SSEND time before SRP */ #define TB_SSEND_SRP (1500) /* minimum 1.5 sec, section:5.1.2 */ -#define TB_SESS_VLD (1000) - -enum ci_otg_fsm_timer_index { - /* - * CI specific timers, start from the end - * of standard and auxiliary OTG timers - */ - B_DATA_PLS = NUM_OTG_FSM_TIMERS, - B_SSEND_SRP, - B_SESS_VLD, - - NUM_CI_OTG_FSM_TIMERS, -}; - -struct ci_otg_fsm_timer { - unsigned long expires; /* Number of count increase to timeout */ - unsigned long count; /* Tick counter */ - void (*function)(void *, unsigned long); /* Timeout function */ - unsigned long data; /* Data passed to function */ - struct list_head list; -}; - -struct ci_otg_fsm_timer_list { - struct ci_otg_fsm_timer *timer_list[NUM_CI_OTG_FSM_TIMERS]; - struct list_head active_timers; -}; - #ifdef CONFIG_USB_OTG_FSM int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci); diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c index 4bfb7ac0239f..764f668d45a9 100644 --- a/drivers/usb/chipidea/udc.c +++ b/drivers/usb/chipidea/udc.c @@ -86,10 +86,8 @@ static int hw_device_state(struct ci_hdrc *ci, u32 dma) /* interrupt, error, port change, reset, sleep/suspend */ hw_write(ci, OP_USBINTR, ~0, USBi_UI|USBi_UEI|USBi_PCI|USBi_URI|USBi_SLI); - hw_write(ci, OP_USBCMD, USBCMD_RS, USBCMD_RS); } else { hw_write(ci, OP_USBINTR, ~0, 0); - hw_write(ci, OP_USBCMD, USBCMD_RS, 0); } return 0; } @@ -522,6 +520,20 @@ static void free_pending_td(struct ci_hw_ep *hwep) kfree(pending); } +static int reprime_dtd(struct ci_hdrc *ci, struct ci_hw_ep *hwep, + struct td_node *node) +{ + hwep->qh.ptr->td.next = node->dma; + hwep->qh.ptr->td.token &= + cpu_to_le32(~(TD_STATUS_HALTED | TD_STATUS_ACTIVE)); + + /* Synchronize before ep prime */ + wmb(); + + return hw_ep_prime(ci, hwep->num, hwep->dir, + hwep->type == USB_ENDPOINT_XFER_CONTROL); +} + /** * _hardware_dequeue: handles a request at hardware level * @gadget: gadget @@ -535,6 +547,7 @@ static int _hardware_dequeue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq) struct td_node *node, *tmpnode; unsigned remaining_length; unsigned actual = hwreq->req.length; + struct ci_hdrc *ci = hwep->ci; if (hwreq->req.status != -EALREADY) return -EINVAL; @@ -544,6 +557,11 @@ static int _hardware_dequeue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq) list_for_each_entry_safe(node, tmpnode, &hwreq->tds, td) { tmptoken = le32_to_cpu(node->ptr->token); if ((TD_STATUS_ACTIVE & tmptoken) != 0) { + int n = hw_ep_bit(hwep->num, hwep->dir); + + if (ci->rev == CI_REVISION_24) + if (!hw_read(ci, OP_ENDPTSTAT, BIT(n))) + reprime_dtd(ci, hwep, node); hwreq->req.status = -EALREADY; return -EBUSY; } @@ -1162,10 +1180,13 @@ static int ep_enable(struct usb_ep *ep, /* only internal SW should enable ctrl endpts */ - hwep->ep.desc = desc; - - if (!list_empty(&hwep->qh.queue)) + if (!list_empty(&hwep->qh.queue)) { dev_warn(hwep->ci->dev, "enabling a non-empty endpoint!\n"); + spin_unlock_irqrestore(hwep->lock, flags); + return -EBUSY; + } + + hwep->ep.desc = desc; hwep->dir = usb_endpoint_dir_in(desc) ? TX : RX; hwep->num = usb_endpoint_num(desc); @@ -1485,7 +1506,9 @@ static int ci_udc_vbus_session(struct usb_gadget *_gadget, int is_active) hw_device_reset(ci); hw_device_state(ci, ci->ep0out->qh.dma); usb_gadget_set_state(_gadget, USB_STATE_POWERED); + usb_udc_vbus_handler(_gadget, true); } else { + usb_udc_vbus_handler(_gadget, false); if (ci->driver) ci->driver->disconnect(&ci->gadget); hw_device_state(ci, 0); @@ -1551,13 +1574,16 @@ static int ci_udc_pullup(struct usb_gadget *_gadget, int is_on) { struct ci_hdrc *ci = container_of(_gadget, struct ci_hdrc, gadget); - if (!ci->vbus_active) - return -EOPNOTSUPP; + /* Data+ pullup controlled by OTG state machine in OTG fsm mode */ + if (ci_otg_is_fsm_mode(ci)) + return 0; + pm_runtime_get_sync(&ci->gadget.dev); if (is_on) hw_write(ci, OP_USBCMD, USBCMD_RS, USBCMD_RS); else hw_write(ci, OP_USBCMD, USBCMD_RS, 0); + pm_runtime_put_sync(&ci->gadget.dev); return 0; } @@ -1687,6 +1713,7 @@ static int ci_udc_start(struct usb_gadget *gadget, spin_lock_irqsave(&ci->lock, flags); hw_device_reset(ci); } else { + usb_udc_vbus_handler(&ci->gadget, false); pm_runtime_put_sync(&ci->gadget.dev); return retval; } diff --git a/drivers/usb/chipidea/usbmisc_imx.c b/drivers/usb/chipidea/usbmisc_imx.c index c3c6225b8acf..140945cb124f 100644 --- a/drivers/usb/chipidea/usbmisc_imx.c +++ b/drivers/usb/chipidea/usbmisc_imx.c @@ -11,7 +11,6 @@ #include <linux/module.h> #include <linux/of_platform.h> -#include <linux/clk.h> #include <linux/err.h> #include <linux/io.h> #include <linux/delay.h> @@ -56,6 +55,19 @@ #define MX53_USB_PLL_DIV_24_MHZ 0x01 #define MX6_BM_OVER_CUR_DIS BIT(7) +#define MX6_BM_WAKEUP_ENABLE BIT(10) +#define MX6_BM_ID_WAKEUP BIT(16) +#define MX6_BM_VBUS_WAKEUP BIT(17) +#define MX6SX_BM_DPDM_WAKEUP_EN BIT(29) +#define MX6_BM_WAKEUP_INTR BIT(31) +#define MX6_USB_OTG1_PHY_CTRL 0x18 +/* For imx6dql, it is host-only controller, for later imx6, it is otg's */ +#define MX6_USB_OTG2_PHY_CTRL 0x1c +#define MX6SX_USB_VBUS_WAKEUP_SOURCE(v) (v << 8) +#define MX6SX_USB_VBUS_WAKEUP_SOURCE_VBUS MX6SX_USB_VBUS_WAKEUP_SOURCE(0) +#define MX6SX_USB_VBUS_WAKEUP_SOURCE_AVALID MX6SX_USB_VBUS_WAKEUP_SOURCE(1) +#define MX6SX_USB_VBUS_WAKEUP_SOURCE_BVALID MX6SX_USB_VBUS_WAKEUP_SOURCE(2) +#define MX6SX_USB_VBUS_WAKEUP_SOURCE_SESS_END MX6SX_USB_VBUS_WAKEUP_SOURCE(3) #define VF610_OVER_CUR_DIS BIT(7) @@ -64,12 +76,13 @@ struct usbmisc_ops { int (*init)(struct imx_usbmisc_data *data); /* It's called once after adding a usb device */ int (*post)(struct imx_usbmisc_data *data); + /* It's called when we need to enable/disable usb wakeup */ + int (*set_wakeup)(struct imx_usbmisc_data *data, bool enabled); }; struct imx_usbmisc { void __iomem *base; spinlock_t lock; - struct clk *clk; const struct usbmisc_ops *ops; }; @@ -204,6 +217,35 @@ static int usbmisc_imx53_init(struct imx_usbmisc_data *data) return 0; } +static int usbmisc_imx6q_set_wakeup + (struct imx_usbmisc_data *data, bool enabled) +{ + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + unsigned long flags; + u32 val; + u32 wakeup_setting = (MX6_BM_WAKEUP_ENABLE | + MX6_BM_VBUS_WAKEUP | MX6_BM_ID_WAKEUP); + int ret = 0; + + if (data->index > 3) + return -EINVAL; + + spin_lock_irqsave(&usbmisc->lock, flags); + val = readl(usbmisc->base + data->index * 4); + if (enabled) { + val |= wakeup_setting; + writel(val, usbmisc->base + data->index * 4); + } else { + if (val & MX6_BM_WAKEUP_INTR) + pr_debug("wakeup int at ci_hdrc.%d\n", data->index); + val &= ~wakeup_setting; + writel(val, usbmisc->base + data->index * 4); + } + spin_unlock_irqrestore(&usbmisc->lock, flags); + + return ret; +} + static int usbmisc_imx6q_init(struct imx_usbmisc_data *data) { struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); @@ -221,6 +263,36 @@ static int usbmisc_imx6q_init(struct imx_usbmisc_data *data) spin_unlock_irqrestore(&usbmisc->lock, flags); } + usbmisc_imx6q_set_wakeup(data, false); + + return 0; +} + +static int usbmisc_imx6sx_init(struct imx_usbmisc_data *data) +{ + void __iomem *reg = NULL; + unsigned long flags; + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + u32 val; + + usbmisc_imx6q_init(data); + + if (data->index == 0 || data->index == 1) { + reg = usbmisc->base + MX6_USB_OTG1_PHY_CTRL + data->index * 4; + spin_lock_irqsave(&usbmisc->lock, flags); + /* Set vbus wakeup source as bvalid */ + val = readl(reg); + writel(val | MX6SX_USB_VBUS_WAKEUP_SOURCE_BVALID, reg); + /* + * Disable dp/dm wakeup in device mode when vbus is + * not there. + */ + val = readl(usbmisc->base + data->index * 4); + writel(val & ~MX6SX_BM_DPDM_WAKEUP_EN, + usbmisc->base + data->index * 4); + spin_unlock_irqrestore(&usbmisc->lock, flags); + } + return 0; } @@ -258,6 +330,7 @@ static const struct usbmisc_ops imx53_usbmisc_ops = { }; static const struct usbmisc_ops imx6q_usbmisc_ops = { + .set_wakeup = usbmisc_imx6q_set_wakeup, .init = usbmisc_imx6q_init, }; @@ -265,10 +338,19 @@ static const struct usbmisc_ops vf610_usbmisc_ops = { .init = usbmisc_vf610_init, }; +static const struct usbmisc_ops imx6sx_usbmisc_ops = { + .set_wakeup = usbmisc_imx6q_set_wakeup, + .init = usbmisc_imx6sx_init, +}; + int imx_usbmisc_init(struct imx_usbmisc_data *data) { - struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + struct imx_usbmisc *usbmisc; + + if (!data) + return 0; + usbmisc = dev_get_drvdata(data->dev); if (!usbmisc->ops->init) return 0; return usbmisc->ops->init(data); @@ -277,14 +359,32 @@ EXPORT_SYMBOL_GPL(imx_usbmisc_init); int imx_usbmisc_init_post(struct imx_usbmisc_data *data) { - struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + struct imx_usbmisc *usbmisc; + 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 (!data) + return 0; + + usbmisc = dev_get_drvdata(data->dev); + if (!usbmisc->ops->set_wakeup) + return 0; + return usbmisc->ops->set_wakeup(data, enabled); +} +EXPORT_SYMBOL_GPL(imx_usbmisc_set_wakeup); + static const struct of_device_id usbmisc_imx_dt_ids[] = { { .compatible = "fsl,imx25-usbmisc", @@ -314,6 +414,10 @@ static const struct of_device_id usbmisc_imx_dt_ids[] = { .compatible = "fsl,vf610-usbmisc", .data = &vf610_usbmisc_ops, }, + { + .compatible = "fsl,imx6sx-usbmisc", + .data = &imx6sx_usbmisc_ops, + }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, usbmisc_imx_dt_ids); @@ -322,7 +426,6 @@ static int usbmisc_imx_probe(struct platform_device *pdev) { struct resource *res; struct imx_usbmisc *data; - int ret; struct of_device_id *tmp_dev; data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); @@ -336,20 +439,6 @@ static int usbmisc_imx_probe(struct platform_device *pdev) if (IS_ERR(data->base)) return PTR_ERR(data->base); - data->clk = devm_clk_get(&pdev->dev, NULL); - if (IS_ERR(data->clk)) { - dev_err(&pdev->dev, - "failed to get clock, err=%ld\n", PTR_ERR(data->clk)); - return PTR_ERR(data->clk); - } - - ret = clk_prepare_enable(data->clk); - if (ret) { - dev_err(&pdev->dev, - "clk_prepare_enable failed, err=%d\n", ret); - return ret; - } - tmp_dev = (struct of_device_id *) of_match_device(usbmisc_imx_dt_ids, &pdev->dev); data->ops = (const struct usbmisc_ops *)tmp_dev->data; @@ -360,8 +449,6 @@ static int usbmisc_imx_probe(struct platform_device *pdev) static int usbmisc_imx_remove(struct platform_device *pdev) { - struct imx_usbmisc *usbmisc = dev_get_drvdata(&pdev->dev); - clk_disable_unprepare(usbmisc->clk); return 0; } |