root/drivers/usb/dwc2/platform.c
// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
/*
 * platform.c - DesignWare HS OTG Controller platform driver
 *
 * Copyright (C) Matthijs Kooijman <matthijs@stdin.nl>
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/of.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/phy/phy.h>
#include <linux/platform_data/s3c-hsotg.h>
#include <linux/reset.h>

#include <linux/usb/of.h>

#include "core.h"
#include "hcd.h"
#include "debug.h"

static const char dwc2_driver_name[] = "dwc2";

/*
 * Check the dr_mode against the module configuration and hardware
 * capabilities.
 *
 * The hardware, module, and dr_mode, can each be set to host, device,
 * or otg. Check that all these values are compatible and adjust the
 * value of dr_mode if possible.
 *
 *                      actual
 *    HW  MOD dr_mode   dr_mode
 *  ------------------------------
 *   HST  HST  any    :  HST
 *   HST  DEV  any    :  ---
 *   HST  OTG  any    :  HST
 *
 *   DEV  HST  any    :  ---
 *   DEV  DEV  any    :  DEV
 *   DEV  OTG  any    :  DEV
 *
 *   OTG  HST  any    :  HST
 *   OTG  DEV  any    :  DEV
 *   OTG  OTG  any    :  dr_mode
 */
static int dwc2_get_dr_mode(struct dwc2_hsotg *hsotg)
{
        enum usb_dr_mode mode;

        hsotg->dr_mode = usb_get_dr_mode(hsotg->dev);
        if (hsotg->dr_mode == USB_DR_MODE_UNKNOWN)
                hsotg->dr_mode = USB_DR_MODE_OTG;

        mode = hsotg->dr_mode;

        if (dwc2_hw_is_device(hsotg)) {
                if (IS_ENABLED(CONFIG_USB_DWC2_HOST)) {
                        dev_err(hsotg->dev,
                                "Controller does not support host mode.\n");
                        return -EINVAL;
                }
                mode = USB_DR_MODE_PERIPHERAL;
        } else if (dwc2_hw_is_host(hsotg)) {
                if (IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL)) {
                        dev_err(hsotg->dev,
                                "Controller does not support device mode.\n");
                        return -EINVAL;
                }
                mode = USB_DR_MODE_HOST;
        } else {
                if (IS_ENABLED(CONFIG_USB_DWC2_HOST))
                        mode = USB_DR_MODE_HOST;
                else if (IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL))
                        mode = USB_DR_MODE_PERIPHERAL;
        }

        if (mode != hsotg->dr_mode) {
                dev_warn(hsotg->dev,
                         "Configuration mismatch. dr_mode forced to %s\n",
                        mode == USB_DR_MODE_HOST ? "host" : "device");

                hsotg->dr_mode = mode;
        }

        return 0;
}

static int __dwc2_lowlevel_hw_enable(struct dwc2_hsotg *hsotg)
{
        struct platform_device *pdev = to_platform_device(hsotg->dev);
        int ret;

        ret = regulator_bulk_enable(ARRAY_SIZE(hsotg->supplies),
                                    hsotg->supplies);
        if (ret)
                return ret;

        if (hsotg->utmi_clk) {
                ret = clk_prepare_enable(hsotg->utmi_clk);
                if (ret)
                        goto err_dis_reg;
        }

        if (hsotg->clk) {
                ret = clk_prepare_enable(hsotg->clk);
                if (ret)
                        goto err_dis_utmi_clk;
        }

        if (hsotg->uphy) {
                ret = usb_phy_init(hsotg->uphy);
        } else if (hsotg->plat && hsotg->plat->phy_init) {
                ret = hsotg->plat->phy_init(pdev, hsotg->plat->phy_type);
        } else {
                ret = phy_init(hsotg->phy);
                if (ret == 0) {
                        ret = phy_power_on(hsotg->phy);
                        if (ret)
                                phy_exit(hsotg->phy);
                }
        }

        if (ret)
                goto err_dis_clk;

        return 0;

err_dis_clk:
        if (hsotg->clk)
                clk_disable_unprepare(hsotg->clk);

err_dis_utmi_clk:
        if (hsotg->utmi_clk)
                clk_disable_unprepare(hsotg->utmi_clk);

err_dis_reg:
        regulator_bulk_disable(ARRAY_SIZE(hsotg->supplies), hsotg->supplies);

        return ret;
}

