| .. | .. |
|---|
| 11 | 11 | #include <linux/clk.h> |
|---|
| 12 | 12 | #include <linux/err.h> |
|---|
| 13 | 13 | #include <linux/usb/otg.h> |
|---|
| 14 | +#include <linux/usb/of.h> |
|---|
| 14 | 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 |
|---|
| 15 | 24 | |
|---|
| 16 | 25 | #define CAPLENGTH_MASK (0xff) |
|---|
| 17 | 26 | |
|---|
| 18 | | -struct ehci_hcd_mv { |
|---|
| 19 | | - struct usb_hcd *hcd; |
|---|
| 27 | +#define hcd_to_ehci_hcd_mv(h) ((struct ehci_hcd_mv *)hcd_to_ehci(h)->priv) |
|---|
| 20 | 28 | |
|---|
| 29 | +struct ehci_hcd_mv { |
|---|
| 21 | 30 | /* Which mode does this ehci running OTG/Host ? */ |
|---|
| 22 | 31 | int mode; |
|---|
| 23 | 32 | |
|---|
| 24 | | - void __iomem *phy_regs; |
|---|
| 33 | + void __iomem *base; |
|---|
| 25 | 34 | void __iomem *cap_regs; |
|---|
| 26 | 35 | void __iomem *op_regs; |
|---|
| 27 | 36 | |
|---|
| 28 | 37 | struct usb_phy *otg; |
|---|
| 29 | | - |
|---|
| 30 | | - struct mv_usb_platform_data *pdata; |
|---|
| 31 | | - |
|---|
| 32 | 38 | struct clk *clk; |
|---|
| 39 | + |
|---|
| 40 | + struct phy *phy; |
|---|
| 41 | + |
|---|
| 42 | + int (*set_vbus)(unsigned int vbus); |
|---|
| 33 | 43 | }; |
|---|
| 34 | | - |
|---|
| 35 | | -static void ehci_clock_enable(struct ehci_hcd_mv *ehci_mv) |
|---|
| 36 | | -{ |
|---|
| 37 | | - clk_prepare_enable(ehci_mv->clk); |
|---|
| 38 | | -} |
|---|
| 39 | | - |
|---|
| 40 | | -static void ehci_clock_disable(struct ehci_hcd_mv *ehci_mv) |
|---|
| 41 | | -{ |
|---|
| 42 | | - clk_disable_unprepare(ehci_mv->clk); |
|---|
| 43 | | -} |
|---|
| 44 | 44 | |
|---|
| 45 | 45 | static int mv_ehci_enable(struct ehci_hcd_mv *ehci_mv) |
|---|
| 46 | 46 | { |
|---|
| 47 | 47 | int retval; |
|---|
| 48 | 48 | |
|---|
| 49 | | - ehci_clock_enable(ehci_mv); |
|---|
| 50 | | - if (ehci_mv->pdata->phy_init) { |
|---|
| 51 | | - retval = ehci_mv->pdata->phy_init(ehci_mv->phy_regs); |
|---|
| 52 | | - if (retval) |
|---|
| 53 | | - return retval; |
|---|
| 54 | | - } |
|---|
| 49 | + retval = clk_prepare_enable(ehci_mv->clk); |
|---|
| 50 | + if (retval) |
|---|
| 51 | + return retval; |
|---|
| 55 | 52 | |
|---|
| 56 | | - return 0; |
|---|
| 53 | + retval = phy_init(ehci_mv->phy); |
|---|
| 54 | + if (retval) |
|---|
| 55 | + clk_disable_unprepare(ehci_mv->clk); |
|---|
| 56 | + |
|---|
| 57 | + return retval; |
|---|
| 57 | 58 | } |
|---|
| 58 | 59 | |
|---|
| 59 | 60 | static void mv_ehci_disable(struct ehci_hcd_mv *ehci_mv) |
|---|
| 60 | 61 | { |
|---|
| 61 | | - if (ehci_mv->pdata->phy_deinit) |
|---|
| 62 | | - ehci_mv->pdata->phy_deinit(ehci_mv->phy_regs); |
|---|
| 63 | | - ehci_clock_disable(ehci_mv); |
|---|
| 62 | + phy_exit(ehci_mv->phy); |
|---|
| 63 | + clk_disable_unprepare(ehci_mv->clk); |
|---|
| 64 | 64 | } |
|---|
| 65 | 65 | |
|---|
| 66 | 66 | static int mv_ehci_reset(struct usb_hcd *hcd) |
|---|
| 67 | 67 | { |
|---|
| 68 | 68 | struct device *dev = hcd->self.controller; |
|---|
| 69 | | - struct ehci_hcd_mv *ehci_mv = dev_get_drvdata(dev); |
|---|
| 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; |
|---|
| 70 | 72 | int retval; |
|---|
| 71 | 73 | |
|---|
| 72 | 74 | if (ehci_mv == NULL) { |
|---|
| .. | .. |
|---|
| 80 | 82 | if (retval) |
|---|
| 81 | 83 | dev_err(dev, "ehci_setup failed %d\n", retval); |
|---|
| 82 | 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 | + |
|---|
| 83 | 93 | return retval; |
|---|
| 84 | 94 | } |
|---|
| 85 | 95 | |
|---|
| 86 | | -static const struct hc_driver mv_ehci_hc_driver = { |
|---|
| 87 | | - .description = hcd_name, |
|---|
| 88 | | - .product_desc = "Marvell EHCI", |
|---|
| 89 | | - .hcd_priv_size = sizeof(struct ehci_hcd), |
|---|
| 96 | +static struct hc_driver __read_mostly ehci_platform_hc_driver; |
|---|
| 90 | 97 | |
|---|
| 91 | | - /* |
|---|
| 92 | | - * generic hardware linkage |
|---|
| 93 | | - */ |
|---|
| 94 | | - .irq = ehci_irq, |
|---|
| 95 | | - .flags = HCD_MEMORY | HCD_USB2 | HCD_BH, |
|---|
| 96 | | - |
|---|
| 97 | | - /* |
|---|
| 98 | | - * basic lifecycle operations |
|---|
| 99 | | - */ |
|---|
| 100 | | - .reset = mv_ehci_reset, |
|---|
| 101 | | - .start = ehci_run, |
|---|
| 102 | | - .stop = ehci_stop, |
|---|
| 103 | | - .shutdown = ehci_shutdown, |
|---|
| 104 | | - |
|---|
| 105 | | - /* |
|---|
| 106 | | - * managing i/o requests and associated device resources |
|---|
| 107 | | - */ |
|---|
| 108 | | - .urb_enqueue = ehci_urb_enqueue, |
|---|
| 109 | | - .urb_dequeue = ehci_urb_dequeue, |
|---|
| 110 | | - .endpoint_disable = ehci_endpoint_disable, |
|---|
| 111 | | - .endpoint_reset = ehci_endpoint_reset, |
|---|
| 112 | | - .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, |
|---|
| 113 | | - |
|---|
| 114 | | - /* |
|---|
| 115 | | - * scheduling support |
|---|
| 116 | | - */ |
|---|
| 117 | | - .get_frame_number = ehci_get_frame, |
|---|
| 118 | | - |
|---|
| 119 | | - /* |
|---|
| 120 | | - * root hub support |
|---|
| 121 | | - */ |
|---|
| 122 | | - .hub_status_data = ehci_hub_status_data, |
|---|
| 123 | | - .hub_control = ehci_hub_control, |
|---|
| 124 | | - .bus_suspend = ehci_bus_suspend, |
|---|
| 125 | | - .bus_resume = ehci_bus_resume, |
|---|
| 98 | +static const struct ehci_driver_overrides platform_overrides __initconst = { |
|---|
| 99 | + .reset = mv_ehci_reset, |
|---|
| 100 | + .extra_priv_size = sizeof(struct ehci_hcd_mv), |
|---|
| 126 | 101 | }; |
|---|
| 127 | 102 | |
|---|
| 128 | 103 | static int mv_ehci_probe(struct platform_device *pdev) |
|---|
| .. | .. |
|---|
| 132 | 107 | struct ehci_hcd *ehci; |
|---|
| 133 | 108 | struct ehci_hcd_mv *ehci_mv; |
|---|
| 134 | 109 | struct resource *r; |
|---|
| 135 | | - int retval = -ENODEV; |
|---|
| 110 | + int retval; |
|---|
| 136 | 111 | u32 offset; |
|---|
| 137 | | - |
|---|
| 138 | | - if (!pdata) { |
|---|
| 139 | | - dev_err(&pdev->dev, "missing platform_data\n"); |
|---|
| 140 | | - return -ENODEV; |
|---|
| 141 | | - } |
|---|
| 112 | + u32 status; |
|---|
| 142 | 113 | |
|---|
| 143 | 114 | if (usb_disabled()) |
|---|
| 144 | 115 | return -ENODEV; |
|---|
| 145 | 116 | |
|---|
| 146 | | - hcd = usb_create_hcd(&mv_ehci_hc_driver, &pdev->dev, "mv ehci"); |
|---|
| 117 | + hcd = usb_create_hcd(&ehci_platform_hc_driver, &pdev->dev, dev_name(&pdev->dev)); |
|---|
| 147 | 118 | if (!hcd) |
|---|
| 148 | 119 | return -ENOMEM; |
|---|
| 149 | 120 | |
|---|
| 150 | | - ehci_mv = devm_kzalloc(&pdev->dev, sizeof(*ehci_mv), GFP_KERNEL); |
|---|
| 151 | | - if (ehci_mv == NULL) { |
|---|
| 152 | | - retval = -ENOMEM; |
|---|
| 153 | | - goto err_put_hcd; |
|---|
| 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; |
|---|
| 154 | 128 | } |
|---|
| 155 | 129 | |
|---|
| 156 | | - platform_set_drvdata(pdev, ehci_mv); |
|---|
| 157 | | - ehci_mv->pdata = pdata; |
|---|
| 158 | | - ehci_mv->hcd = hcd; |
|---|
| 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 | + } |
|---|
| 159 | 137 | |
|---|
| 160 | 138 | ehci_mv->clk = devm_clk_get(&pdev->dev, NULL); |
|---|
| 161 | 139 | if (IS_ERR(ehci_mv->clk)) { |
|---|
| .. | .. |
|---|
| 164 | 142 | goto err_put_hcd; |
|---|
| 165 | 143 | } |
|---|
| 166 | 144 | |
|---|
| 167 | | - r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phyregs"); |
|---|
| 168 | | - ehci_mv->phy_regs = devm_ioremap_resource(&pdev->dev, r); |
|---|
| 169 | | - if (IS_ERR(ehci_mv->phy_regs)) { |
|---|
| 170 | | - retval = PTR_ERR(ehci_mv->phy_regs); |
|---|
| 171 | | - goto err_put_hcd; |
|---|
| 172 | | - } |
|---|
| 173 | | - |
|---|
| 174 | | - r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "capregs"); |
|---|
| 175 | | - ehci_mv->cap_regs = devm_ioremap_resource(&pdev->dev, r); |
|---|
| 176 | | - if (IS_ERR(ehci_mv->cap_regs)) { |
|---|
| 177 | | - retval = PTR_ERR(ehci_mv->cap_regs); |
|---|
| 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); |
|---|
| 178 | 149 | goto err_put_hcd; |
|---|
| 179 | 150 | } |
|---|
| 180 | 151 | |
|---|
| .. | .. |
|---|
| 184 | 155 | goto err_put_hcd; |
|---|
| 185 | 156 | } |
|---|
| 186 | 157 | |
|---|
| 158 | + ehci_mv->cap_regs = |
|---|
| 159 | + (void __iomem *) ((unsigned long) ehci_mv->base + U2x_CAPREGS_OFFSET); |
|---|
| 187 | 160 | offset = readl(ehci_mv->cap_regs) & CAPLENGTH_MASK; |
|---|
| 188 | 161 | ehci_mv->op_regs = |
|---|
| 189 | 162 | (void __iomem *) ((unsigned long) ehci_mv->cap_regs + offset); |
|---|
| .. | .. |
|---|
| 198 | 171 | hcd->irq = retval; |
|---|
| 199 | 172 | |
|---|
| 200 | 173 | ehci = hcd_to_ehci(hcd); |
|---|
| 201 | | - ehci->caps = (struct ehci_caps *) ehci_mv->cap_regs; |
|---|
| 174 | + ehci->caps = (struct ehci_caps __iomem *) ehci_mv->cap_regs; |
|---|
| 202 | 175 | |
|---|
| 203 | | - ehci_mv->mode = pdata->mode; |
|---|
| 204 | 176 | if (ehci_mv->mode == MV_USB_MODE_OTG) { |
|---|
| 205 | 177 | ehci_mv->otg = devm_usb_get_phy(&pdev->dev, USB_PHY_TYPE_USB2); |
|---|
| 206 | 178 | if (IS_ERR(ehci_mv->otg)) { |
|---|
| .. | .. |
|---|
| 225 | 197 | /* otg will enable clock before use as host */ |
|---|
| 226 | 198 | mv_ehci_disable(ehci_mv); |
|---|
| 227 | 199 | } else { |
|---|
| 228 | | - if (pdata->set_vbus) |
|---|
| 229 | | - pdata->set_vbus(1); |
|---|
| 200 | + if (ehci_mv->set_vbus) |
|---|
| 201 | + ehci_mv->set_vbus(1); |
|---|
| 230 | 202 | |
|---|
| 231 | 203 | retval = usb_add_hcd(hcd, hcd->irq, IRQF_SHARED); |
|---|
| 232 | 204 | if (retval) { |
|---|
| .. | .. |
|---|
| 237 | 209 | device_wakeup_enable(hcd->self.controller); |
|---|
| 238 | 210 | } |
|---|
| 239 | 211 | |
|---|
| 240 | | - if (pdata->private_init) |
|---|
| 241 | | - pdata->private_init(ehci_mv->op_regs, ehci_mv->phy_regs); |
|---|
| 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 | + } |
|---|
| 242 | 219 | |
|---|
| 243 | 220 | dev_info(&pdev->dev, |
|---|
| 244 | 221 | "successful find EHCI device with regs 0x%p irq %d" |
|---|
| .. | .. |
|---|
| 248 | 225 | return 0; |
|---|
| 249 | 226 | |
|---|
| 250 | 227 | err_set_vbus: |
|---|
| 251 | | - if (pdata->set_vbus) |
|---|
| 252 | | - pdata->set_vbus(0); |
|---|
| 228 | + if (ehci_mv->set_vbus) |
|---|
| 229 | + ehci_mv->set_vbus(0); |
|---|
| 253 | 230 | err_disable_clk: |
|---|
| 254 | 231 | mv_ehci_disable(ehci_mv); |
|---|
| 255 | 232 | err_put_hcd: |
|---|
| .. | .. |
|---|
| 260 | 237 | |
|---|
| 261 | 238 | static int mv_ehci_remove(struct platform_device *pdev) |
|---|
| 262 | 239 | { |
|---|
| 263 | | - struct ehci_hcd_mv *ehci_mv = platform_get_drvdata(pdev); |
|---|
| 264 | | - struct usb_hcd *hcd = ehci_mv->hcd; |
|---|
| 240 | + struct usb_hcd *hcd = platform_get_drvdata(pdev); |
|---|
| 241 | + struct ehci_hcd_mv *ehci_mv = hcd_to_ehci_hcd_mv(hcd); |
|---|
| 265 | 242 | |
|---|
| 266 | 243 | if (hcd->rh_registered) |
|---|
| 267 | 244 | usb_remove_hcd(hcd); |
|---|
| .. | .. |
|---|
| 270 | 247 | otg_set_host(ehci_mv->otg->otg, NULL); |
|---|
| 271 | 248 | |
|---|
| 272 | 249 | if (ehci_mv->mode == MV_USB_MODE_HOST) { |
|---|
| 273 | | - if (ehci_mv->pdata->set_vbus) |
|---|
| 274 | | - ehci_mv->pdata->set_vbus(0); |
|---|
| 250 | + if (ehci_mv->set_vbus) |
|---|
| 251 | + ehci_mv->set_vbus(0); |
|---|
| 275 | 252 | |
|---|
| 276 | 253 | mv_ehci_disable(ehci_mv); |
|---|
| 277 | 254 | } |
|---|
| .. | .. |
|---|
| 284 | 261 | MODULE_ALIAS("mv-ehci"); |
|---|
| 285 | 262 | |
|---|
| 286 | 263 | static const struct platform_device_id ehci_id_table[] = { |
|---|
| 287 | | - {"pxa-u2oehci", PXA_U2OEHCI}, |
|---|
| 288 | | - {"pxa-sph", PXA_SPH}, |
|---|
| 289 | | - {"mmp3-hsic", MMP3_HSIC}, |
|---|
| 290 | | - {"mmp3-fsic", MMP3_FSIC}, |
|---|
| 264 | + {"pxa-u2oehci", 0}, |
|---|
| 265 | + {"pxa-sph", 0}, |
|---|
| 291 | 266 | {}, |
|---|
| 292 | 267 | }; |
|---|
| 293 | 268 | |
|---|
| 294 | 269 | static void mv_ehci_shutdown(struct platform_device *pdev) |
|---|
| 295 | 270 | { |
|---|
| 296 | | - struct ehci_hcd_mv *ehci_mv = platform_get_drvdata(pdev); |
|---|
| 297 | | - struct usb_hcd *hcd = ehci_mv->hcd; |
|---|
| 271 | + struct usb_hcd *hcd = platform_get_drvdata(pdev); |
|---|
| 298 | 272 | |
|---|
| 299 | 273 | if (!hcd->rh_registered) |
|---|
| 300 | 274 | return; |
|---|
| .. | .. |
|---|
| 303 | 277 | hcd->driver->shutdown(hcd); |
|---|
| 304 | 278 | } |
|---|
| 305 | 279 | |
|---|
| 280 | +static const struct of_device_id ehci_mv_dt_ids[] = { |
|---|
| 281 | + { .compatible = "marvell,pxau2o-ehci", }, |
|---|
| 282 | + {}, |
|---|
| 283 | +}; |
|---|
| 284 | + |
|---|
| 306 | 285 | static struct platform_driver ehci_mv_driver = { |
|---|
| 307 | 286 | .probe = mv_ehci_probe, |
|---|
| 308 | 287 | .remove = mv_ehci_remove, |
|---|
| 309 | 288 | .shutdown = mv_ehci_shutdown, |
|---|
| 310 | 289 | .driver = { |
|---|
| 311 | | - .name = "mv-ehci", |
|---|
| 312 | | - .bus = &platform_bus_type, |
|---|
| 313 | | - }, |
|---|
| 290 | + .name = "mv-ehci", |
|---|
| 291 | + .bus = &platform_bus_type, |
|---|
| 292 | + .of_match_table = ehci_mv_dt_ids, |
|---|
| 293 | + }, |
|---|
| 314 | 294 | .id_table = ehci_id_table, |
|---|
| 315 | 295 | }; |
|---|
| 296 | + |
|---|
| 297 | +static int __init ehci_platform_init(void) |
|---|
| 298 | +{ |
|---|
| 299 | + if (usb_disabled()) |
|---|
| 300 | + return -ENODEV; |
|---|
| 301 | + |
|---|
| 302 | + ehci_init_driver(&ehci_platform_hc_driver, &platform_overrides); |
|---|
| 303 | + return platform_driver_register(&ehci_mv_driver); |
|---|
| 304 | +} |
|---|
| 305 | +module_init(ehci_platform_init); |
|---|
| 306 | + |
|---|
| 307 | +static void __exit ehci_platform_cleanup(void) |
|---|
| 308 | +{ |
|---|
| 309 | + platform_driver_unregister(&ehci_mv_driver); |
|---|
| 310 | +} |
|---|
| 311 | +module_exit(ehci_platform_cleanup); |
|---|
| 312 | + |
|---|
| 313 | +MODULE_DESCRIPTION("Marvell EHCI driver"); |
|---|
| 314 | +MODULE_AUTHOR("Chao Xie <chao.xie@marvell.com>"); |
|---|
| 315 | +MODULE_AUTHOR("Neil Zhang <zhangwm@marvell.com>"); |
|---|
| 316 | +MODULE_ALIAS("mv-ehci"); |
|---|
| 317 | +MODULE_LICENSE("GPL"); |
|---|
| 318 | +MODULE_DEVICE_TABLE(of, ehci_mv_dt_ids); |
|---|