15fd54aceSGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0+
23a082ec9SNeil Zhang /*
33a082ec9SNeil Zhang * Copyright (C) 2011 Marvell International Ltd. All rights reserved.
43a082ec9SNeil Zhang * Author: Chao Xie <chao.xie@marvell.com>
53a082ec9SNeil Zhang * Neil Zhang <zhangwm@marvell.com>
63a082ec9SNeil Zhang */
73a082ec9SNeil Zhang
83a082ec9SNeil Zhang #include <linux/kernel.h>
93a082ec9SNeil Zhang #include <linux/module.h>
103a082ec9SNeil Zhang #include <linux/platform_device.h>
113a082ec9SNeil Zhang #include <linux/clk.h>
12ded017eeSKishon Vijay Abraham I #include <linux/err.h>
133a082ec9SNeil Zhang #include <linux/usb/otg.h>
147b104f89SLubomir Rintel #include <linux/usb/of.h>
153a082ec9SNeil Zhang #include <linux/platform_data/mv_usb.h>
160440fa3dSLubomir Rintel #include <linux/io.h>
170440fa3dSLubomir Rintel
180440fa3dSLubomir Rintel #include <linux/usb/hcd.h>
190440fa3dSLubomir Rintel
200440fa3dSLubomir Rintel #include "ehci.h"
213a082ec9SNeil Zhang
22a740f20dSLubomir Rintel /* registers */
23a740f20dSLubomir Rintel #define U2x_CAPREGS_OFFSET 0x100
24a740f20dSLubomir Rintel
253a082ec9SNeil Zhang #define CAPLENGTH_MASK (0xff)
263a082ec9SNeil Zhang
270440fa3dSLubomir Rintel #define hcd_to_ehci_hcd_mv(h) ((struct ehci_hcd_mv *)hcd_to_ehci(h)->priv)
283a082ec9SNeil Zhang
290440fa3dSLubomir Rintel struct ehci_hcd_mv {
303a082ec9SNeil Zhang /* Which mode does this ehci running OTG/Host ? */
313a082ec9SNeil Zhang int mode;
323a082ec9SNeil Zhang
33a740f20dSLubomir Rintel void __iomem *base;
343a082ec9SNeil Zhang void __iomem *cap_regs;
353a082ec9SNeil Zhang void __iomem *op_regs;
363a082ec9SNeil Zhang
3786753811SHeikki Krogerus struct usb_phy *otg;
38813e18b1SLubomir Rintel struct clk *clk;
393a082ec9SNeil Zhang
40a740f20dSLubomir Rintel struct phy *phy;
413a082ec9SNeil Zhang
42813e18b1SLubomir Rintel int (*set_vbus)(unsigned int vbus);
433a082ec9SNeil Zhang };
443a082ec9SNeil Zhang
mv_ehci_enable(struct ehci_hcd_mv * ehci_mv)453a082ec9SNeil Zhang static int mv_ehci_enable(struct ehci_hcd_mv *ehci_mv)
463a082ec9SNeil Zhang {
4761136a12SEvgeny Novikov int retval;
4861136a12SEvgeny Novikov
4961136a12SEvgeny Novikov retval = clk_prepare_enable(ehci_mv->clk);
5061136a12SEvgeny Novikov if (retval)
5161136a12SEvgeny Novikov return retval;
5261136a12SEvgeny Novikov
5361136a12SEvgeny Novikov retval = phy_init(ehci_mv->phy);
5461136a12SEvgeny Novikov if (retval)
5561136a12SEvgeny Novikov clk_disable_unprepare(ehci_mv->clk);
5661136a12SEvgeny Novikov
5761136a12SEvgeny Novikov return retval;
583a082ec9SNeil Zhang }
593a082ec9SNeil Zhang
mv_ehci_disable(struct ehci_hcd_mv * ehci_mv)603a082ec9SNeil Zhang static void mv_ehci_disable(struct ehci_hcd_mv *ehci_mv)
613a082ec9SNeil Zhang {
62a740f20dSLubomir Rintel phy_exit(ehci_mv->phy);
6361136a12SEvgeny Novikov clk_disable_unprepare(ehci_mv->clk);
643a082ec9SNeil Zhang }
653a082ec9SNeil Zhang
mv_ehci_reset(struct usb_hcd * hcd)663a082ec9SNeil Zhang static int mv_ehci_reset(struct usb_hcd *hcd)
673a082ec9SNeil Zhang {
683a082ec9SNeil Zhang struct device *dev = hcd->self.controller;
690440fa3dSLubomir Rintel struct ehci_hcd_mv *ehci_mv = hcd_to_ehci_hcd_mv(hcd);
707b104f89SLubomir Rintel struct ehci_hcd *ehci = hcd_to_ehci(hcd);
717b104f89SLubomir Rintel u32 status;
723a082ec9SNeil Zhang int retval;
733a082ec9SNeil Zhang
743a082ec9SNeil Zhang if (ehci_mv == NULL) {
753a082ec9SNeil Zhang dev_err(dev, "Can not find private ehci data\n");
763a082ec9SNeil Zhang return -ENODEV;
773a082ec9SNeil Zhang }
783a082ec9SNeil Zhang
793a082ec9SNeil Zhang hcd->has_tt = 1;
803a082ec9SNeil Zhang
811a49e2acSAlan Stern retval = ehci_setup(hcd);
821a49e2acSAlan Stern if (retval)
831a49e2acSAlan Stern dev_err(dev, "ehci_setup failed %d\n", retval);
841a49e2acSAlan Stern
857b104f89SLubomir Rintel if (of_usb_get_phy_mode(dev->of_node) == USBPHY_INTERFACE_MODE_HSIC) {
867b104f89SLubomir Rintel status = ehci_readl(ehci, &ehci->regs->port_status[0]);
877b104f89SLubomir Rintel status |= PORT_TEST_FORCE;
887b104f89SLubomir Rintel ehci_writel(ehci, status, &ehci->regs->port_status[0]);
897b104f89SLubomir Rintel status &= ~PORT_TEST_FORCE;
907b104f89SLubomir Rintel ehci_writel(ehci, status, &ehci->regs->port_status[0]);
917b104f89SLubomir Rintel }
927b104f89SLubomir Rintel
933a082ec9SNeil Zhang return retval;
943a082ec9SNeil Zhang }
953a082ec9SNeil Zhang
960440fa3dSLubomir Rintel static struct hc_driver __read_mostly ehci_platform_hc_driver;
973a082ec9SNeil Zhang
980440fa3dSLubomir Rintel static const struct ehci_driver_overrides platform_overrides __initconst = {
993a082ec9SNeil Zhang .reset = mv_ehci_reset,
1000440fa3dSLubomir Rintel .extra_priv_size = sizeof(struct ehci_hcd_mv),
1013a082ec9SNeil Zhang };
1023a082ec9SNeil Zhang
mv_ehci_probe(struct platform_device * pdev)1033a082ec9SNeil Zhang static int mv_ehci_probe(struct platform_device *pdev)
1043a082ec9SNeil Zhang {
105d4f09e28SJingoo Han struct mv_usb_platform_data *pdata = dev_get_platdata(&pdev->dev);
1063a082ec9SNeil Zhang struct usb_hcd *hcd;
1073a082ec9SNeil Zhang struct ehci_hcd *ehci;
1083a082ec9SNeil Zhang struct ehci_hcd_mv *ehci_mv;
1093a082ec9SNeil Zhang struct resource *r;
11009806ebaSTang Bin int retval;
1113a082ec9SNeil Zhang u32 offset;
1126351f170SLubomir Rintel u32 status;
1133a082ec9SNeil Zhang
1143a082ec9SNeil Zhang if (usb_disabled())
1153a082ec9SNeil Zhang return -ENODEV;
1163a082ec9SNeil Zhang
117aed67922SLubomir Rintel hcd = usb_create_hcd(&ehci_platform_hc_driver, &pdev->dev, dev_name(&pdev->dev));
1183a082ec9SNeil Zhang if (!hcd)
1193a082ec9SNeil Zhang return -ENOMEM;
1203a082ec9SNeil Zhang
1210440fa3dSLubomir Rintel platform_set_drvdata(pdev, hcd);
1220440fa3dSLubomir Rintel ehci_mv = hcd_to_ehci_hcd_mv(hcd);
123813e18b1SLubomir Rintel
124813e18b1SLubomir Rintel ehci_mv->mode = MV_USB_MODE_HOST;
125813e18b1SLubomir Rintel if (pdata) {
126813e18b1SLubomir Rintel ehci_mv->mode = pdata->mode;
127813e18b1SLubomir Rintel ehci_mv->set_vbus = pdata->set_vbus;
128813e18b1SLubomir Rintel }
1293a082ec9SNeil Zhang
130cf94ca49SLubomir Rintel ehci_mv->phy = devm_phy_optional_get(&pdev->dev, "usb");
131a740f20dSLubomir Rintel if (IS_ERR(ehci_mv->phy)) {
132a740f20dSLubomir Rintel retval = PTR_ERR(ehci_mv->phy);
133a740f20dSLubomir Rintel if (retval != -EPROBE_DEFER)
134a740f20dSLubomir Rintel dev_err(&pdev->dev, "Failed to get phy.\n");
135a740f20dSLubomir Rintel goto err_put_hcd;
136a740f20dSLubomir Rintel }
137a740f20dSLubomir Rintel
138b7e159c2SChao Xie ehci_mv->clk = devm_clk_get(&pdev->dev, NULL);
139b7e159c2SChao Xie if (IS_ERR(ehci_mv->clk)) {
140b7e159c2SChao Xie dev_err(&pdev->dev, "error getting clock\n");
141b7e159c2SChao Xie retval = PTR_ERR(ehci_mv->clk);
142970691ebSJingoo Han goto err_put_hcd;
1433a082ec9SNeil Zhang }
1443a082ec9SNeil Zhang
145*d64d7f91SYangtao Li ehci_mv->base = devm_platform_get_and_ioremap_resource(pdev, 0, &r);
146a740f20dSLubomir Rintel if (IS_ERR(ehci_mv->base)) {
147a740f20dSLubomir Rintel retval = PTR_ERR(ehci_mv->base);
148970691ebSJingoo Han goto err_put_hcd;
1493a082ec9SNeil Zhang }
1503a082ec9SNeil Zhang
1513a082ec9SNeil Zhang retval = mv_ehci_enable(ehci_mv);
1523a082ec9SNeil Zhang if (retval) {
1533a082ec9SNeil Zhang dev_err(&pdev->dev, "init phy error %d\n", retval);
154970691ebSJingoo Han goto err_put_hcd;
1553a082ec9SNeil Zhang }
1563a082ec9SNeil Zhang
157a740f20dSLubomir Rintel ehci_mv->cap_regs =
158a740f20dSLubomir Rintel (void __iomem *) ((unsigned long) ehci_mv->base + U2x_CAPREGS_OFFSET);
1593a082ec9SNeil Zhang offset = readl(ehci_mv->cap_regs) & CAPLENGTH_MASK;
1603a082ec9SNeil Zhang ehci_mv->op_regs =
1613a082ec9SNeil Zhang (void __iomem *) ((unsigned long) ehci_mv->cap_regs + offset);
1623a082ec9SNeil Zhang
1633a082ec9SNeil Zhang hcd->rsrc_start = r->start;
16407cd29d7SPaul Vlase hcd->rsrc_len = resource_size(r);
1653a082ec9SNeil Zhang hcd->regs = ehci_mv->op_regs;
1663a082ec9SNeil Zhang
167a7f40c23SColin Ian King retval = platform_get_irq(pdev, 0);
168a7f40c23SColin Ian King if (retval < 0)
1693a082ec9SNeil Zhang goto err_disable_clk;
170a7f40c23SColin Ian King hcd->irq = retval;
1713a082ec9SNeil Zhang
1723a082ec9SNeil Zhang ehci = hcd_to_ehci(hcd);
17360826786SKrzysztof Kozlowski ehci->caps = (struct ehci_caps __iomem *) ehci_mv->cap_regs;
1743a082ec9SNeil Zhang
1753a082ec9SNeil Zhang if (ehci_mv->mode == MV_USB_MODE_OTG) {
17635b55563SJulia Lawall ehci_mv->otg = devm_usb_get_phy(&pdev->dev, USB_PHY_TYPE_USB2);
1776f3ed4ecSFelipe Balbi if (IS_ERR(ehci_mv->otg)) {
1786f3ed4ecSFelipe Balbi retval = PTR_ERR(ehci_mv->otg);
1796f3ed4ecSFelipe Balbi
1806f3ed4ecSFelipe Balbi if (retval == -ENXIO)
1816f3ed4ecSFelipe Balbi dev_info(&pdev->dev, "MV_USB_MODE_OTG "
1826f3ed4ecSFelipe Balbi "must have CONFIG_USB_PHY enabled\n");
1836f3ed4ecSFelipe Balbi else
1843a082ec9SNeil Zhang dev_err(&pdev->dev,
1853a082ec9SNeil Zhang "unable to find transceiver\n");
1863a082ec9SNeil Zhang goto err_disable_clk;
1873a082ec9SNeil Zhang }
1883a082ec9SNeil Zhang
1896e13c650SHeikki Krogerus retval = otg_set_host(ehci_mv->otg->otg, &hcd->self);
1903a082ec9SNeil Zhang if (retval < 0) {
1913a082ec9SNeil Zhang dev_err(&pdev->dev,
1923a082ec9SNeil Zhang "unable to register with transceiver\n");
1933a082ec9SNeil Zhang retval = -ENODEV;
19435b55563SJulia Lawall goto err_disable_clk;
1953a082ec9SNeil Zhang }
1963a082ec9SNeil Zhang /* otg will enable clock before use as host */
1973a082ec9SNeil Zhang mv_ehci_disable(ehci_mv);
1983a082ec9SNeil Zhang } else {
199813e18b1SLubomir Rintel if (ehci_mv->set_vbus)
200813e18b1SLubomir Rintel ehci_mv->set_vbus(1);
2013a082ec9SNeil Zhang
2023a082ec9SNeil Zhang retval = usb_add_hcd(hcd, hcd->irq, IRQF_SHARED);
2033a082ec9SNeil Zhang if (retval) {
2043a082ec9SNeil Zhang dev_err(&pdev->dev,
2053a082ec9SNeil Zhang "failed to add hcd with err %d\n", retval);
2063a082ec9SNeil Zhang goto err_set_vbus;
2073a082ec9SNeil Zhang }
2083c9740a1SPeter Chen device_wakeup_enable(hcd->self.controller);
2093a082ec9SNeil Zhang }
2103a082ec9SNeil Zhang
2116351f170SLubomir Rintel if (of_usb_get_phy_mode(pdev->dev.of_node) == USBPHY_INTERFACE_MODE_HSIC) {
2126351f170SLubomir Rintel status = ehci_readl(ehci, &ehci->regs->port_status[0]);
2136351f170SLubomir Rintel /* These "reserved" bits actually enable HSIC mode. */
2146351f170SLubomir Rintel status |= BIT(25);
2156351f170SLubomir Rintel status &= ~GENMASK(31, 30);
2166351f170SLubomir Rintel ehci_writel(ehci, status, &ehci->regs->port_status[0]);
2176351f170SLubomir Rintel }
2186351f170SLubomir Rintel
2193a082ec9SNeil Zhang dev_info(&pdev->dev,
2203a082ec9SNeil Zhang "successful find EHCI device with regs 0x%p irq %d"
2213a082ec9SNeil Zhang " working in %s mode\n", hcd->regs, hcd->irq,
2223a082ec9SNeil Zhang ehci_mv->mode == MV_USB_MODE_OTG ? "OTG" : "Host");
2233a082ec9SNeil Zhang
2243a082ec9SNeil Zhang return 0;
2253a082ec9SNeil Zhang
2263a082ec9SNeil Zhang err_set_vbus:
227813e18b1SLubomir Rintel if (ehci_mv->set_vbus)
228813e18b1SLubomir Rintel ehci_mv->set_vbus(0);
2293a082ec9SNeil Zhang err_disable_clk:
2303a082ec9SNeil Zhang mv_ehci_disable(ehci_mv);
2313a082ec9SNeil Zhang err_put_hcd:
2323a082ec9SNeil Zhang usb_put_hcd(hcd);
2333a082ec9SNeil Zhang
2343a082ec9SNeil Zhang return retval;
2353a082ec9SNeil Zhang }
2363a082ec9SNeil Zhang
mv_ehci_remove(struct platform_device * pdev)237450955d7SUwe Kleine-König static void mv_ehci_remove(struct platform_device *pdev)
2383a082ec9SNeil Zhang {
2390440fa3dSLubomir Rintel struct usb_hcd *hcd = platform_get_drvdata(pdev);
2400440fa3dSLubomir Rintel struct ehci_hcd_mv *ehci_mv = hcd_to_ehci_hcd_mv(hcd);
2413a082ec9SNeil Zhang
2423a082ec9SNeil Zhang if (hcd->rh_registered)
2433a082ec9SNeil Zhang usb_remove_hcd(hcd);
2443a082ec9SNeil Zhang
24535b55563SJulia Lawall if (!IS_ERR_OR_NULL(ehci_mv->otg))
2466e13c650SHeikki Krogerus otg_set_host(ehci_mv->otg->otg, NULL);
2473a082ec9SNeil Zhang
2483a082ec9SNeil Zhang if (ehci_mv->mode == MV_USB_MODE_HOST) {
249813e18b1SLubomir Rintel if (ehci_mv->set_vbus)
250813e18b1SLubomir Rintel ehci_mv->set_vbus(0);
2513a082ec9SNeil Zhang
2523a082ec9SNeil Zhang mv_ehci_disable(ehci_mv);
2533a082ec9SNeil Zhang }
2543a082ec9SNeil Zhang
2553a082ec9SNeil Zhang usb_put_hcd(hcd);
2563a082ec9SNeil Zhang }
2573a082ec9SNeil Zhang
2583a082ec9SNeil Zhang static const struct platform_device_id ehci_id_table[] = {
25992f98352SLubomir Rintel {"pxa-u2oehci", 0},
26092f98352SLubomir Rintel {"pxa-sph", 0},
2613a082ec9SNeil Zhang {},
2623a082ec9SNeil Zhang };
2633a082ec9SNeil Zhang
mv_ehci_shutdown(struct platform_device * pdev)2643a082ec9SNeil Zhang static void mv_ehci_shutdown(struct platform_device *pdev)
2653a082ec9SNeil Zhang {
2660440fa3dSLubomir Rintel struct usb_hcd *hcd = platform_get_drvdata(pdev);
2673a082ec9SNeil Zhang
2683a082ec9SNeil Zhang if (!hcd->rh_registered)
2693a082ec9SNeil Zhang return;
2703a082ec9SNeil Zhang
2713a082ec9SNeil Zhang if (hcd->driver->shutdown)
2723a082ec9SNeil Zhang hcd->driver->shutdown(hcd);
2733a082ec9SNeil Zhang }
2743a082ec9SNeil Zhang
275813e18b1SLubomir Rintel static const struct of_device_id ehci_mv_dt_ids[] = {
276813e18b1SLubomir Rintel { .compatible = "marvell,pxau2o-ehci", },
277813e18b1SLubomir Rintel {},
278813e18b1SLubomir Rintel };
279813e18b1SLubomir Rintel
2803a082ec9SNeil Zhang static struct platform_driver ehci_mv_driver = {
2813a082ec9SNeil Zhang .probe = mv_ehci_probe,
282450955d7SUwe Kleine-König .remove_new = mv_ehci_remove,
2833a082ec9SNeil Zhang .shutdown = mv_ehci_shutdown,
2843a082ec9SNeil Zhang .driver = {
2853a082ec9SNeil Zhang .name = "mv-ehci",
2863a082ec9SNeil Zhang .bus = &platform_bus_type,
287813e18b1SLubomir Rintel .of_match_table = ehci_mv_dt_ids,
2883a082ec9SNeil Zhang },
2893a082ec9SNeil Zhang .id_table = ehci_id_table,
2903a082ec9SNeil Zhang };
2910440fa3dSLubomir Rintel
ehci_platform_init(void)2920440fa3dSLubomir Rintel static int __init ehci_platform_init(void)
2930440fa3dSLubomir Rintel {
2940440fa3dSLubomir Rintel if (usb_disabled())
2950440fa3dSLubomir Rintel return -ENODEV;
2960440fa3dSLubomir Rintel
2970440fa3dSLubomir Rintel ehci_init_driver(&ehci_platform_hc_driver, &platform_overrides);
2980440fa3dSLubomir Rintel return platform_driver_register(&ehci_mv_driver);
2990440fa3dSLubomir Rintel }
3000440fa3dSLubomir Rintel module_init(ehci_platform_init);
3010440fa3dSLubomir Rintel
ehci_platform_cleanup(void)3020440fa3dSLubomir Rintel static void __exit ehci_platform_cleanup(void)
3030440fa3dSLubomir Rintel {
3040440fa3dSLubomir Rintel platform_driver_unregister(&ehci_mv_driver);
3050440fa3dSLubomir Rintel }
3060440fa3dSLubomir Rintel module_exit(ehci_platform_cleanup);
3070440fa3dSLubomir Rintel
3080440fa3dSLubomir Rintel MODULE_DESCRIPTION("Marvell EHCI driver");
3090440fa3dSLubomir Rintel MODULE_AUTHOR("Chao Xie <chao.xie@marvell.com>");
3100440fa3dSLubomir Rintel MODULE_AUTHOR("Neil Zhang <zhangwm@marvell.com>");
3110440fa3dSLubomir Rintel MODULE_ALIAS("mv-ehci");
3120440fa3dSLubomir Rintel MODULE_LICENSE("GPL");
31370d0ba4cSLubomir Rintel MODULE_DEVICE_TABLE(of, ehci_mv_dt_ids);
314