/**
 * dwc2_lowlevel_hw_enable - enable platform lowlevel hw resources
 * @hsotg: The driver state
 *
 * A wrapper for platform code responsible for controlling
 * low-level USB platform resources (phy, clock, regulators)
 */
int dwc2_lowlevel_hw_enable(struct dwc2_hsotg *hsotg)
{
        int ret = __dwc2_lowlevel_hw_enable(hsotg);

        if (ret == 0)
                hsotg->ll_hw_enabled = true;
        return ret;
}

static int __dwc2_lowlevel_hw_disable(struct dwc2_hsotg *hsotg)
{
        struct platform_device *pdev = to_platform_device(hsotg->dev);
        int ret = 0;

        if (hsotg->uphy) {
                usb_phy_shutdown(hsotg->uphy);
        } else if (hsotg->plat && hsotg->plat->phy_exit) {
                ret = hsotg->plat->phy_exit(pdev, hsotg->plat->phy_type);
        } else {
                ret = phy_power_off(hsotg->phy);
                if (ret == 0)
                        ret = phy_exit(hsotg->phy);
        }
        if (ret)
                return ret;

        if (hsotg->clk)
                clk_disable_unprepare(hsotg->clk);

        if (hsotg->utmi_clk)
                clk_disable_unprepare(hsotg->utmi_clk);

        return regulator_bulk_disable(ARRAY_SIZE(hsotg->supplies), hsotg->supplies);
}

/**
 * dwc2_lowlevel_hw_disable - disable platform lowlevel hw resources
 * @hsotg: The driver state
 *
 * A wrapper for platform code responsible for controlling
 * low-level USB platform resources (phy, clock, regulators)
 */
int dwc2_lowlevel_hw_disable(struct dwc2_hsotg *hsotg)
{
        int ret = __dwc2_lowlevel_hw_disable(hsotg);

        if (ret == 0)
                hsotg->ll_hw_enabled = false;
        return ret;
}

static void dwc2_reset_control_assert(void *data)
{
        reset_control_assert(data);
}

static int dwc2_lowlevel_hw_init(struct dwc2_hsotg *hsotg)
{
        int i, ret;

        hsotg->reset = devm_reset_control_get_optional(hsotg->dev, "dwc2");
        if (IS_ERR(hsotg->reset))
                return dev_err_probe(hsotg->dev, PTR_ERR(hsotg->reset),
                                     "error getting reset control\n");

        reset_control_deassert(hsotg->reset);
        ret = devm_add_action_or_reset(hsotg->dev, dwc2_reset_control_assert,
                                       hsotg->reset);
        if (ret)
                return ret;

        hsotg->reset_ecc = devm_reset_control_get_optional(hsotg->dev, "dwc2-ecc");
        if (IS_ERR(hsotg->reset_ecc))
                return dev_err_probe(hsotg->dev, PTR_ERR(hsotg->reset_ecc),
                                     "error getting reset control for ecc\n");

        reset_control_deassert(hsotg->reset_ecc);
        ret = devm_add_action_or_reset(hsotg->dev, dwc2_reset_control_assert,
                                       hsotg->reset_ecc);
        if (ret)
                return ret;

        /*
         * Attempt to find a generic PHY, then look for an old style
         * USB PHY and then fall back to pdata
         */
        hsotg->phy = devm_phy_get(hsotg->dev, "usb2-phy");
        if (IS_ERR(hsotg->phy)) {
                ret = PTR_ERR(hsotg->phy);
                switch (ret) {
                case -ENODEV:
                case -ENOSYS:
                        hsotg->phy = NULL;
                        break;
                default:
                        return dev_err_probe(hsotg->dev, ret, "error getting phy\n");
                }
        }

        if (!hsotg->phy) {
                hsotg->uphy = devm_usb_get_phy(hsotg->dev, USB_PHY_TYPE_USB2);
                if (IS_ERR(hsotg->uphy)) {
                        ret = PTR_ERR(hsotg->uphy);
                        switch (ret) {
                        case -ENODEV:
                        case -ENXIO:
                                hsotg->uphy = NULL;
                                break;
                        default:
                                return dev_err_probe(hsotg->dev, ret, "error getting usb phy\n");
                        }
                }
        }

        hsotg->plat = dev_get_platdata(hsotg->dev);

        /* Clock */
        hsotg->clk = devm_clk_get_optional(hsotg->dev, "otg");
        if (IS_ERR(hsotg->clk))
                return dev_err_probe(hsotg->dev, PTR_ERR(hsotg->clk), "cannot get otg clock\n");

        hsotg->utmi_clk = devm_clk_get_optional(hsotg->dev, "utmi");
        if (IS_ERR(hsotg->utmi_clk))
                return dev_err_probe(hsotg->dev, PTR_ERR(hsotg->utmi_clk),
                                     "cannot get utmi clock\n");

        /* Regulators */
        for (i = 0; i < ARRAY_SIZE(hsotg->supplies); i++)
                hsotg->supplies[i].supply = dwc2_hsotg_supply_names[i];

        ret = devm_regulator_bulk_get(hsotg->dev, ARRAY_SIZE(hsotg->supplies),
                                      hsotg->supplies);
        if (ret)
                return dev_err_probe(hsotg->dev, ret, "failed to request supplies\n");

        return 0;
}

