linux/drivers/usb/cdns3/cdns3-plat.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * Cadence USBSS DRD Driver.
   4 *
   5 * Copyright (C) 2018-2020 Cadence.
   6 * Copyright (C) 2017-2018 NXP
   7 * Copyright (C) 2019 Texas Instruments
   8 *
   9 *
  10 * Author: Peter Chen <peter.chen@nxp.com>
  11 *         Pawel Laszczak <pawell@cadence.com>
  12 *         Roger Quadros <rogerq@ti.com>
  13 */
  14
  15#include <linux/module.h>
  16#include <linux/kernel.h>
  17#include <linux/platform_device.h>
  18#include <linux/pm_runtime.h>
  19
  20#include "core.h"
  21#include "gadget-export.h"
  22#include "drd.h"
  23
  24static int set_phy_power_on(struct cdns *cdns)
  25{
  26        int ret;
  27
  28        ret = phy_power_on(cdns->usb2_phy);
  29        if (ret)
  30                return ret;
  31
  32        ret = phy_power_on(cdns->usb3_phy);
  33        if (ret)
  34                phy_power_off(cdns->usb2_phy);
  35
  36        return ret;
  37}
  38
  39static void set_phy_power_off(struct cdns *cdns)
  40{
  41        phy_power_off(cdns->usb3_phy);
  42        phy_power_off(cdns->usb2_phy);
  43}
  44
  45/**
  46 * cdns3_plat_probe - probe for cdns3 core device
  47 * @pdev: Pointer to cdns3 core platform device
  48 *
  49 * Returns 0 on success otherwise negative errno
  50 */
  51static int cdns3_plat_probe(struct platform_device *pdev)
  52{
  53        struct device *dev = &pdev->dev;
  54        struct resource *res;
  55        struct cdns *cdns;
  56        void __iomem *regs;
  57        int ret;
  58
  59        cdns = devm_kzalloc(dev, sizeof(*cdns), GFP_KERNEL);
  60        if (!cdns)
  61                return -ENOMEM;
  62
  63        cdns->dev = dev;
  64        cdns->pdata = dev_get_platdata(dev);
  65
  66        platform_set_drvdata(pdev, cdns);
  67
  68        res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "host");
  69        if (!res) {
  70                dev_err(dev, "missing host IRQ\n");
  71                return -ENODEV;
  72        }
  73
  74        cdns->xhci_res[0] = *res;
  75
  76        res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "xhci");
  77        if (!res) {
  78                dev_err(dev, "couldn't get xhci resource\n");
  79                return -ENXIO;
  80        }
  81
  82        cdns->xhci_res[1] = *res;
  83
  84        cdns->dev_irq = platform_get_irq_byname(pdev, "peripheral");
  85
  86        if (cdns->dev_irq < 0)
  87                return cdns->dev_irq;
  88
  89        regs = devm_platform_ioremap_resource_byname(pdev, "dev");
  90        if (IS_ERR(regs))
  91                return PTR_ERR(regs);
  92        cdns->dev_regs  = regs;
  93
  94        cdns->otg_irq = platform_get_irq_byname(pdev, "otg");
  95        if (cdns->otg_irq < 0)
  96                return cdns->otg_irq;
  97
  98        res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "otg");
  99        if (!res) {
 100                dev_err(dev, "couldn't get otg resource\n");
 101                return -ENXIO;
 102        }
 103
 104        cdns->phyrst_a_enable = device_property_read_bool(dev, "cdns,phyrst-a-enable");
 105
 106        cdns->otg_res = *res;
 107
 108        cdns->wakeup_irq = platform_get_irq_byname_optional(pdev, "wakeup");
 109        if (cdns->wakeup_irq == -EPROBE_DEFER)
 110                return cdns->wakeup_irq;
 111        else if (cdns->wakeup_irq == 0)
 112                return -EINVAL;
 113
 114        if (cdns->wakeup_irq < 0) {
 115                dev_dbg(dev, "couldn't get wakeup irq\n");
 116                cdns->wakeup_irq = 0x0;
 117        }
 118
 119        cdns->usb2_phy = devm_phy_optional_get(dev, "cdns3,usb2-phy");
 120        if (IS_ERR(cdns->usb2_phy))
 121                return PTR_ERR(cdns->usb2_phy);
 122
 123        ret = phy_init(cdns->usb2_phy);
 124        if (ret)
 125                return ret;
 126
 127        cdns->usb3_phy = devm_phy_optional_get(dev, "cdns3,usb3-phy");
 128        if (IS_ERR(cdns->usb3_phy))
 129                return PTR_ERR(cdns->usb3_phy);
 130
 131        ret = phy_init(cdns->usb3_phy);
 132        if (ret)
 133                goto err_phy3_init;
 134
 135        ret = set_phy_power_on(cdns);
 136        if (ret)
 137                goto err_phy_power_on;
 138
 139        cdns->gadget_init = cdns3_gadget_init;
 140
 141        ret = cdns_init(cdns);
 142        if (ret)
 143                goto err_cdns_init;
 144
 145        device_set_wakeup_capable(dev, true);
 146        pm_runtime_set_active(dev);
 147        pm_runtime_enable(dev);
 148        if (!(cdns->pdata && (cdns->pdata->quirks & CDNS3_DEFAULT_PM_RUNTIME_ALLOW)))
 149                pm_runtime_forbid(dev);
 150
 151        /*
 152         * The controller needs less time between bus and controller suspend,
 153         * and we also needs a small delay to avoid frequently entering low
 154         * power mode.
 155         */
 156        pm_runtime_set_autosuspend_delay(dev, 20);
 157        pm_runtime_mark_last_busy(dev);
 158        pm_runtime_use_autosuspend(dev);
 159
 160        return 0;
 161
 162err_cdns_init:
 163        set_phy_power_off(cdns);
 164err_phy_power_on:
 165        phy_exit(cdns->usb3_phy);
 166err_phy3_init:
 167        phy_exit(cdns->usb2_phy);
 168
 169        return ret;
 170}
 171
 172/**
 173 * cdns3_plat_remove() - unbind drd driver and clean up
 174 * @pdev: Pointer to Linux platform device
 175 *
 176 * Returns 0 on success otherwise negative errno
 177 */
 178static int cdns3_plat_remove(struct platform_device *pdev)
 179{
 180        struct cdns *cdns = platform_get_drvdata(pdev);
 181        struct device *dev = cdns->dev;
 182
 183        pm_runtime_get_sync(dev);
 184        pm_runtime_disable(dev);
 185        pm_runtime_put_noidle(dev);
 186        cdns_remove(cdns);
 187        set_phy_power_off(cdns);
 188        phy_exit(cdns->usb2_phy);
 189        phy_exit(cdns->usb3_phy);
 190        return 0;
 191}
 192
 193#ifdef CONFIG_PM
 194
 195static int cdns3_set_platform_suspend(struct device *dev,
 196                                      bool suspend, bool wakeup)
 197{
 198        struct cdns *cdns = dev_get_drvdata(dev);
 199        int ret = 0;
 200
 201        if (cdns->pdata && cdns->pdata->platform_suspend)
 202                ret = cdns->pdata->platform_suspend(dev, suspend, wakeup);
 203
 204        return ret;
 205}
 206
 207static int cdns3_controller_suspend(struct device *dev, pm_message_t msg)
 208{
 209        struct cdns *cdns = dev_get_drvdata(dev);
 210        bool wakeup;
 211        unsigned long flags;
 212
 213        if (cdns->in_lpm)
 214                return 0;
 215
 216        if (PMSG_IS_AUTO(msg))
 217                wakeup = true;
 218        else
 219                wakeup = device_may_wakeup(dev);
 220
 221        cdns3_set_platform_suspend(cdns->dev, true, wakeup);
 222        set_phy_power_off(cdns);
 223        spin_lock_irqsave(&cdns->lock, flags);
 224        cdns->in_lpm = true;
 225        spin_unlock_irqrestore(&cdns->lock, flags);
 226        dev_dbg(cdns->dev, "%s ends\n", __func__);
 227
 228        return 0;
 229}
 230
 231static int cdns3_controller_resume(struct device *dev, pm_message_t msg)
 232{
 233        struct cdns *cdns = dev_get_drvdata(dev);
 234        int ret;
 235        unsigned long flags;
 236
 237        if (!cdns->in_lpm)
 238                return 0;
 239
 240        if (cdns_power_is_lost(cdns)) {
 241                phy_exit(cdns->usb2_phy);
 242                ret = phy_init(cdns->usb2_phy);
 243                if (ret)
 244                        return ret;
 245
 246                phy_exit(cdns->usb3_phy);
 247                ret = phy_init(cdns->usb3_phy);
 248                if (ret)
 249                        return ret;
 250        }
 251
 252        ret = set_phy_power_on(cdns);
 253        if (ret)
 254                return ret;
 255
 256        cdns3_set_platform_suspend(cdns->dev, false, false);
 257
 258        spin_lock_irqsave(&cdns->lock, flags);
 259        cdns_resume(cdns, !PMSG_IS_AUTO(msg));
 260        cdns->in_lpm = false;
 261        spin_unlock_irqrestore(&cdns->lock, flags);
 262        if (cdns->wakeup_pending) {
 263                cdns->wakeup_pending = false;
 264                enable_irq(cdns->wakeup_irq);
 265        }
 266        dev_dbg(cdns->dev, "%s ends\n", __func__);
 267
 268        return ret;
 269}
 270
 271static int cdns3_plat_runtime_suspend(struct device *dev)
 272{
 273        return cdns3_controller_suspend(dev, PMSG_AUTO_SUSPEND);
 274}
 275
 276static int cdns3_plat_runtime_resume(struct device *dev)
 277{
 278        return cdns3_controller_resume(dev, PMSG_AUTO_RESUME);
 279}
 280
 281#ifdef CONFIG_PM_SLEEP
 282
 283static int cdns3_plat_suspend(struct device *dev)
 284{
 285        struct cdns *cdns = dev_get_drvdata(dev);
 286        int ret;
 287
 288        cdns_suspend(cdns);
 289
 290        ret = cdns3_controller_suspend(dev, PMSG_SUSPEND);
 291        if (ret)
 292                return ret;
 293
 294        if (device_may_wakeup(dev) && cdns->wakeup_irq)
 295                enable_irq_wake(cdns->wakeup_irq);
 296
 297        return ret;
 298}
 299
 300static int cdns3_plat_resume(struct device *dev)
 301{
 302        return cdns3_controller_resume(dev, PMSG_RESUME);
 303}
 304#endif /* CONFIG_PM_SLEEP */
 305#endif /* CONFIG_PM */
 306
 307static const struct dev_pm_ops cdns3_pm_ops = {
 308        SET_SYSTEM_SLEEP_PM_OPS(cdns3_plat_suspend, cdns3_plat_resume)
 309        SET_RUNTIME_PM_OPS(cdns3_plat_runtime_suspend,
 310                           cdns3_plat_runtime_resume, NULL)
 311};
 312
 313#ifdef CONFIG_OF
 314static const struct of_device_id of_cdns3_match[] = {
 315        { .compatible = "cdns,usb3" },
 316        { },
 317};
 318MODULE_DEVICE_TABLE(of, of_cdns3_match);
 319#endif
 320
 321static struct platform_driver cdns3_driver = {
 322        .probe          = cdns3_plat_probe,
 323        .remove         = cdns3_plat_remove,
 324        .driver         = {
 325                .name   = "cdns-usb3",
 326                .of_match_table = of_match_ptr(of_cdns3_match),
 327                .pm     = &cdns3_pm_ops,
 328        },
 329};
 330
 331module_platform_driver(cdns3_driver);
 332
 333MODULE_ALIAS("platform:cdns3");
 334MODULE_AUTHOR("Pawel Laszczak <pawell@cadence.com>");
 335MODULE_LICENSE("GPL v2");
 336MODULE_DESCRIPTION("Cadence USB3 DRD Controller Driver");
 337