ehci-mv.c (7473B)
1// SPDX-License-Identifier: GPL-2.0+ 2/* 3 * Copyright (C) 2011 Marvell International Ltd. All rights reserved. 4 * Author: Chao Xie <chao.xie@marvell.com> 5 * Neil Zhang <zhangwm@marvell.com> 6 */ 7 8#include <linux/kernel.h> 9#include <linux/module.h> 10#include <linux/platform_device.h> 11#include <linux/clk.h> 12#include <linux/err.h> 13#include <linux/usb/otg.h> 14#include <linux/usb/of.h> 15#include <linux/platform_data/mv_usb.h> 16#include <linux/io.h> 17 18#include <linux/usb/hcd.h> 19 20#include "ehci.h" 21 22/* registers */ 23#define U2x_CAPREGS_OFFSET 0x100 24 25#define CAPLENGTH_MASK (0xff) 26 27#define hcd_to_ehci_hcd_mv(h) ((struct ehci_hcd_mv *)hcd_to_ehci(h)->priv) 28 29struct ehci_hcd_mv { 30 /* Which mode does this ehci running OTG/Host ? */ 31 int mode; 32 33 void __iomem *base; 34 void __iomem *cap_regs; 35 void __iomem *op_regs; 36 37 struct usb_phy *otg; 38 struct clk *clk; 39 40 struct phy *phy; 41 42 int (*set_vbus)(unsigned int vbus); 43}; 44 45static int mv_ehci_enable(struct ehci_hcd_mv *ehci_mv) 46{ 47 int retval; 48 49 retval = clk_prepare_enable(ehci_mv->clk); 50 if (retval) 51 return retval; 52 53 retval = phy_init(ehci_mv->phy); 54 if (retval) 55 clk_disable_unprepare(ehci_mv->clk); 56 57 return retval; 58} 59 60static void mv_ehci_disable(struct ehci_hcd_mv *ehci_mv) 61{ 62 phy_exit(ehci_mv->phy); 63 clk_disable_unprepare(ehci_mv->clk); 64} 65 66static int mv_ehci_reset(struct usb_hcd *hcd) 67{ 68 struct device *dev = hcd->self.controller; 69 struct ehci_hcd_mv *ehci_mv = hcd_to_ehci_hcd_mv(hcd); 70 struct ehci_hcd *ehci = hcd_to_ehci(hcd); 71 u32 status; 72 int retval; 73 74 if (ehci_mv == NULL) { 75 dev_err(dev, "Can not find private ehci data\n"); 76 return -ENODEV; 77 } 78 79 hcd->has_tt = 1; 80 81 retval = ehci_setup(hcd); 82 if (retval) 83 dev_err(dev, "ehci_setup failed %d\n", retval); 84 85 if (of_usb_get_phy_mode(dev->of_node) == USBPHY_INTERFACE_MODE_HSIC) { 86 status = ehci_readl(ehci, &ehci->regs->port_status[0]); 87 status |= PORT_TEST_FORCE; 88 ehci_writel(ehci, status, &ehci->regs->port_status[0]); 89 status &= ~PORT_TEST_FORCE; 90 ehci_writel(ehci, status, &ehci->regs->port_status[0]); 91 } 92 93 return retval; 94} 95 96static struct hc_driver __read_mostly ehci_platform_hc_driver; 97 98static const struct ehci_driver_overrides platform_overrides __initconst = { 99 .reset = mv_ehci_reset, 100 .extra_priv_size = sizeof(struct ehci_hcd_mv), 101}; 102 103static int mv_ehci_probe(struct platform_device *pdev) 104{ 105 struct mv_usb_platform_data *pdata = dev_get_platdata(&pdev->dev); 106 struct usb_hcd *hcd; 107 struct ehci_hcd *ehci; 108 struct ehci_hcd_mv *ehci_mv; 109 struct resource *r; 110 int retval; 111 u32 offset; 112 u32 status; 113 114 if (usb_disabled()) 115 return -ENODEV; 116 117 hcd = usb_create_hcd(&ehci_platform_hc_driver, &pdev->dev, dev_name(&pdev->dev)); 118 if (!hcd) 119 return -ENOMEM; 120 121 platform_set_drvdata(pdev, hcd); 122 ehci_mv = hcd_to_ehci_hcd_mv(hcd); 123 124 ehci_mv->mode = MV_USB_MODE_HOST; 125 if (pdata) { 126 ehci_mv->mode = pdata->mode; 127 ehci_mv->set_vbus = pdata->set_vbus; 128 } 129 130 ehci_mv->phy = devm_phy_optional_get(&pdev->dev, "usb"); 131 if (IS_ERR(ehci_mv->phy)) { 132 retval = PTR_ERR(ehci_mv->phy); 133 if (retval != -EPROBE_DEFER) 134 dev_err(&pdev->dev, "Failed to get phy.\n"); 135 goto err_put_hcd; 136 } 137 138 ehci_mv->clk = devm_clk_get(&pdev->dev, NULL); 139 if (IS_ERR(ehci_mv->clk)) { 140 dev_err(&pdev->dev, "error getting clock\n"); 141 retval = PTR_ERR(ehci_mv->clk); 142 goto err_put_hcd; 143 } 144 145 r = platform_get_resource(pdev, IORESOURCE_MEM, 0); 146 ehci_mv->base = devm_ioremap_resource(&pdev->dev, r); 147 if (IS_ERR(ehci_mv->base)) { 148 retval = PTR_ERR(ehci_mv->base); 149 goto err_put_hcd; 150 } 151 152 retval = mv_ehci_enable(ehci_mv); 153 if (retval) { 154 dev_err(&pdev->dev, "init phy error %d\n", retval); 155 goto err_put_hcd; 156 } 157 158 ehci_mv->cap_regs = 159 (void __iomem *) ((unsigned long) ehci_mv->base + U2x_CAPREGS_OFFSET); 160 offset = readl(ehci_mv->cap_regs) & CAPLENGTH_MASK; 161 ehci_mv->op_regs = 162 (void __iomem *) ((unsigned long) ehci_mv->cap_regs + offset); 163 164 hcd->rsrc_start = r->start; 165 hcd->rsrc_len = resource_size(r); 166 hcd->regs = ehci_mv->op_regs; 167 168 retval = platform_get_irq(pdev, 0); 169 if (retval < 0) 170 goto err_disable_clk; 171 hcd->irq = retval; 172 173 ehci = hcd_to_ehci(hcd); 174 ehci->caps = (struct ehci_caps __iomem *) ehci_mv->cap_regs; 175 176 if (ehci_mv->mode == MV_USB_MODE_OTG) { 177 ehci_mv->otg = devm_usb_get_phy(&pdev->dev, USB_PHY_TYPE_USB2); 178 if (IS_ERR(ehci_mv->otg)) { 179 retval = PTR_ERR(ehci_mv->otg); 180 181 if (retval == -ENXIO) 182 dev_info(&pdev->dev, "MV_USB_MODE_OTG " 183 "must have CONFIG_USB_PHY enabled\n"); 184 else 185 dev_err(&pdev->dev, 186 "unable to find transceiver\n"); 187 goto err_disable_clk; 188 } 189 190 retval = otg_set_host(ehci_mv->otg->otg, &hcd->self); 191 if (retval < 0) { 192 dev_err(&pdev->dev, 193 "unable to register with transceiver\n"); 194 retval = -ENODEV; 195 goto err_disable_clk; 196 } 197 /* otg will enable clock before use as host */ 198 mv_ehci_disable(ehci_mv); 199 } else { 200 if (ehci_mv->set_vbus) 201 ehci_mv->set_vbus(1); 202 203 retval = usb_add_hcd(hcd, hcd->irq, IRQF_SHARED); 204 if (retval) { 205 dev_err(&pdev->dev, 206 "failed to add hcd with err %d\n", retval); 207 goto err_set_vbus; 208 } 209 device_wakeup_enable(hcd->self.controller); 210 } 211 212 if (of_usb_get_phy_mode(pdev->dev.of_node) == USBPHY_INTERFACE_MODE_HSIC) { 213 status = ehci_readl(ehci, &ehci->regs->port_status[0]); 214 /* These "reserved" bits actually enable HSIC mode. */ 215 status |= BIT(25); 216 status &= ~GENMASK(31, 30); 217 ehci_writel(ehci, status, &ehci->regs->port_status[0]); 218 } 219 220 dev_info(&pdev->dev, 221 "successful find EHCI device with regs 0x%p irq %d" 222 " working in %s mode\n", hcd->regs, hcd->irq, 223 ehci_mv->mode == MV_USB_MODE_OTG ? "OTG" : "Host"); 224 225 return 0; 226 227err_set_vbus: 228 if (ehci_mv->set_vbus) 229 ehci_mv->set_vbus(0); 230err_disable_clk: 231 mv_ehci_disable(ehci_mv); 232err_put_hcd: 233 usb_put_hcd(hcd); 234 235 return retval; 236} 237 238static int mv_ehci_remove(struct platform_device *pdev) 239{ 240 struct usb_hcd *hcd = platform_get_drvdata(pdev); 241 struct ehci_hcd_mv *ehci_mv = hcd_to_ehci_hcd_mv(hcd); 242 243 if (hcd->rh_registered) 244 usb_remove_hcd(hcd); 245 246 if (!IS_ERR_OR_NULL(ehci_mv->otg)) 247 otg_set_host(ehci_mv->otg->otg, NULL); 248 249 if (ehci_mv->mode == MV_USB_MODE_HOST) { 250 if (ehci_mv->set_vbus) 251 ehci_mv->set_vbus(0); 252 253 mv_ehci_disable(ehci_mv); 254 } 255 256 usb_put_hcd(hcd); 257 258 return 0; 259} 260 261static const struct platform_device_id ehci_id_table[] = { 262 {"pxa-u2oehci", 0}, 263 {"pxa-sph", 0}, 264 {}, 265}; 266 267static void mv_ehci_shutdown(struct platform_device *pdev) 268{ 269 struct usb_hcd *hcd = platform_get_drvdata(pdev); 270 271 if (!hcd->rh_registered) 272 return; 273 274 if (hcd->driver->shutdown) 275 hcd->driver->shutdown(hcd); 276} 277 278static const struct of_device_id ehci_mv_dt_ids[] = { 279 { .compatible = "marvell,pxau2o-ehci", }, 280 {}, 281}; 282 283static struct platform_driver ehci_mv_driver = { 284 .probe = mv_ehci_probe, 285 .remove = mv_ehci_remove, 286 .shutdown = mv_ehci_shutdown, 287 .driver = { 288 .name = "mv-ehci", 289 .bus = &platform_bus_type, 290 .of_match_table = ehci_mv_dt_ids, 291 }, 292 .id_table = ehci_id_table, 293}; 294 295static int __init ehci_platform_init(void) 296{ 297 if (usb_disabled()) 298 return -ENODEV; 299 300 ehci_init_driver(&ehci_platform_hc_driver, &platform_overrides); 301 return platform_driver_register(&ehci_mv_driver); 302} 303module_init(ehci_platform_init); 304 305static void __exit ehci_platform_cleanup(void) 306{ 307 platform_driver_unregister(&ehci_mv_driver); 308} 309module_exit(ehci_platform_cleanup); 310 311MODULE_DESCRIPTION("Marvell EHCI driver"); 312MODULE_AUTHOR("Chao Xie <chao.xie@marvell.com>"); 313MODULE_AUTHOR("Neil Zhang <zhangwm@marvell.com>"); 314MODULE_ALIAS("mv-ehci"); 315MODULE_LICENSE("GPL"); 316MODULE_DEVICE_TABLE(of, ehci_mv_dt_ids);