/**
 * dwc2_driver_remove() - Called when the DWC_otg core is unregistered with the
 * DWC_otg driver
 *
 * @dev: Platform device
 *
 * This routine is called, for example, when the rmmod command is executed. The
 * device may or may not be electrically present. If it is present, the driver
 * stops device processing. Any resources used on behalf of this device are
 * freed.
 */
static void dwc2_driver_remove(struct platform_device *dev)
{
        struct dwc2_hsotg *hsotg = platform_get_drvdata(dev);
        struct dwc2_gregs_backup *gr;
        int ret = 0;

        gr = &hsotg->gr_backup;

        /* Exit Hibernation when driver is removed. */
        if (hsotg->hibernated) {
                if (gr->gotgctl & GOTGCTL_CURMODE_HOST)
                        ret = dwc2_exit_hibernation(hsotg, 0, 0, 1);
                else
                        ret = dwc2_exit_hibernation(hsotg, 0, 0, 0);

                if (ret)
                        dev_err(hsotg->dev,
                                "exit hibernation failed.\n");
        }

        /* Exit Partial Power Down when driver is removed. */
        if (hsotg->in_ppd) {
                ret = dwc2_exit_partial_power_down(hsotg, 0, true);
                if (ret)
                        dev_err(hsotg->dev,
                                "exit partial_power_down failed\n");
        }

        /* Exit clock gating when driver is removed. */
        if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_NONE &&
            hsotg->bus_suspended && !hsotg->params.no_clock_gating) {
                if (dwc2_is_device_mode(hsotg))
                        dwc2_gadget_exit_clock_gating(hsotg, 0);
                else
                        dwc2_host_exit_clock_gating(hsotg, 0);
        }

        dwc2_debugfs_exit(hsotg);
        if (hsotg->hcd_enabled)
                dwc2_hcd_remove(hsotg);
        if (hsotg->gadget_enabled)
                dwc2_hsotg_remove(hsotg);

        dwc2_drd_exit(hsotg);

        if (hsotg->params.activate_stm_id_vb_detection)
                regulator_disable(hsotg->usb33d);

        if (hsotg->ll_hw_enabled)
                dwc2_lowlevel_hw_disable(hsotg);
}

/**
 * dwc2_driver_shutdown() - Called on device shutdown
 *
 * @dev: Platform device
 *
 * In specific conditions (involving usb hubs) dwc2 devices can create a
 * lot of interrupts, even to the point of overwhelming devices running
 * at low frequencies. Some devices need to do special clock handling
 * at shutdown-time which may bring the system clock below the threshold
 * of being able to handle the dwc2 interrupts. Disabling dwc2-irqs
 * prevents reboots/poweroffs from getting stuck in such cases.
 */
