linux/drivers/usb/host/ehci-s5p.c
<<
>>
Prefs
   1/*
   2 * SAMSUNG S5P USB HOST EHCI Controller
   3 *
   4 * Copyright (C) 2011 Samsung Electronics Co.Ltd
   5 * Author: Jingoo Han <jg1.han@samsung.com>
   6 * Author: Joonyoung Shim <jy0922.shim@samsung.com>
   7 *
   8 * This program is free software; you can redistribute  it and/or modify it
   9 * under  the terms of  the GNU General  Public License as published by the
  10 * Free Software Foundation;  either version 2 of the  License, or (at your
  11 * option) any later version.
  12 *
  13 */
  14
  15#include <linux/clk.h>
  16#include <linux/dma-mapping.h>
  17#include <linux/io.h>
  18#include <linux/kernel.h>
  19#include <linux/module.h>
  20#include <linux/of.h>
  21#include <linux/of_gpio.h>
  22#include <linux/platform_device.h>
  23#include <linux/platform_data/usb-ehci-s5p.h>
  24#include <linux/usb/phy.h>
  25#include <linux/usb/samsung_usb_phy.h>
  26#include <linux/usb.h>
  27#include <linux/usb/hcd.h>
  28#include <linux/usb/otg.h>
  29
  30#include "ehci.h"
  31
  32#define DRIVER_DESC "EHCI s5p driver"
  33
  34#define EHCI_INSNREG00(base)                    (base + 0x90)
  35#define EHCI_INSNREG00_ENA_INCR16               (0x1 << 25)
  36#define EHCI_INSNREG00_ENA_INCR8                (0x1 << 24)
  37#define EHCI_INSNREG00_ENA_INCR4                (0x1 << 23)
  38#define EHCI_INSNREG00_ENA_INCRX_ALIGN          (0x1 << 22)
  39#define EHCI_INSNREG00_ENABLE_DMA_BURST \
  40        (EHCI_INSNREG00_ENA_INCR16 | EHCI_INSNREG00_ENA_INCR8 | \
  41         EHCI_INSNREG00_ENA_INCR4 | EHCI_INSNREG00_ENA_INCRX_ALIGN)
  42
  43static const char hcd_name[] = "ehci-s5p";
  44static struct hc_driver __read_mostly s5p_ehci_hc_driver;
  45
  46struct s5p_ehci_hcd {
  47        struct clk *clk;
  48        struct usb_phy *phy;
  49        struct usb_otg *otg;
  50        struct s5p_ehci_platdata *pdata;
  51};
  52
  53#define to_s5p_ehci(hcd)      (struct s5p_ehci_hcd *)(hcd_to_ehci(hcd)->priv)
  54
  55static void s5p_setup_vbus_gpio(struct platform_device *pdev)
  56{
  57        struct device *dev = &pdev->dev;
  58        int err;
  59        int gpio;
  60
  61        if (!dev->of_node)
  62                return;
  63
  64        gpio = of_get_named_gpio(dev->of_node, "samsung,vbus-gpio", 0);
  65        if (!gpio_is_valid(gpio))
  66                return;
  67
  68        err = devm_gpio_request_one(dev, gpio, GPIOF_OUT_INIT_HIGH,
  69                                    "ehci_vbus_gpio");
  70        if (err)
  71                dev_err(dev, "can't request ehci vbus gpio %d", gpio);
  72}
  73
  74static int s5p_ehci_probe(struct platform_device *pdev)
  75{
  76        struct s5p_ehci_platdata *pdata = pdev->dev.platform_data;
  77        struct s5p_ehci_hcd *s5p_ehci;
  78        struct usb_hcd *hcd;
  79        struct ehci_hcd *ehci;
  80        struct resource *res;
  81        struct usb_phy *phy;
  82        int irq;
  83        int err;
  84
  85        /*
  86         * Right now device-tree probed devices don't get dma_mask set.
  87         * Since shared usb code relies on it, set it here for now.
  88         * Once we move to full device tree support this will vanish off.
  89         */
  90        if (!pdev->dev.dma_mask)
  91                pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask;
  92        if (!pdev->dev.coherent_dma_mask)
  93                pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
  94
  95        s5p_setup_vbus_gpio(pdev);
  96
  97        hcd = usb_create_hcd(&s5p_ehci_hc_driver,
  98                             &pdev->dev, dev_name(&pdev->dev));
  99        if (!hcd) {
 100                dev_err(&pdev->dev, "Unable to create HCD\n");
 101                return -ENOMEM;
 102        }
 103        s5p_ehci = to_s5p_ehci(hcd);
 104        phy = devm_usb_get_phy(&pdev->dev, USB_PHY_TYPE_USB2);
 105        if (IS_ERR(phy)) {
 106                /* Fallback to pdata */
 107                if (!pdata) {
 108                        usb_put_hcd(hcd);
 109                        dev_warn(&pdev->dev, "no platform data or transceiver defined\n");
 110                        return -EPROBE_DEFER;
 111                } else {
 112                        s5p_ehci->pdata = pdata;
 113                }
 114        } else {
 115                s5p_ehci->phy = phy;
 116                s5p_ehci->otg = phy->otg;
 117        }
 118
 119        s5p_ehci->clk = devm_clk_get(&pdev->dev, "usbhost");
 120
 121        if (IS_ERR(s5p_ehci->clk)) {
 122                dev_err(&pdev->dev, "Failed to get usbhost clock\n");
 123                err = PTR_ERR(s5p_ehci->clk);
 124                goto fail_clk;
 125        }
 126
 127        err = clk_prepare_enable(s5p_ehci->clk);
 128        if (err)
 129                goto fail_clk;
 130
 131        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 132        if (!res) {
 133                dev_err(&pdev->dev, "Failed to get I/O memory\n");
 134                err = -ENXIO;
 135                goto fail_io;
 136        }
 137
 138        hcd->rsrc_start = res->start;
 139        hcd->rsrc_len = resource_size(res);
 140        hcd->regs = devm_ioremap(&pdev->dev, res->start, hcd->rsrc_len);
 141        if (!hcd->regs) {
 142                dev_err(&pdev->dev, "Failed to remap I/O memory\n");
 143                err = -ENOMEM;
 144                goto fail_io;
 145        }
 146
 147        irq = platform_get_irq(pdev, 0);
 148        if (!irq) {
 149                dev_err(&pdev->dev, "Failed to get IRQ\n");
 150                err = -ENODEV;
 151                goto fail_io;
 152        }
 153
 154        if (s5p_ehci->otg)
 155                s5p_ehci->otg->set_host(s5p_ehci->otg, &hcd->self);
 156
 157        if (s5p_ehci->phy)
 158                usb_phy_init(s5p_ehci->phy);
 159        else if (s5p_ehci->pdata->phy_init)
 160                s5p_ehci->pdata->phy_init(pdev, USB_PHY_TYPE_HOST);
 161
 162        ehci = hcd_to_ehci(hcd);
 163        ehci->caps = hcd->regs;
 164
 165        /* DMA burst Enable */
 166        writel(EHCI_INSNREG00_ENABLE_DMA_BURST, EHCI_INSNREG00(hcd->regs));
 167
 168        err = usb_add_hcd(hcd, irq, IRQF_SHARED);
 169        if (err) {
 170                dev_err(&pdev->dev, "Failed to add USB HCD\n");
 171                goto fail_add_hcd;
 172        }
 173
 174        platform_set_drvdata(pdev, hcd);
 175
 176        return 0;
 177
 178fail_add_hcd:
 179        if (s5p_ehci->phy)
 180                usb_phy_shutdown(s5p_ehci->phy);
 181        else if (s5p_ehci->pdata->phy_exit)
 182                s5p_ehci->pdata->phy_exit(pdev, USB_PHY_TYPE_HOST);
 183fail_io:
 184        clk_disable_unprepare(s5p_ehci->clk);
 185fail_clk:
 186        usb_put_hcd(hcd);
 187        return err;
 188}
 189
 190static int s5p_ehci_remove(struct platform_device *pdev)
 191{
 192        struct usb_hcd *hcd = platform_get_drvdata(pdev);
 193        struct s5p_ehci_hcd *s5p_ehci = to_s5p_ehci(hcd);
 194
 195        usb_remove_hcd(hcd);
 196
 197        if (s5p_ehci->otg)
 198                s5p_ehci->otg->set_host(s5p_ehci->otg, &hcd->self);
 199
 200        if (s5p_ehci->phy)
 201                usb_phy_shutdown(s5p_ehci->phy);
 202        else if (s5p_ehci->pdata->phy_exit)
 203                s5p_ehci->pdata->phy_exit(pdev, USB_PHY_TYPE_HOST);
 204
 205        clk_disable_unprepare(s5p_ehci->clk);
 206
 207        usb_put_hcd(hcd);
 208
 209        return 0;
 210}
 211
 212static void s5p_ehci_shutdown(struct platform_device *pdev)
 213{
 214        struct usb_hcd *hcd = platform_get_drvdata(pdev);
 215
 216        if (hcd->driver->shutdown)
 217                hcd->driver->shutdown(hcd);
 218}
 219
 220#ifdef CONFIG_PM
 221static int s5p_ehci_suspend(struct device *dev)
 222{
 223        struct usb_hcd *hcd = dev_get_drvdata(dev);
 224        struct s5p_ehci_hcd *s5p_ehci = to_s5p_ehci(hcd);
 225        struct platform_device *pdev = to_platform_device(dev);
 226
 227        bool do_wakeup = device_may_wakeup(dev);
 228        int rc;
 229
 230        rc = ehci_suspend(hcd, do_wakeup);
 231
 232        if (s5p_ehci->otg)
 233                s5p_ehci->otg->set_host(s5p_ehci->otg, &hcd->self);
 234
 235        if (s5p_ehci->phy)
 236                usb_phy_shutdown(s5p_ehci->phy);
 237        else if (s5p_ehci->pdata->phy_exit)
 238                s5p_ehci->pdata->phy_exit(pdev, USB_PHY_TYPE_HOST);
 239
 240        clk_disable_unprepare(s5p_ehci->clk);
 241
 242        return rc;
 243}
 244
 245static int s5p_ehci_resume(struct device *dev)
 246{
 247        struct usb_hcd *hcd = dev_get_drvdata(dev);
 248        struct  s5p_ehci_hcd *s5p_ehci = to_s5p_ehci(hcd);
 249        struct platform_device *pdev = to_platform_device(dev);
 250
 251        clk_prepare_enable(s5p_ehci->clk);
 252
 253        if (s5p_ehci->otg)
 254                s5p_ehci->otg->set_host(s5p_ehci->otg, &hcd->self);
 255
 256        if (s5p_ehci->phy)
 257                usb_phy_init(s5p_ehci->phy);
 258        else if (s5p_ehci->pdata->phy_init)
 259                s5p_ehci->pdata->phy_init(pdev, USB_PHY_TYPE_HOST);
 260
 261        /* DMA burst Enable */
 262        writel(EHCI_INSNREG00_ENABLE_DMA_BURST, EHCI_INSNREG00(hcd->regs));
 263
 264        ehci_resume(hcd, false);
 265        return 0;
 266}
 267#else
 268#define s5p_ehci_suspend        NULL
 269#define s5p_ehci_resume         NULL
 270#endif
 271
 272static const struct dev_pm_ops s5p_ehci_pm_ops = {
 273        .suspend        = s5p_ehci_suspend,
 274        .resume         = s5p_ehci_resume,
 275};
 276
 277#ifdef CONFIG_OF
 278static const struct of_device_id exynos_ehci_match[] = {
 279        { .compatible = "samsung,exynos4210-ehci" },
 280        {},
 281};
 282MODULE_DEVICE_TABLE(of, exynos_ehci_match);
 283#endif
 284
 285static struct platform_driver s5p_ehci_driver = {
 286        .probe          = s5p_ehci_probe,
 287        .remove         = s5p_ehci_remove,
 288        .shutdown       = s5p_ehci_shutdown,
 289        .driver = {
 290                .name   = "s5p-ehci",
 291                .owner  = THIS_MODULE,
 292                .pm     = &s5p_ehci_pm_ops,
 293                .of_match_table = of_match_ptr(exynos_ehci_match),
 294        }
 295};
 296static const struct ehci_driver_overrides s5p_overrides __initdata = {
 297        .extra_priv_size = sizeof(struct s5p_ehci_hcd),
 298};
 299
 300static int __init ehci_s5p_init(void)
 301{
 302        if (usb_disabled())
 303                return -ENODEV;
 304
 305        pr_info("%s: " DRIVER_DESC "\n", hcd_name);
 306        ehci_init_driver(&s5p_ehci_hc_driver, &s5p_overrides);
 307        return platform_driver_register(&s5p_ehci_driver);
 308}
 309module_init(ehci_s5p_init);
 310
 311static void __exit ehci_s5p_cleanup(void)
 312{
 313        platform_driver_unregister(&s5p_ehci_driver);
 314}
 315module_exit(ehci_s5p_cleanup);
 316
 317MODULE_DESCRIPTION(DRIVER_DESC);
 318MODULE_ALIAS("platform:s5p-ehci");
 319MODULE_AUTHOR("Jingoo Han");
 320MODULE_AUTHOR("Joonyoung Shim");
 321MODULE_LICENSE("GPL v2");
 322