13a082ec9SNeil Zhang /* 23a082ec9SNeil Zhang * Copyright (C) 2011 Marvell International Ltd. All rights reserved. 33a082ec9SNeil Zhang * Author: Chao Xie <chao.xie@marvell.com> 43a082ec9SNeil Zhang * Neil Zhang <zhangwm@marvell.com> 53a082ec9SNeil Zhang * 63a082ec9SNeil Zhang * This program is free software; you can redistribute it and/or modify it 73a082ec9SNeil Zhang * under the terms of the GNU General Public License as published by the 83a082ec9SNeil Zhang * Free Software Foundation; either version 2 of the License, or (at your 93a082ec9SNeil Zhang * option) any later version. 103a082ec9SNeil Zhang */ 113a082ec9SNeil Zhang 123a082ec9SNeil Zhang #include <linux/kernel.h> 133a082ec9SNeil Zhang #include <linux/module.h> 143a082ec9SNeil Zhang #include <linux/platform_device.h> 153a082ec9SNeil Zhang #include <linux/clk.h> 16ded017eeSKishon Vijay Abraham I #include <linux/err.h> 173a082ec9SNeil Zhang #include <linux/usb/otg.h> 183a082ec9SNeil Zhang #include <linux/platform_data/mv_usb.h> 193a082ec9SNeil Zhang 203a082ec9SNeil Zhang #define CAPLENGTH_MASK (0xff) 213a082ec9SNeil Zhang 223a082ec9SNeil Zhang struct ehci_hcd_mv { 233a082ec9SNeil Zhang struct usb_hcd *hcd; 243a082ec9SNeil Zhang 253a082ec9SNeil Zhang /* Which mode does this ehci running OTG/Host ? */ 263a082ec9SNeil Zhang int mode; 273a082ec9SNeil Zhang 283a082ec9SNeil Zhang void __iomem *phy_regs; 293a082ec9SNeil Zhang void __iomem *cap_regs; 303a082ec9SNeil Zhang void __iomem *op_regs; 313a082ec9SNeil Zhang 3286753811SHeikki Krogerus struct usb_phy *otg; 333a082ec9SNeil Zhang 343a082ec9SNeil Zhang struct mv_usb_platform_data *pdata; 353a082ec9SNeil Zhang 36b7e159c2SChao Xie struct clk *clk; 373a082ec9SNeil Zhang }; 383a082ec9SNeil Zhang 393a082ec9SNeil Zhang static void ehci_clock_enable(struct ehci_hcd_mv *ehci_mv) 403a082ec9SNeil Zhang { 41b7e159c2SChao Xie clk_prepare_enable(ehci_mv->clk); 423a082ec9SNeil Zhang } 433a082ec9SNeil Zhang 443a082ec9SNeil Zhang static void ehci_clock_disable(struct ehci_hcd_mv *ehci_mv) 453a082ec9SNeil Zhang { 46b7e159c2SChao Xie clk_disable_unprepare(ehci_mv->clk); 473a082ec9SNeil Zhang } 483a082ec9SNeil Zhang 493a082ec9SNeil Zhang static int mv_ehci_enable(struct ehci_hcd_mv *ehci_mv) 503a082ec9SNeil Zhang { 513a082ec9SNeil Zhang int retval; 523a082ec9SNeil Zhang 533a082ec9SNeil Zhang ehci_clock_enable(ehci_mv); 543a082ec9SNeil Zhang if (ehci_mv->pdata->phy_init) { 553a082ec9SNeil Zhang retval = ehci_mv->pdata->phy_init(ehci_mv->phy_regs); 563a082ec9SNeil Zhang if (retval) 573a082ec9SNeil Zhang return retval; 583a082ec9SNeil Zhang } 593a082ec9SNeil Zhang 603a082ec9SNeil Zhang return 0; 613a082ec9SNeil Zhang } 623a082ec9SNeil Zhang 633a082ec9SNeil Zhang static void mv_ehci_disable(struct ehci_hcd_mv *ehci_mv) 643a082ec9SNeil Zhang { 653a082ec9SNeil Zhang if (ehci_mv->pdata->phy_deinit) 663a082ec9SNeil Zhang ehci_mv->pdata->phy_deinit(ehci_mv->phy_regs); 673a082ec9SNeil Zhang ehci_clock_disable(ehci_mv); 683a082ec9SNeil Zhang } 693a082ec9SNeil Zhang 703a082ec9SNeil Zhang static int mv_ehci_reset(struct usb_hcd *hcd) 713a082ec9SNeil Zhang { 723a082ec9SNeil Zhang struct device *dev = hcd->self.controller; 733a082ec9SNeil Zhang struct ehci_hcd_mv *ehci_mv = dev_get_drvdata(dev); 743a082ec9SNeil Zhang int retval; 753a082ec9SNeil Zhang 763a082ec9SNeil Zhang if (ehci_mv == NULL) { 773a082ec9SNeil Zhang dev_err(dev, "Can not find private ehci data\n"); 783a082ec9SNeil Zhang return -ENODEV; 793a082ec9SNeil Zhang } 803a082ec9SNeil Zhang 813a082ec9SNeil Zhang hcd->has_tt = 1; 823a082ec9SNeil Zhang 831a49e2acSAlan Stern retval = ehci_setup(hcd); 841a49e2acSAlan Stern if (retval) 851a49e2acSAlan Stern dev_err(dev, "ehci_setup failed %d\n", retval); 861a49e2acSAlan Stern 873a082ec9SNeil Zhang return retval; 883a082ec9SNeil Zhang } 893a082ec9SNeil Zhang 903a082ec9SNeil Zhang static const struct hc_driver mv_ehci_hc_driver = { 913a082ec9SNeil Zhang .description = hcd_name, 923a082ec9SNeil Zhang .product_desc = "Marvell EHCI", 933a082ec9SNeil Zhang .hcd_priv_size = sizeof(struct ehci_hcd), 943a082ec9SNeil Zhang 953a082ec9SNeil Zhang /* 963a082ec9SNeil Zhang * generic hardware linkage 973a082ec9SNeil Zhang */ 983a082ec9SNeil Zhang .irq = ehci_irq, 99c04ee4b1SGreg Kroah-Hartman .flags = HCD_MEMORY | HCD_USB2 | HCD_BH, 1003a082ec9SNeil Zhang 1013a082ec9SNeil Zhang /* 1023a082ec9SNeil Zhang * basic lifecycle operations 1033a082ec9SNeil Zhang */ 1043a082ec9SNeil Zhang .reset = mv_ehci_reset, 1053a082ec9SNeil Zhang .start = ehci_run, 1063a082ec9SNeil Zhang .stop = ehci_stop, 1073a082ec9SNeil Zhang .shutdown = ehci_shutdown, 1083a082ec9SNeil Zhang 1093a082ec9SNeil Zhang /* 1103a082ec9SNeil Zhang * managing i/o requests and associated device resources 1113a082ec9SNeil Zhang */ 1123a082ec9SNeil Zhang .urb_enqueue = ehci_urb_enqueue, 1133a082ec9SNeil Zhang .urb_dequeue = ehci_urb_dequeue, 1143a082ec9SNeil Zhang .endpoint_disable = ehci_endpoint_disable, 1153a082ec9SNeil Zhang .endpoint_reset = ehci_endpoint_reset, 1163a082ec9SNeil Zhang .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, 1173a082ec9SNeil Zhang 1183a082ec9SNeil Zhang /* 1193a082ec9SNeil Zhang * scheduling support 1203a082ec9SNeil Zhang */ 1213a082ec9SNeil Zhang .get_frame_number = ehci_get_frame, 1223a082ec9SNeil Zhang 1233a082ec9SNeil Zhang /* 1243a082ec9SNeil Zhang * root hub support 1253a082ec9SNeil Zhang */ 1263a082ec9SNeil Zhang .hub_status_data = ehci_hub_status_data, 1273a082ec9SNeil Zhang .hub_control = ehci_hub_control, 1283a082ec9SNeil Zhang .bus_suspend = ehci_bus_suspend, 1293a082ec9SNeil Zhang .bus_resume = ehci_bus_resume, 1303a082ec9SNeil Zhang }; 1313a082ec9SNeil Zhang 1323a082ec9SNeil Zhang static int mv_ehci_probe(struct platform_device *pdev) 1333a082ec9SNeil Zhang { 134d4f09e28SJingoo Han struct mv_usb_platform_data *pdata = dev_get_platdata(&pdev->dev); 1353a082ec9SNeil Zhang struct usb_hcd *hcd; 1363a082ec9SNeil Zhang struct ehci_hcd *ehci; 1373a082ec9SNeil Zhang struct ehci_hcd_mv *ehci_mv; 1383a082ec9SNeil Zhang struct resource *r; 139b7e159c2SChao Xie int retval = -ENODEV; 1403a082ec9SNeil Zhang u32 offset; 1413a082ec9SNeil Zhang 1423a082ec9SNeil Zhang if (!pdata) { 1433a082ec9SNeil Zhang dev_err(&pdev->dev, "missing platform_data\n"); 1443a082ec9SNeil Zhang return -ENODEV; 1453a082ec9SNeil Zhang } 1463a082ec9SNeil Zhang 1473a082ec9SNeil Zhang if (usb_disabled()) 1483a082ec9SNeil Zhang return -ENODEV; 1493a082ec9SNeil Zhang 1503a082ec9SNeil Zhang hcd = usb_create_hcd(&mv_ehci_hc_driver, &pdev->dev, "mv ehci"); 1513a082ec9SNeil Zhang if (!hcd) 1523a082ec9SNeil Zhang return -ENOMEM; 1533a082ec9SNeil Zhang 154b7e159c2SChao Xie ehci_mv = devm_kzalloc(&pdev->dev, sizeof(*ehci_mv), GFP_KERNEL); 1553a082ec9SNeil Zhang if (ehci_mv == NULL) { 1563a082ec9SNeil Zhang dev_err(&pdev->dev, "cannot allocate ehci_hcd_mv\n"); 1573a082ec9SNeil Zhang retval = -ENOMEM; 1583a082ec9SNeil Zhang goto err_put_hcd; 1593a082ec9SNeil Zhang } 1603a082ec9SNeil Zhang 1613a082ec9SNeil Zhang platform_set_drvdata(pdev, ehci_mv); 1623a082ec9SNeil Zhang ehci_mv->pdata = pdata; 1633a082ec9SNeil Zhang ehci_mv->hcd = hcd; 1643a082ec9SNeil Zhang 165b7e159c2SChao Xie ehci_mv->clk = devm_clk_get(&pdev->dev, NULL); 166b7e159c2SChao Xie if (IS_ERR(ehci_mv->clk)) { 167b7e159c2SChao Xie dev_err(&pdev->dev, "error getting clock\n"); 168b7e159c2SChao Xie retval = PTR_ERR(ehci_mv->clk); 169970691ebSJingoo Han goto err_put_hcd; 1703a082ec9SNeil Zhang } 1713a082ec9SNeil Zhang 1723a082ec9SNeil Zhang r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phyregs"); 1733a082ec9SNeil Zhang if (r == NULL) { 1743a082ec9SNeil Zhang dev_err(&pdev->dev, "no phy I/O memory resource defined\n"); 1753a082ec9SNeil Zhang retval = -ENODEV; 176970691ebSJingoo Han goto err_put_hcd; 1773a082ec9SNeil Zhang } 1783a082ec9SNeil Zhang 17935b55563SJulia Lawall ehci_mv->phy_regs = devm_ioremap(&pdev->dev, r->start, 18035b55563SJulia Lawall resource_size(r)); 1812e30d14fSFengguang Wu if (!ehci_mv->phy_regs) { 1823a082ec9SNeil Zhang dev_err(&pdev->dev, "failed to map phy I/O memory\n"); 1833a082ec9SNeil Zhang retval = -EFAULT; 184970691ebSJingoo Han goto err_put_hcd; 1853a082ec9SNeil Zhang } 1863a082ec9SNeil Zhang 1873a082ec9SNeil Zhang r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "capregs"); 1883a082ec9SNeil Zhang if (!r) { 1893a082ec9SNeil Zhang dev_err(&pdev->dev, "no I/O memory resource defined\n"); 1903a082ec9SNeil Zhang retval = -ENODEV; 191970691ebSJingoo Han goto err_put_hcd; 1923a082ec9SNeil Zhang } 1933a082ec9SNeil Zhang 19435b55563SJulia Lawall ehci_mv->cap_regs = devm_ioremap(&pdev->dev, r->start, 19535b55563SJulia Lawall resource_size(r)); 1963a082ec9SNeil Zhang if (ehci_mv->cap_regs == NULL) { 1973a082ec9SNeil Zhang dev_err(&pdev->dev, "failed to map I/O memory\n"); 1983a082ec9SNeil Zhang retval = -EFAULT; 199970691ebSJingoo Han goto err_put_hcd; 2003a082ec9SNeil Zhang } 2013a082ec9SNeil Zhang 2023a082ec9SNeil Zhang retval = mv_ehci_enable(ehci_mv); 2033a082ec9SNeil Zhang if (retval) { 2043a082ec9SNeil Zhang dev_err(&pdev->dev, "init phy error %d\n", retval); 205970691ebSJingoo Han goto err_put_hcd; 2063a082ec9SNeil Zhang } 2073a082ec9SNeil Zhang 2083a082ec9SNeil Zhang offset = readl(ehci_mv->cap_regs) & CAPLENGTH_MASK; 2093a082ec9SNeil Zhang ehci_mv->op_regs = 2103a082ec9SNeil Zhang (void __iomem *) ((unsigned long) ehci_mv->cap_regs + offset); 2113a082ec9SNeil Zhang 2123a082ec9SNeil Zhang hcd->rsrc_start = r->start; 21307cd29d7SPaul Vlase hcd->rsrc_len = resource_size(r); 2143a082ec9SNeil Zhang hcd->regs = ehci_mv->op_regs; 2153a082ec9SNeil Zhang 2163a082ec9SNeil Zhang hcd->irq = platform_get_irq(pdev, 0); 2173a082ec9SNeil Zhang if (!hcd->irq) { 2183a082ec9SNeil Zhang dev_err(&pdev->dev, "Cannot get irq."); 2193a082ec9SNeil Zhang retval = -ENODEV; 2203a082ec9SNeil Zhang goto err_disable_clk; 2213a082ec9SNeil Zhang } 2223a082ec9SNeil Zhang 2233a082ec9SNeil Zhang ehci = hcd_to_ehci(hcd); 2243a082ec9SNeil Zhang ehci->caps = (struct ehci_caps *) ehci_mv->cap_regs; 2253a082ec9SNeil Zhang 2263a082ec9SNeil Zhang ehci_mv->mode = pdata->mode; 2273a082ec9SNeil Zhang if (ehci_mv->mode == MV_USB_MODE_OTG) { 22835b55563SJulia Lawall ehci_mv->otg = devm_usb_get_phy(&pdev->dev, USB_PHY_TYPE_USB2); 2296f3ed4ecSFelipe Balbi if (IS_ERR(ehci_mv->otg)) { 2306f3ed4ecSFelipe Balbi retval = PTR_ERR(ehci_mv->otg); 2316f3ed4ecSFelipe Balbi 2326f3ed4ecSFelipe Balbi if (retval == -ENXIO) 2336f3ed4ecSFelipe Balbi dev_info(&pdev->dev, "MV_USB_MODE_OTG " 2346f3ed4ecSFelipe Balbi "must have CONFIG_USB_PHY enabled\n"); 2356f3ed4ecSFelipe Balbi else 2363a082ec9SNeil Zhang dev_err(&pdev->dev, 2373a082ec9SNeil Zhang "unable to find transceiver\n"); 2383a082ec9SNeil Zhang goto err_disable_clk; 2393a082ec9SNeil Zhang } 2403a082ec9SNeil Zhang 2416e13c650SHeikki Krogerus retval = otg_set_host(ehci_mv->otg->otg, &hcd->self); 2423a082ec9SNeil Zhang if (retval < 0) { 2433a082ec9SNeil Zhang dev_err(&pdev->dev, 2443a082ec9SNeil Zhang "unable to register with transceiver\n"); 2453a082ec9SNeil Zhang retval = -ENODEV; 24635b55563SJulia Lawall goto err_disable_clk; 2473a082ec9SNeil Zhang } 2483a082ec9SNeil Zhang /* otg will enable clock before use as host */ 2493a082ec9SNeil Zhang mv_ehci_disable(ehci_mv); 2503a082ec9SNeil Zhang } else { 2513a082ec9SNeil Zhang if (pdata->set_vbus) 2523a082ec9SNeil Zhang pdata->set_vbus(1); 2533a082ec9SNeil Zhang 2543a082ec9SNeil Zhang retval = usb_add_hcd(hcd, hcd->irq, IRQF_SHARED); 2553a082ec9SNeil Zhang if (retval) { 2563a082ec9SNeil Zhang dev_err(&pdev->dev, 2573a082ec9SNeil Zhang "failed to add hcd with err %d\n", retval); 2583a082ec9SNeil Zhang goto err_set_vbus; 2593a082ec9SNeil Zhang } 260*3c9740a1SPeter Chen device_wakeup_enable(hcd->self.controller); 2613a082ec9SNeil Zhang } 2623a082ec9SNeil Zhang 2633a082ec9SNeil Zhang if (pdata->private_init) 2643a082ec9SNeil Zhang pdata->private_init(ehci_mv->op_regs, ehci_mv->phy_regs); 2653a082ec9SNeil Zhang 2663a082ec9SNeil Zhang dev_info(&pdev->dev, 2673a082ec9SNeil Zhang "successful find EHCI device with regs 0x%p irq %d" 2683a082ec9SNeil Zhang " working in %s mode\n", hcd->regs, hcd->irq, 2693a082ec9SNeil Zhang ehci_mv->mode == MV_USB_MODE_OTG ? "OTG" : "Host"); 2703a082ec9SNeil Zhang 2713a082ec9SNeil Zhang return 0; 2723a082ec9SNeil Zhang 2733a082ec9SNeil Zhang err_set_vbus: 2743a082ec9SNeil Zhang if (pdata->set_vbus) 2753a082ec9SNeil Zhang pdata->set_vbus(0); 2763a082ec9SNeil Zhang err_disable_clk: 2773a082ec9SNeil Zhang mv_ehci_disable(ehci_mv); 2783a082ec9SNeil Zhang err_put_hcd: 2793a082ec9SNeil Zhang usb_put_hcd(hcd); 2803a082ec9SNeil Zhang 2813a082ec9SNeil Zhang return retval; 2823a082ec9SNeil Zhang } 2833a082ec9SNeil Zhang 2843a082ec9SNeil Zhang static int mv_ehci_remove(struct platform_device *pdev) 2853a082ec9SNeil Zhang { 2863a082ec9SNeil Zhang struct ehci_hcd_mv *ehci_mv = platform_get_drvdata(pdev); 2873a082ec9SNeil Zhang struct usb_hcd *hcd = ehci_mv->hcd; 2883a082ec9SNeil Zhang 2893a082ec9SNeil Zhang if (hcd->rh_registered) 2903a082ec9SNeil Zhang usb_remove_hcd(hcd); 2913a082ec9SNeil Zhang 29235b55563SJulia Lawall if (!IS_ERR_OR_NULL(ehci_mv->otg)) 2936e13c650SHeikki Krogerus otg_set_host(ehci_mv->otg->otg, NULL); 2943a082ec9SNeil Zhang 2953a082ec9SNeil Zhang if (ehci_mv->mode == MV_USB_MODE_HOST) { 2963a082ec9SNeil Zhang if (ehci_mv->pdata->set_vbus) 2973a082ec9SNeil Zhang ehci_mv->pdata->set_vbus(0); 2983a082ec9SNeil Zhang 2993a082ec9SNeil Zhang mv_ehci_disable(ehci_mv); 3003a082ec9SNeil Zhang } 3013a082ec9SNeil Zhang 3023a082ec9SNeil Zhang usb_put_hcd(hcd); 3033a082ec9SNeil Zhang 3043a082ec9SNeil Zhang return 0; 3053a082ec9SNeil Zhang } 3063a082ec9SNeil Zhang 3073a082ec9SNeil Zhang MODULE_ALIAS("mv-ehci"); 3083a082ec9SNeil Zhang 3093a082ec9SNeil Zhang static const struct platform_device_id ehci_id_table[] = { 3103a082ec9SNeil Zhang {"pxa-u2oehci", PXA_U2OEHCI}, 3113a082ec9SNeil Zhang {"pxa-sph", PXA_SPH}, 3123a082ec9SNeil Zhang {"mmp3-hsic", MMP3_HSIC}, 3133a082ec9SNeil Zhang {"mmp3-fsic", MMP3_FSIC}, 3143a082ec9SNeil Zhang {}, 3153a082ec9SNeil Zhang }; 3163a082ec9SNeil Zhang 3173a082ec9SNeil Zhang static void mv_ehci_shutdown(struct platform_device *pdev) 3183a082ec9SNeil Zhang { 3193a082ec9SNeil Zhang struct ehci_hcd_mv *ehci_mv = platform_get_drvdata(pdev); 3203a082ec9SNeil Zhang struct usb_hcd *hcd = ehci_mv->hcd; 3213a082ec9SNeil Zhang 3223a082ec9SNeil Zhang if (!hcd->rh_registered) 3233a082ec9SNeil Zhang return; 3243a082ec9SNeil Zhang 3253a082ec9SNeil Zhang if (hcd->driver->shutdown) 3263a082ec9SNeil Zhang hcd->driver->shutdown(hcd); 3273a082ec9SNeil Zhang } 3283a082ec9SNeil Zhang 3293a082ec9SNeil Zhang static struct platform_driver ehci_mv_driver = { 3303a082ec9SNeil Zhang .probe = mv_ehci_probe, 3313a082ec9SNeil Zhang .remove = mv_ehci_remove, 3323a082ec9SNeil Zhang .shutdown = mv_ehci_shutdown, 3333a082ec9SNeil Zhang .driver = { 3343a082ec9SNeil Zhang .name = "mv-ehci", 3353a082ec9SNeil Zhang .bus = &platform_bus_type, 3363a082ec9SNeil Zhang }, 3373a082ec9SNeil Zhang .id_table = ehci_id_table, 3383a082ec9SNeil Zhang }; 339