static void dwc2_driver_shutdown(struct platform_device *dev)
{
        struct dwc2_hsotg *hsotg = platform_get_drvdata(dev);

        if (hsotg->ll_hw_enabled) {
                dwc2_disable_global_interrupts(hsotg);
                synchronize_irq(hsotg->irq);
                dwc2_lowlevel_hw_disable(hsotg);
        }
}

/**
 * dwc2_check_core_endianness() - Returns true if core and AHB have
 * opposite endianness.
 * @hsotg:      Programming view of the DWC_otg controller.
 */
static bool dwc2_check_core_endianness(struct dwc2_hsotg *hsotg)
{
        u32 snpsid;

        snpsid = ioread32(hsotg->regs + GSNPSID);
        if ((snpsid & GSNPSID_ID_MASK) == DWC2_OTG_ID ||
            (snpsid & GSNPSID_ID_MASK) == DWC2_FS_IOT_ID ||
            (snpsid & GSNPSID_ID_MASK) == DWC2_HS_IOT_ID)
                return false;
        return true;
}

/**
 * dwc2_check_core_version() - Check core version
 *
 * @hsotg: Programming view of the DWC_otg controller
 *
 */
int dwc2_check_core_version(struct dwc2_hsotg *hsotg)
{
        struct dwc2_hw_params *hw = &hsotg->hw_params;

        /*
         * Attempt to ensure this device is really a DWC_otg Controller.
         * Read and verify the GSNPSID register contents. The value should be
         * 0x45f4xxxx, 0x5531xxxx or 0x5532xxxx
         */

        hw->snpsid = dwc2_readl(hsotg, GSNPSID);
        if ((hw->snpsid & GSNPSID_ID_MASK) != DWC2_OTG_ID &&
            (hw->snpsid & GSNPSID_ID_MASK) != DWC2_FS_IOT_ID &&
            (hw->snpsid & GSNPSID_ID_MASK) != DWC2_HS_IOT_ID) {
                dev_err(hsotg->dev, "Bad value for GSNPSID: 0x%08x\n",
                        hw->snpsid);
                return -ENODEV;
        }

        dev_dbg(hsotg->dev, "Core Release: %1x.%1x%1x%1x (snpsid=%x)\n",
                hw->snpsid >> 12 & 0xf, hw->snpsid >> 8 & 0xf,
                hw->snpsid >> 4 & 0xf, hw->snpsid & 0xf, hw->snpsid);
        return 0;
}

/**
 * dwc2_driver_probe() - Called when the DWC_otg core is bound to the DWC_otg
 * driver
 *
 * @dev: Platform device
 *
 * This routine creates the driver components required to control the device
 * (core, HCD, and PCD) and initializes the device. The driver components are
 * stored in a dwc2_hsotg structure. A reference to the dwc2_hsotg is saved
 * in the device private data. This allows the driver to access the dwc2_hsotg
 * structure on subsequent calls to driver methods for this device.
 */
