From: Hao Wu <hao.wu@xxxxxxxxx> This patch enables PHY low power mode in OTG state machine. Please notice that there is a limitation, after drive VBus down, 2ms delay is required from SCU FW to sync up OTGSC register with USBCFG register. Signed-off-by: Hao Wu <hao.wu@xxxxxxxxx> Signed-off-by: Alan Cox <alan@xxxxxxxxxxxxxxx> --- drivers/usb/otg/langwell_otg.c | 154 +++++++++++++++++++++++++++++++++++++- include/linux/usb/langwell_otg.h | 8 ++ 2 files changed, 157 insertions(+), 5 deletions(-) diff --git a/drivers/usb/otg/langwell_otg.c b/drivers/usb/otg/langwell_otg.c index ada4cda..2cbfec5 100644 --- a/drivers/usb/otg/langwell_otg.c +++ b/drivers/usb/otg/langwell_otg.c @@ -295,10 +295,64 @@ static void langwell_otg_loc_sof(int on) dev_dbg(&lnw->pdev->dev, "%s <---\n", __func__); } +static int langwell_otg_check_otgsc(void) +{ + struct langwell_otg *lnw = the_transceiver; + u32 otgsc, usbcfg; + + dev_dbg(&lnw->pdev->dev, "check sync OTGSC and USBCFG registers\n"); + + otgsc = readl(lnw->regs + CI_OTGSC); + usbcfg = readl(lnw->usbcfg); + + dev_dbg(&lnw->pdev->dev, "OTGSC = %08x, USBCFG = %08x\n", + otgsc, usbcfg); + dev_dbg(&lnw->pdev->dev, "OTGSC_AVV = %d\n", !!(otgsc & OTGSC_AVV)); + dev_dbg(&lnw->pdev->dev, "USBCFG.VBUSVAL = %d\n", + !!(usbcfg & USBCFG_VBUSVAL)); + dev_dbg(&lnw->pdev->dev, "OTGSC_ASV = %d\n", !!(otgsc & OTGSC_ASV)); + dev_dbg(&lnw->pdev->dev, "USBCFG.AVALID = %d\n", + !!(usbcfg & USBCFG_AVALID)); + dev_dbg(&lnw->pdev->dev, "OTGSC_BSV = %d\n", !!(otgsc & OTGSC_BSV)); + dev_dbg(&lnw->pdev->dev, "USBCFG.BVALID = %d\n", + !!(usbcfg & USBCFG_BVALID)); + dev_dbg(&lnw->pdev->dev, "OTGSC_BSE = %d\n", !!(otgsc & OTGSC_BSE)); + dev_dbg(&lnw->pdev->dev, "USBCFG.SESEND = %d\n", + !!(usbcfg & USBCFG_SESEND)); + + /* Check USBCFG VBusValid/AValid/BValid/SessEnd */ + if (!!(otgsc & OTGSC_AVV) ^ !!(usbcfg & USBCFG_VBUSVAL)) { + dev_dbg(&lnw->pdev->dev, "OTGSC.AVV != USBCFG.VBUSVAL\n"); + goto err; + } + if (!!(otgsc & OTGSC_ASV) ^ !!(usbcfg & USBCFG_AVALID)) { + dev_dbg(&lnw->pdev->dev, "OTGSC.ASV != USBCFG.AVALID\n"); + goto err; + } + if (!!(otgsc & OTGSC_BSV) ^ !!(usbcfg & USBCFG_BVALID)) { + dev_dbg(&lnw->pdev->dev, "OTGSC.BSV != USBCFG.BVALID\n"); + goto err; + } + if (!!(otgsc & OTGSC_BSE) ^ !!(usbcfg & USBCFG_SESEND)) { + dev_dbg(&lnw->pdev->dev, "OTGSC.BSE != USBCFG.SESSEN\n"); + goto err; + } + + dev_dbg(&lnw->pdev->dev, "OTGSC and USBCFG are synced\n"); + + return 0; + +err: + dev_warn(&lnw->pdev->dev, "OTGSC isn't equal to USBCFG\n"); + return -EPIPE; +} + + static void langwell_otg_phy_low_power(int on) { struct langwell_otg *lnw = the_transceiver; u8 val, phcd; + int retval; dev_dbg(&lnw->pdev->dev, "%s ---> %s mode\n", __func__, on ? "Low power" : "Normal"); @@ -307,14 +361,34 @@ static void langwell_otg_phy_low_power(int on) val = readb(lnw->regs + CI_HOSTPC1 + 2); - if (on) + if (on) { + /* Due to hardware issue, after set PHCD, sync will failed + * between USBCFG and OTGSC, so before set PHCD, check if + * sync is in process now. If the answer is "yes", then do + * not touch PHCD bit */ + retval = langwell_otg_check_otgsc(); + if (retval) { + dev_dbg(&lnw->pdev->dev, "Skip PHCD programming..\n"); + return ; + } writeb(val | phcd, lnw->regs + CI_HOSTPC1 + 2); - else + } else writeb(val & ~phcd, lnw->regs + CI_HOSTPC1 + 2); dev_dbg(&lnw->pdev->dev, "%s <--- done\n", __func__); } +/* After drv vbus, add 2 ms delay to set PHCD */ +static void langwell_otg_phy_low_power_wait(int on) +{ + struct langwell_otg *lnw = the_transceiver; + + dev_dbg(&lnw->pdev->dev, "add 2ms delay before programing PHCD\n"); + + mdelay(2); + langwell_otg_phy_low_power(on); +} + /* Enable/Disable OTG interrupt */ static void langwell_otg_intr(int on) { @@ -598,6 +672,8 @@ static void init_hsm(void) lnw->hsm.b_bus_req = 0; /* no system error */ lnw->hsm.a_clr_err = 0; + + langwell_otg_phy_low_power_wait(1); } static void update_hsm(void) @@ -736,6 +812,7 @@ static void langwell_otg_work(struct work_struct *work) langwell_otg_chrg_vbus(0); langwell_otg_drv_vbus(0); set_host_mode(); + langwell_otg_phy_low_power(1); lnw->otg.state = OTG_STATE_A_IDLE; queue_work(lnw->qwork, &lnw->work); } else if (lnw->hsm.b_srp_res_tmout) { @@ -763,10 +840,16 @@ static void langwell_otg_work(struct work_struct *work) dev_dbg(&lnw->pdev->dev, "LS is not SE0, try again later\n"); } else { + /* clear the PHCD before start srp */ + langwell_otg_phy_low_power(0); + /* Start SRP */ langwell_otg_add_timer(b_srp_res_tmr); langwell_otg_start_srp(&lnw->otg); langwell_otg_del_timer(b_srp_res_tmr); + + /* reset PHY low power mode here */ + langwell_otg_phy_low_power_wait(1); } } break; @@ -776,6 +859,8 @@ static void langwell_otg_work(struct work_struct *work) lnw->hsm.a_srp_det = 0; langwell_otg_drv_vbus(0); langwell_otg_chrg_vbus(0); + set_host_mode(); + langwell_otg_phy_low_power(1); lnw->otg.state = OTG_STATE_A_IDLE; queue_work(lnw->qwork, &lnw->work); } else if (lnw->hsm.b_sess_vld) { @@ -795,7 +880,6 @@ static void langwell_otg_work(struct work_struct *work) langwell_otg_drv_vbus(0); langwell_otg_chrg_vbus(0); - set_host_mode(); if (lnw->client_ops) { lnw->client_ops->suspend(lnw->pdev, @@ -804,6 +888,8 @@ static void langwell_otg_work(struct work_struct *work) dev_dbg(&lnw->pdev->dev, "client driver has been removed.\n"); + set_host_mode(); + langwell_otg_phy_low_power(1); lnw->otg.state = OTG_STATE_A_IDLE; queue_work(lnw->qwork, &lnw->work); } else if (!lnw->hsm.b_sess_vld) { @@ -852,7 +938,6 @@ static void langwell_otg_work(struct work_struct *work) langwell_otg_drv_vbus(0); langwell_otg_chrg_vbus(0); - set_host_mode(); langwell_otg_HAAR(0); if (lnw->host_ops) @@ -860,6 +945,8 @@ static void langwell_otg_work(struct work_struct *work) else dev_dbg(&lnw->pdev->dev, "host driver has been removed.\n"); + set_host_mode(); + langwell_otg_phy_low_power(1); lnw->otg.state = OTG_STATE_A_IDLE; queue_work(lnw->qwork, &lnw->work); } else if (!lnw->hsm.b_sess_vld) { @@ -874,6 +961,9 @@ static void langwell_otg_work(struct work_struct *work) else dev_dbg(&lnw->pdev->dev, "host driver has been removed.\n"); + + set_client_mode(); + langwell_otg_phy_low_power(1); lnw->otg.state = OTG_STATE_B_IDLE; } else if (lnw->hsm.a_conn) { langwell_otg_del_timer(b_ase0_brst_tmr); @@ -912,12 +1002,13 @@ static void langwell_otg_work(struct work_struct *work) langwell_otg_drv_vbus(0); langwell_otg_chrg_vbus(0); - set_host_mode(); if (lnw->host_ops) lnw->host_ops->remove(lnw->pdev); else dev_dbg(&lnw->pdev->dev, "host driver has been removed.\n"); + set_host_mode(); + langwell_otg_phy_low_power(1); lnw->otg.state = OTG_STATE_A_IDLE; queue_work(lnw->qwork, &lnw->work); } else if (!lnw->hsm.b_sess_vld) { @@ -929,6 +1020,9 @@ static void langwell_otg_work(struct work_struct *work) else dev_dbg(&lnw->pdev->dev, "host driver has been removed.\n"); + + set_client_mode(); + langwell_otg_phy_low_power(1); lnw->otg.state = OTG_STATE_B_IDLE; } else if ((!lnw->hsm.b_bus_req) || (!lnw->hsm.a_conn)) { @@ -960,10 +1054,13 @@ static void langwell_otg_work(struct work_struct *work) lnw->hsm.vbus_srp_up = 0; langwell_otg_drv_vbus(0); langwell_otg_chrg_vbus(0); + set_client_mode(); + langwell_otg_phy_low_power(1); lnw->otg.state = OTG_STATE_B_IDLE; queue_work(lnw->qwork, &lnw->work); } else if (!lnw->hsm.a_bus_drop && (lnw->hsm.a_srp_det || lnw->hsm.a_bus_req)) { + langwell_otg_phy_low_power(0); langwell_otg_drv_vbus(1); lnw->hsm.a_srp_det = 1; lnw->hsm.a_wait_vrise_tmout = 0; @@ -975,12 +1072,16 @@ static void langwell_otg_work(struct work_struct *work) } else if (!lnw->hsm.a_sess_vld && lnw->hsm.vbus_srp_up) { msleep(10); + langwell_otg_phy_low_power(0); langwell_otg_drv_vbus(1); lnw->hsm.a_srp_det = 1; lnw->hsm.a_wait_vrise_tmout = 0; langwell_otg_add_timer(a_wait_vrise_tmr); lnw->otg.state = OTG_STATE_A_WAIT_VRISE; queue_work(lnw->qwork, &lnw->work); + } else if (!lnw->hsm.a_sess_vld && + !lnw->hsm.vbus_srp_up) { + langwell_otg_phy_low_power(1); } break; case OTG_STATE_A_WAIT_VRISE: @@ -989,6 +1090,9 @@ static void langwell_otg_work(struct work_struct *work) lnw->hsm.b_bus_req = 0; lnw->otg.default_a = 0; langwell_otg_drv_vbus(0); + set_client_mode(); + langwell_otg_phy_low_power_wait(1); + lnw->otg.state = OTG_STATE_B_IDLE; } else if (lnw->hsm.a_vbus_vld) { langwell_otg_del_timer(a_wait_vrise_tmr); @@ -1021,6 +1125,7 @@ static void langwell_otg_work(struct work_struct *work) lnw->otg.state = OTG_STATE_A_WAIT_BCON; } else { langwell_otg_drv_vbus(0); + langwell_otg_phy_low_power_wait(1); lnw->otg.state = OTG_STATE_A_VBUS_ERR; } } @@ -1037,6 +1142,8 @@ static void langwell_otg_work(struct work_struct *work) dev_dbg(&lnw->pdev->dev, "host driver has been removed.\n"); langwell_otg_drv_vbus(0); + set_client_mode(); + langwell_otg_phy_low_power_wait(1); lnw->otg.state = OTG_STATE_B_IDLE; queue_work(lnw->qwork, &lnw->work); } else if (!lnw->hsm.a_vbus_vld) { @@ -1048,6 +1155,7 @@ static void langwell_otg_work(struct work_struct *work) dev_dbg(&lnw->pdev->dev, "host driver has been removed.\n"); langwell_otg_drv_vbus(0); + langwell_otg_phy_low_power_wait(1); lnw->otg.state = OTG_STATE_A_VBUS_ERR; } else if (lnw->hsm.a_bus_drop || (lnw->hsm.a_wait_bcon_tmout && @@ -1117,6 +1225,8 @@ static void langwell_otg_work(struct work_struct *work) dev_dbg(&lnw->pdev->dev, "host driver has been removed.\n"); langwell_otg_drv_vbus(0); + set_client_mode(); + langwell_otg_phy_low_power_wait(1); lnw->otg.state = OTG_STATE_B_IDLE; queue_work(lnw->qwork, &lnw->work); } else if (lnw->hsm.a_bus_drop || @@ -1136,6 +1246,7 @@ static void langwell_otg_work(struct work_struct *work) dev_dbg(&lnw->pdev->dev, "host driver has been removed.\n"); langwell_otg_drv_vbus(0); + langwell_otg_phy_low_power_wait(1); lnw->otg.state = OTG_STATE_A_VBUS_ERR; } else if (lnw->otg.host->b_hnp_enable && !lnw->hsm.a_bus_req) { @@ -1179,6 +1290,8 @@ static void langwell_otg_work(struct work_struct *work) dev_dbg(&lnw->pdev->dev, "host driver has been removed.\n"); langwell_otg_drv_vbus(0); + set_client_mode(); + langwell_otg_phy_low_power(1); lnw->otg.state = OTG_STATE_B_IDLE; queue_work(lnw->qwork, &lnw->work); } else if (lnw->hsm.a_bus_req || @@ -1237,6 +1350,7 @@ static void langwell_otg_work(struct work_struct *work) dev_dbg(&lnw->pdev->dev, "host driver has been removed.\n"); langwell_otg_drv_vbus(0); + langwell_otg_phy_low_power_wait(1); lnw->otg.state = OTG_STATE_A_VBUS_ERR; } break; @@ -1252,6 +1366,8 @@ static void langwell_otg_work(struct work_struct *work) dev_dbg(&lnw->pdev->dev, "client driver has been removed.\n"); langwell_otg_drv_vbus(0); + set_client_mode(); + langwell_otg_phy_low_power_wait(1); lnw->otg.state = OTG_STATE_B_IDLE; queue_work(lnw->qwork, &lnw->work); } else if (!lnw->hsm.a_vbus_vld) { @@ -1263,6 +1379,7 @@ static void langwell_otg_work(struct work_struct *work) dev_dbg(&lnw->pdev->dev, "client driver has been removed.\n"); langwell_otg_drv_vbus(0); + langwell_otg_phy_low_power_wait(1); lnw->otg.state = OTG_STATE_A_VBUS_ERR; } else if (lnw->hsm.a_bus_drop) { langwell_otg_del_timer(b_bus_suspend_tmr); @@ -1318,6 +1435,8 @@ static void langwell_otg_work(struct work_struct *work) lnw->otg.default_a = 0; lnw->hsm.a_clr_err = 0; lnw->hsm.a_srp_det = 0; + set_client_mode(); + langwell_otg_phy_low_power(1); lnw->otg.state = OTG_STATE_B_IDLE; queue_work(lnw->qwork, &lnw->work); } else if (lnw->hsm.a_clr_err) { @@ -1327,11 +1446,17 @@ static void langwell_otg_work(struct work_struct *work) init_hsm(); if (lnw->otg.state == OTG_STATE_A_IDLE) queue_work(lnw->qwork, &lnw->work); + } else { + /* FW will clear PHCD bit when any VBus + * event detected. Reset PHCD to 1 again */ + langwell_otg_phy_low_power(1); } break; case OTG_STATE_A_WAIT_VFALL: if (lnw->hsm.id) { lnw->otg.default_a = 0; + set_client_mode(); + langwell_otg_phy_low_power(1); lnw->otg.state = OTG_STATE_B_IDLE; queue_work(lnw->qwork, &lnw->work); } else if (lnw->hsm.a_bus_req) { @@ -1343,6 +1468,7 @@ static void langwell_otg_work(struct work_struct *work) lnw->hsm.a_srp_det = 0; langwell_otg_drv_vbus(0); set_host_mode(); + langwell_otg_phy_low_power(1); lnw->otg.state = OTG_STATE_A_IDLE; } break; @@ -1735,6 +1861,20 @@ static int langwell_otg_probe(struct pci_dev *pdev, } langwell->regs = base; + if (!request_mem_region(USBCFG_ADDR, USBCFG_LEN, driver_name)) { + retval = -EBUSY; + goto err; + } + langwell->cfg_region = 1; + + /* For the SCCB.USBCFG register */ + base = ioremap_nocache(USBCFG_ADDR, USBCFG_LEN); + if (base == NULL) { + retval = -EFAULT; + goto err; + } + langwell->usbcfg = base; + if (!pdev->irq) { dev_dbg(&pdev->dev, "No IRQ.\n"); retval = -ENODEV; @@ -1833,6 +1973,10 @@ static void langwell_otg_remove(struct pci_dev *pdev) if (pdev->irq) free_irq(pdev->irq, langwell); + if (langwell->usbcfg) + iounmap(langwell->usbcfg); + if (langwell->cfg_region) + release_mem_region(USBCFG_ADDR, USBCFG_LEN); if (langwell->regs) iounmap(langwell->regs); if (langwell->region) diff --git a/include/linux/usb/langwell_otg.h b/include/linux/usb/langwell_otg.h index 7619871..64ff588 100644 --- a/include/linux/usb/langwell_otg.h +++ b/include/linux/usb/langwell_otg.h @@ -84,6 +84,12 @@ extern void langwell_otg_nsf_msg(unsigned long message); # define USBMODE_IDLE 0 # define USBMODE_DEVICE 0x2 # define USBMODE_HOST 0x3 +#define USBCFG_ADDR 0xff10801c +#define USBCFG_LEN 4 +# define USBCFG_VBUSVAL BIT(14) +# define USBCFG_AVALID BIT(13) +# define USBCFG_BVALID BIT(12) +# define USBCFG_SESEND BIT(11) #define INTR_DUMMY_MASK (USBSTS_SLI | USBSTS_URI | USBSTS_PCI) @@ -153,7 +159,9 @@ struct langwell_otg { struct otg_transceiver otg; struct otg_hsm hsm; void __iomem *regs; + void __iomem *usbcfg; /* SCCB USB config Reg */ unsigned region; + unsigned cfg_region; struct pci_driver *host_ops; struct pci_driver *client_ops; struct pci_dev *pdev; -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html