static int dwc2_driver_probe(struct platform_device *dev)
{
        struct dwc2_hsotg *hsotg;
        struct resource *res;
        int retval;

        hsotg = devm_kzalloc(&dev->dev, sizeof(*hsotg), GFP_KERNEL);
        if (!hsotg)
                return -ENOMEM;

        hsotg->dev = &dev->dev;

        /*
         * Use reasonable defaults so platforms don't have to provide these.
         */
        if (!dev->dev.dma_mask)
                dev->dev.dma_mask = &dev->dev.coherent_dma_mask;
        retval = dma_set_coherent_mask(&dev->dev, DMA_BIT_MASK(32));
        if (retval) {
                dev_err(&dev->dev, "can't set coherent DMA mask: %d\n", retval);
                return retval;
        }

        hsotg->regs = devm_platform_get_and_ioremap_resource(dev, 0, &res);
        if (IS_ERR(hsotg->regs))
                return PTR_ERR(hsotg->regs);

        dev_dbg(&dev->dev, "mapped PA %08lx to VA %p\n",
                (unsigned long)res->start, hsotg->regs);

        retval = dwc2_lowlevel_hw_init(hsotg);
        if (retval)
                return retval;

        spin_lock_init(&hsotg->lock);

        hsotg->vbus_supply = devm_regulator_get_optional(hsotg->dev, "vbus");
        if (IS_ERR(hsotg->vbus_supply)) {
                retval = PTR_ERR(hsotg->vbus_supply);
                hsotg->vbus_supply = NULL;
                if (retval != -ENODEV)
                        return retval;
        }

        retval = dwc2_lowlevel_hw_enable(hsotg);
        if (retval)
                return retval;

        hsotg->needs_byte_swap = dwc2_check_core_endianness(hsotg);

        retval = dwc2_get_dr_mode(hsotg);
        if (retval)
                goto error;

        hsotg->need_phy_for_wake =
                of_property_read_bool(dev->dev.of_node,
                                      "snps,need-phy-for-wake");

        /*
         * Before performing any core related operations
         * check core version.
         */
        retval = dwc2_check_core_version(hsotg);
        if (retval)
                goto error;

        /*
         * Reset before dwc2_get_hwparams() then it could get power-on real
         * reset value form registers.
         */
        retval = dwc2_core_reset(hsotg, false);
        if (retval)
                goto error;

        /* Detect config values from hardware */
        retval = dwc2_get_hwparams(hsotg);
        if (retval)
                goto error;

        hsotg->irq = platform_get_irq(dev, 0);
        if (hsotg->irq < 0) {
                retval = hsotg->irq;
                goto error;
        }

        dev_dbg(hsotg->dev, "registering common handler for irq%d\n",
                hsotg->irq);
        retval = devm_request_irq(hsotg->dev, hsotg->irq,
                                  dwc2_handle_common_intr, IRQF_SHARED,
                                  dev_name(hsotg->dev), hsotg);
        if (retval)
                goto error;

        /*
         * For OTG cores, set the force mode bits to reflect the value
         * of dr_mode. Force mode bits should not be touched at any
         * other time after this.
         */
        dwc2_force_dr_mode(hsotg);

        retval = dwc2_init_params(hsotg);
        if (retval)
                goto error;

        if (hsotg->params.activate_stm_id_vb_detection) {
                u32 ggpio;

                hsotg->usb33d = devm_regulator_get(hsotg->dev, "usb33d");
                if (IS_ERR(hsotg->usb33d)) {
                        retval = PTR_ERR(hsotg->usb33d);
                        dev_err_probe(hsotg->dev, retval, "failed to request usb33d supply\n");
                        goto error;
                }
                retval = regulator_enable(hsotg->usb33d);
                if (retval) {
                        dev_err_probe(hsotg->dev, retval, "failed to enable usb33d supply\n");
                        goto error;
                }

                ggpio = dwc2_readl(hsotg, GGPIO);
                ggpio |= GGPIO_STM32_OTG_GCCFG_IDEN;
                ggpio |= GGPIO_STM32_OTG_GCCFG_VBDEN;
                dwc2_writel(hsotg, ggpio, GGPIO);

                /* ID/VBUS detection startup time */
                usleep_range(5000, 7000);
        }

        retval = dwc2_drd_init(hsotg);
        if (retval) {
                dev_err_probe(hsotg->dev, retval, "failed to initialize dual-role\n");
                goto error_init;
        }

        if (hsotg->dr_mode != USB_DR_MODE_HOST) {
                retval = dwc2_gadget_init(hsotg);
                if (retval)
                        goto error_drd;
                hsotg->gadget_enabled = 1;
        }

        /*
         * If we need PHY for wakeup we must be wakeup capable.
         * When we have a device that can wake without the PHY we
         * can adjust this condition.
         */
        if (hsotg->need_phy_for_wake)
                device_set_wakeup_capable(&dev->dev, true);

        hsotg->reset_phy_on_wake =
                of_property_read_bool(dev->dev.of_node,
                                      "snps,reset-phy-on-wake");
        if (hsotg->reset_phy_on_wake && !hsotg->phy) {
                dev_warn(hsotg->dev,
                         "Quirk reset-phy-on-wake only supports generic PHYs\n");
                hsotg->reset_phy_on_wake = false;
        }

        if (hsotg->dr_mode != USB_DR_MODE_PERIPHERAL) {
                retval = dwc2_hcd_init(hsotg);
                if (retval) {
                        if (hsotg->gadget_enabled)
                                dwc2_hsotg_remove(hsotg);
                        goto error_drd;
                }
                hsotg->hcd_enabled = 1;
        }

        platform_set_drvdata(dev, hsotg);
        hsotg->hibernated = 0;

        dwc2_debugfs_init(hsotg);

        /* Gadget code manages lowlevel hw on its own */
        if (hsotg->dr_mode == USB_DR_MODE_PERIPHERAL)
                dwc2_lowlevel_hw_disable(hsotg);

#if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || \
        IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE)
        /* Postponed adding a new gadget to the udc class driver list */
        if (hsotg->gadget_enabled) {
                retval = usb_add_gadget_udc(hsotg->dev, &hsotg->gadget);
                if (retval) {
                        hsotg->gadget.udc = NULL;
                        dwc2_hsotg_remove(hsotg);
                        goto error_debugfs;
                }
        }
#endif /* CONFIG_USB_DWC2_PERIPHERAL || CONFIG_USB_DWC2_DUAL_ROLE */
        return 0;

#if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || \
        IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE)
error_debugfs:
        dwc2_debugfs_exit(hsotg);
        if (hsotg->hcd_enabled)
                dwc2_hcd_remove(hsotg);
#endif
error_drd:
        dwc2_drd_exit(hsotg);

error_init:
        if (hsotg->params.activate_stm_id_vb_detection)
                regulator_disable(hsotg->usb33d);
error:
        if (hsotg->ll_hw_enabled)
                dwc2_lowlevel_hw_disable(hsotg);
        return retval;
}

static int __maybe_unused dwc2_suspend(struct device *dev)
{
        struct dwc2_hsotg *dwc2 = dev_get_drvdata(dev);
        bool is_device_mode;
        int ret = 0;

        if (!dwc2->ll_hw_enabled)
                return 0;

        is_device_mode = dwc2_is_device_mode(dwc2);
        if (is_device_mode)
                dwc2_hsotg_suspend(dwc2);

        dwc2_drd_suspend(dwc2);

        if (dwc2->params.activate_stm_id_vb_detection) {
                unsigned long flags;
                u32 ggpio, gotgctl;

                /*
                 * Need to force the mode to the current mode to avoid Mode
                 * Mismatch Interrupt when ID detection will be disabled.
                 */
                dwc2_force_mode(dwc2, !is_device_mode);

                spin_lock_irqsave(&dwc2->lock, flags);
                gotgctl = dwc2_readl(dwc2, GOTGCTL);
                /* bypass debounce filter, enable overrides */
                gotgctl |= GOTGCTL_DBNCE_FLTR_BYPASS;
                gotgctl |= GOTGCTL_BVALOEN | GOTGCTL_AVALOEN;
                /* Force A / B session if needed */
                if (gotgctl & GOTGCTL_ASESVLD)
                        gotgctl |= GOTGCTL_AVALOVAL;
                if (gotgctl & GOTGCTL_BSESVLD)
                        gotgctl |= GOTGCTL_BVALOVAL;
                dwc2_writel(dwc2, gotgctl, GOTGCTL);
                spin_unlock_irqrestore(&dwc2->lock, flags);

                ggpio = dwc2_readl(dwc2, GGPIO);
                ggpio &= ~GGPIO_STM32_OTG_GCCFG_IDEN;
                ggpio &= ~GGPIO_STM32_OTG_GCCFG_VBDEN;
                dwc2_writel(dwc2, ggpio, GGPIO);

                regulator_disable(dwc2->usb33d);
        }

        if (is_device_mode)
                ret = dwc2_gadget_backup_critical_registers(dwc2);
        else
                ret = dwc2_host_backup_critical_registers(dwc2);

        if (ret)
                return ret;

        if (dwc2->ll_hw_enabled &&
            (is_device_mode || dwc2_host_can_poweroff_phy(dwc2))) {
                ret = __dwc2_lowlevel_hw_disable(dwc2);
                dwc2->phy_off_for_suspend = true;
        }

        return ret;
}

static int dwc2_restore_critical_registers(struct dwc2_hsotg *hsotg)
{
        struct dwc2_gregs_backup *gr;

        gr = &hsotg->gr_backup;

        if (!gr->valid) {
                dev_err(hsotg->dev, "No valid register backup, failed to restore\n");
                return -EINVAL;
        }

        if (gr->gintsts & GINTSTS_CURMODE_HOST)
                return dwc2_host_restore_critical_registers(hsotg);

        return dwc2_gadget_restore_critical_registers(hsotg, DWC2_RESTORE_DCTL |
                                                      DWC2_RESTORE_DCFG);
}

static int __maybe_unused dwc2_resume(struct device *dev)
{
        struct dwc2_hsotg *dwc2 = dev_get_drvdata(dev);
        int ret = 0;

        if (!dwc2->ll_hw_enabled)
                return 0;

        if (dwc2->phy_off_for_suspend && dwc2->ll_hw_enabled) {
                ret = __dwc2_lowlevel_hw_enable(dwc2);
                if (ret)
                        return ret;
        }
        dwc2->phy_off_for_suspend = false;

        /*
         * During suspend it's possible that the power domain for the
         * DWC2 controller is disabled and all register values get lost.
         * In case the GUSBCFG register is not initialized, it's clear the
         * registers must be restored.
         */
        if (!(dwc2_readl(dwc2, GUSBCFG) & GUSBCFG_TOUTCAL_MASK)) {
                ret = dwc2_restore_critical_registers(dwc2);
                if (ret)
                        return ret;
        }

        if (dwc2->params.activate_stm_id_vb_detection) {
                unsigned long flags;
                u32 ggpio, gotgctl;

                ret = regulator_enable(dwc2->usb33d);
                if (ret)
                        return ret;

                ggpio = dwc2_readl(dwc2, GGPIO);
                ggpio |= GGPIO_STM32_OTG_GCCFG_IDEN;
                ggpio |= GGPIO_STM32_OTG_GCCFG_VBDEN;
                dwc2_writel(dwc2, ggpio, GGPIO);

                /* ID/VBUS detection startup time */
                usleep_range(5000, 7000);

                spin_lock_irqsave(&dwc2->lock, flags);
                gotgctl = dwc2_readl(dwc2, GOTGCTL);
                gotgctl &= ~GOTGCTL_DBNCE_FLTR_BYPASS;
                gotgctl &= ~(GOTGCTL_BVALOEN | GOTGCTL_AVALOEN |
                             GOTGCTL_BVALOVAL | GOTGCTL_AVALOVAL);
                dwc2_writel(dwc2, gotgctl, GOTGCTL);
                spin_unlock_irqrestore(&dwc2->lock, flags);
        }

        if (!dwc2->role_sw) {
                /* Need to restore FORCEDEVMODE/FORCEHOSTMODE */
                dwc2_force_dr_mode(dwc2);
        } else {
                dwc2_drd_resume(dwc2);
        }

        if (dwc2_is_device_mode(dwc2))
                ret = dwc2_hsotg_resume(dwc2);

        return ret;
}

static const struct dev_pm_ops dwc2_dev_pm_ops = {
        SET_SYSTEM_SLEEP_PM_OPS(dwc2_suspend, dwc2_resume)
};

static struct platform_driver dwc2_platform_driver = {
        .driver = {
                .name = dwc2_driver_name,
                .of_match_table = dwc2_of_match_table,
                .acpi_match_table = ACPI_PTR(dwc2_acpi_match),
                .pm = &dwc2_dev_pm_ops,
        },
        .probe = dwc2_driver_probe,
        .remove = dwc2_driver_remove,
        .shutdown = dwc2_driver_shutdown,
};

module_platform_driver(dwc2_platform_driver);