// SPDX-License-Identifier: GPL-2.0 /** * dwc3-exynos.c - Samsung EXYNOS DWC3 Specific Glue layer * * Copyright (c) 2012 Samsung Electronics Co., Ltd. * http://www.samsung.com * * Author: Anton Tikhomirov */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "core-exynos.h" #include "dwc3-exynos.h" #include "io.h" #include "gadget.h" #include #include #include #include "exynos-otg.h" #include "dwc3-exynos.h" #ifdef CONFIG_OF #include #endif #if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) #include #endif #if IS_ENABLED(CONFIG_USB_NOTIFY_LAYER) #include #endif #include #if IS_ENABLED(CONFIG_USB_CONFIGFS_F_SS_MON_GADGET) #include #endif #include #if IS_ENABLED(CONFIG_SND_EXYNOS_USB_AUDIO_MODULE) #include "../../../sound/usb/exynos_usb_audio.h" struct host_data xhci_data; EXPORT_SYMBOL_GPL(xhci_data); #endif struct usb_xhci_pre_alloc xhci_pre_alloc; EXPORT_SYMBOL_GPL(xhci_pre_alloc); void __iomem *usb3_portsc; EXPORT_SYMBOL_GPL(usb3_portsc); /* -------------------------------------------------------------------------- */ /*struct dwc3_exynos_rsw { struct otg_fsm *fsm; struct work_struct work; }; struct dwc3_exynos { struct platform_device *usb2_phy; struct platform_device *usb3_phy; struct device *dev; struct dwc3 *dwc; struct clk **clocks; struct regulator *vdd33; struct regulator *vdd10; int idle_ip_index; struct dwc3_exynos_rsw rsw; }; */ extern void dwc3_otg_run_sm(struct otg_fsm *fsm); static const struct of_device_id exynos_dwc3_match[] = { { .compatible = "samsung,exynos-dwusb", }, {}, }; MODULE_DEVICE_TABLE(of, exynos_dwc3_match); static int dwc3_exynos_clk_get(struct dwc3_exynos *exynos) { struct device *dev = exynos->dev; const char **clk_ids; struct clk *clk; int clk_count; int ret, i; clk_count = of_property_count_strings(dev->of_node, "clock-names"); if (IS_ERR_VALUE((unsigned long)clk_count)) { dev_err(dev, "invalid clk list in %s node", dev->of_node->name); return -EINVAL; } clk_ids = (const char **)devm_kmalloc(dev, (clk_count + 1) * sizeof(const char *), GFP_KERNEL); if (!clk_ids) { dev_err(dev, "failed to alloc for clock ids"); return -ENOMEM; } for (i = 0; i < clk_count; i++) { ret = of_property_read_string_index(dev->of_node, "clock-names", i, &clk_ids[i]); if (ret) { dev_err(dev, "failed to read clocks name %d from %s node\n", i, dev->of_node->name); return ret; } /* * Check Bus clock to get clk node from DT. * CAUTION : Bus clock SHOULD be defiend at the last. */ if (!strncmp(clk_ids[i], "bus", 3)) { dev_info(dev, "BUS clock is defined.\n"); exynos->bus_clock = devm_clk_get(exynos->dev, clk_ids[i]); if (IS_ERR_OR_NULL(exynos->bus_clock)) dev_err(dev, "Can't get Bus clock.\n"); } if (!strncmp(clk_ids[i], "sclk", 4)) { dev_info(dev, "Source clock is defined.\n"); exynos->sclk_clock = devm_clk_get(exynos->dev, clk_ids[i]); if (IS_ERR_OR_NULL(exynos->sclk_clock)) dev_err(dev, "Can't get Source clock.\n"); } } clk_ids[clk_count] = NULL; exynos->clocks = (struct clk **) devm_kmalloc(exynos->dev, clk_count * sizeof(struct clk *), GFP_KERNEL); if (!exynos->clocks) { dev_err(exynos->dev, "%s: couldn't alloc\n", __func__); return -ENOMEM; } for (i = 0; clk_ids[i] != NULL; i++) { clk = devm_clk_get(exynos->dev, clk_ids[i]); if (IS_ERR_OR_NULL(clk)) goto err; exynos->clocks[i] = clk; } exynos->clocks[i] = NULL; return 0; err: dev_err(exynos->dev, "couldn't get %s clock\n", clk_ids[i]); return -EINVAL; } static int dwc3_exynos_clk_prepare(struct dwc3_exynos *exynos) { int i; int ret; for (i = 0; exynos->clocks[i] != NULL; i++) { ret = clk_prepare(exynos->clocks[i]); if (ret) goto err; } return 0; err: dev_err(exynos->dev, "couldn't prepare clock[%d]\n", i); /* roll back */ for (i = i - 1; i >= 0; i--) clk_unprepare(exynos->clocks[i]); return ret; } static int dwc3_exynos_clk_enable(struct dwc3_exynos *exynos) { int i; int ret; for (i = 0; exynos->clocks[i] != NULL; i++) { ret = clk_enable(exynos->clocks[i]); if (ret) goto err; } return 0; err: dev_err(exynos->dev, "couldn't enable clock[%d]\n", i); /* roll back */ for (i = i - 1; i >= 0; i--) clk_disable(exynos->clocks[i]); return ret; } static void dwc3_exynos_clk_unprepare(struct dwc3_exynos *exynos) { int i; for (i = 0; exynos->clocks[i] != NULL; i++) clk_unprepare(exynos->clocks[i]); } static void dwc3_exynos_clk_disable(struct dwc3_exynos *exynos) { int i; for (i = 0; exynos->clocks[i] != NULL; i++) clk_disable(exynos->clocks[i]); } static void dwc3_core_config(struct dwc3 *dwc, struct dwc3_exynos *exynos) { u32 reg, sclk; /* AHB bus configuration */ reg = dwc3_readl(dwc->regs, DWC3_GSBUSCFG0); reg |= (DWC3_GSBUSCFG0_INCRBRSTEN | DWC3_GSBUSCFG0_INCR16BRSTEN); /** * AXI Bus' cache type configuration for DMA transfer. * By below setting, cache type was set to Cacheable/Modifiable. * From DWC USB3.0 Link version 2.20A, this cache type could be set. */ if (!DWC3_VER_IS_PRIOR(DWC3, 220A)) reg |= (DWC3_GSBUSCFG0_DESWRREQINFO | DWC3_GSBUSCFG0_DATWRREQINFO | DWC3_GSBUSCFG0_DESRDREQINFO | DWC3_GSBUSCFG0_DATRDREQINFO); dwc3_writel(dwc->regs, DWC3_GSBUSCFG0, reg); reg = dwc3_readl(dwc->regs, DWC3_GSBUSCFG1); if (DWC3_VER_IS_PRIOR(DWC31, 180A)) { /* * Setting MO request limit to 8 resolved ITMON issue on MTP and DM functions * Further investigation should be done by design team. */ reg |= (DWC3_GSBUSCFG1_BREQLIMIT(0x8)); dwc3_writel(dwc->regs, DWC3_GSBUSCFG1, reg); } /* * WORKAROUND: * For ss bulk-in data packet, when the host detects * a DPP error or the internal buffer becomes full, * it retries with an ACK TP Retry=1. Under the following * conditions, the Retry=1 is falsely carried over to the next * DWC3_GUCTL_USBHSTINAUTORETRYEN should be set to a one * regardless of revision * - There is only single active asynchronous SS EP at the time. * - The active asynchronous EP is a Bulk IN EP. * - The burst with the correctly Retry=1 ACK TP and * the next burst belong to the same transfer. */ sclk = clk_get_rate(exynos->sclk_clock); pr_info("%s: sclk is %d MHz\n", __func__, sclk / 1000 / 1000); reg = dwc3_readl(dwc->regs, DWC3_GUCTL); reg |= (DWC3_GUCTL_USBHSTINAUTORETRYEN); if (dwc->dis_u2_freeclk_exists_quirk) { reg &= ~DWC3_GUCTL_REFCLKPER_MASK; /* fix ITP interval time to 125us */ reg |= DWC3_GUCTL_REFCLKPER(0x32); } if (exynos->config.adj_sof_accuracy) { reg &= ~DWC3_GUCTL_REFCLKPER_MASK; /* fix ITP interval time to 125us */ if (sclk <= 20 * 1000 * 1000) reg |= DWC3_GUCTL_REFCLKPER(0x34); else reg |= DWC3_GUCTL_REFCLKPER(0x26); } if (exynos->config.sparse_transfer_control) reg |= DWC3_GUCTL_SPRSCTRLTRANSEN; if (exynos->config.no_extra_delay) reg |= DWC3_GUCTL_NOEXTRDL; if (exynos->config.usb_host_device_timeout) { reg &= ~DWC3_GUCTL_DTOUT_MASK; reg |= DWC3_GUCTL_DTOUT(exynos->config.usb_host_device_timeout); } dwc3_writel(dwc->regs, DWC3_GUCTL, reg); if (DWC3_VER_IS_WITHIN(DWC3, 190A, 210A)) { reg = dwc3_readl(dwc->regs, DWC3_GRXTHRCFG); reg &= ~(DWC3_GRXTHRCFG_USBRXPKTCNT_MASK | DWC3_GRXTHRCFG_USBMAXRXBURSTSIZE_MASK); reg |= (DWC3_GRXTHRCFG_USBRXPKTCNTSEL | DWC3_GRXTHRCFG_USBRXPKTCNT(3) | DWC3_GRXTHRCFG_USBMAXRXBURSTSIZE(3)); dwc3_writel(dwc->regs, DWC3_GRXTHRCFG, reg); } if (DWC3_IP_IS(DWC31) || DWC3_IP_IS(DWC32)) { reg = dwc3_readl(dwc->regs, DWC3_GUCTL3); if (exynos->config.usb20_pkt_retry_disable) reg |= DWC3_GUCTL3_RETRYDISABLE; else reg &= ~DWC3_GUCTL3_RETRYDISABLE; dwc3_writel(dwc->regs, DWC3_GUCTL3, reg); } /* * WORKAROUND: DWC3 revisions 2.10a and earlier have a bug * The delay of the entry to a low power state such that * for applications where the link stays in a non-U0 state * for a short duration(< 1 microsecond), * the local PHY does not enter the low power state prior * to receiving a potential LFPS wakeup. * This causes the PHY CDR (Clock and Data Recovery) operation * to be unstable for some Synopsys PHYs. * The proposal now is to change the default and the recommended value * for GUSB3PIPECTL[21:19] in the RTL from 3'b100 to a minimum of 3'b001 */ if (DWC3_VER_IS_PRIOR(DWC3, 220A)) { reg = dwc3_readl(dwc->regs, DWC3_GUSB3PIPECTL(0)); reg &= ~(DWC3_GUSB3PIPECTL_DEP1P2P3_MASK); reg |= (DWC3_GUSB3PIPECTL_DEP1P2P3_EN); dwc3_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg); } if (!DWC3_VER_IS_PRIOR(DWC3, 250A)) { reg = dwc3_readl(dwc->regs, DWC3_GUSB3PIPECTL(0)); reg |= DWC3_GUSB3PIPECTL_DISRXDETINP3; dwc3_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg); } reg = dwc3_readl(dwc->regs, DWC3_GUSB3PIPECTL(0)); if (exynos->config.elastic_buf_mode_quirk) reg |= DWC3_ELASTIC_BUFFER_MODE; dwc3_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg); if (!DWC3_VER_IS_PRIOR(DWC31, 120A)) { reg = dwc3_readl(dwc->regs, DWC3_LLUCTL); reg &= ~(DWC3_LLUCTL_TX_TS1_CNT_MASK); reg |= (DWC3_PENDING_HP_TIMER_US(0xb) | DWC3_EN_US_HP_TIMER) | (DWC3_LLUCTL_PIPE_RESET) | (DWC3_LLUCTL_LTSSM_TIMER_OVRRD) | (DWC3_LLUCTL_TX_TS1_CNT(0x7)); if (exynos->config.force_gen1) reg |= DWC3_FORCE_GEN1; dwc3_writel(dwc->regs, DWC3_LLUCTL, reg); reg = dwc3_readl(dwc->regs, DWC3_LSKIPFREQ); reg &= ~(DWC3_PM_LC_TIMER_US_MASK | DWC3_PM_ENTRY_TIMER_US_MASK); reg |= (DWC3_PM_ENTRY_TIMER_US(0x9) | DWC3_PM_LC_TIMER_US(0x5) | DWC3_EN_PM_TIMER_US); dwc3_writel(dwc->regs, DWC3_LSKIPFREQ, reg); reg = dwc3_readl(dwc->regs, DWC3_GUSB3PIPECTL(0)); reg &= ~DWC3_GUSB3PIPECTL_DISRXDETINP3; dwc3_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg); /* Use Default Value reg = dwc3_readl(dwc->regs, DWC3_LU3LFPSRXTIM); dwc3_writel(dwc->regs, DWC3_LU3LFPSRXTIM, reg); */ reg = dwc3_readl(dwc->regs, DWC3_GSBUSCFG0); reg |= (DWC3_GSBUSCFG0_INCR8BRSTEN | DWC3_GSBUSCFG0_INCR4BRSTEN); dwc3_writel(dwc->regs, DWC3_GSBUSCFG0, reg); reg = dwc3_readl(dwc->regs, DWC3_BU31RHBDBG); reg |= DWC3_BU31RHBDBG_TOUTCTL; dwc3_writel(dwc->regs, DWC3_BU31RHBDBG, reg); reg = dwc3_readl(dwc->regs, DWC3_GUCTL1); reg &= ~DWC3_GUCTL1_IP_GAP_ADD_ON_MASK; reg |= DWC3_GUCTL1_IP_GAP_ADD_ON(0x1); reg |= DWC3_GUCTL1_DEV_DECOUPLE_L1L2_EVT; reg |= DWC3_GUCTL1_DEV_L1_EXIT_BY_HW; dwc3_writel(dwc->regs, DWC3_GUCTL1, reg); } /* Enable interrupt moderation */ dwc->imod_interval = 1; } /** * dwc3_soft_reset - Issue soft reset * @dwc: Pointer to our controller context structure */ int dwc3_soft_reset(struct dwc3 *dwc) { unsigned long timeout; u32 reg; timeout = jiffies + msecs_to_jiffies(500); dwc3_writel(dwc->regs, DWC3_DCTL, DWC3_DCTL_CSFTRST); do { reg = dwc3_readl(dwc->regs, DWC3_DCTL); if (!(reg & DWC3_DCTL_CSFTRST)) break; if (time_after(jiffies, timeout)) { dev_err(dwc->dev, "Reset Timed Out\n"); return -ETIMEDOUT; } cpu_relax(); } while (true); return 0; } static void dwc3_exynos_phy_setup(struct dwc3 *dwc, struct dwc3_exynos *exynos) { u32 reg; reg = dwc3_readl(dwc->regs, DWC3_GUSB3PIPECTL(0)); if (exynos->config.ux_exit_in_px_quirk) reg |= DWC3_GUSB3PIPECTL_UX_EXIT_PX; if (exynos->config.u1u2_exitfail_to_recov_quirk) reg |= DWC3_GUSB3PIPECTL_U1U2EXITFAIL_TO_RECOV; dwc3_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg); reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0)); if (!dwc->dis_enblslpm_quirk) reg |= DWC3_GUSB2PHYCFG_ENBLSLPM; if (exynos->config.adj_sof_accuracy) reg &= ~DWC3_GUSB2PHYCFG_U2_FREECLK_EXISTS; dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg); } /* Exynos Specific Configurations */ int dwc3_exynos_core_init(struct dwc3 *dwc, struct dwc3_exynos *exynos) { u32 reg, sclk; dwc3_exynos_phy_setup(dwc, exynos); dwc3_core_config(dwc, exynos); sclk = clk_get_rate(exynos->sclk_clock); pr_info("%s: sclk is %d MHz\n", __func__, sclk / 1000 / 1000); reg = dwc3_readl(dwc->regs, DWC3_GFLADJ); if (exynos->config.adj_sof_accuracy) { if (sclk <= 20 * 1000 * 1000) { /* For 19.2Mhz ref_clk */ reg |= DWC3_GFLADJ_REFCLK_240MHZDECR_PLS1; reg &= ~DWC3_GFLADJ_REFCLK_240MHZ_DECR_MASK; reg |= DWC3_GFLADJ_REFCLK_240MHZ_DECR(0xC); reg |= DWC3_GFLADJ_REFCLK_LPM_SEL; reg &= ~DWC3_GFLADJ_REFCLK_FLADJ_MASK; reg |= DWC3_GFLADJ_REFCLK_FLADJ(0xC8); reg |= DWC3_GFLADJ_30MHZ_SDBND_SEL; } else { /* In case of ref_clk 26MHz */ reg |= DWC3_GFLADJ_REFCLK_240MHZDECR_PLS1; reg &= ~DWC3_GFLADJ_REFCLK_240MHZ_DECR_MASK; reg |= DWC3_GFLADJ_REFCLK_240MHZ_DECR(0x9); reg |= DWC3_GFLADJ_REFCLK_LPM_SEL; reg &= ~DWC3_GFLADJ_REFCLK_FLADJ_MASK; reg |= DWC3_GFLADJ_REFCLK_FLADJ(0x5EE); reg |= DWC3_GFLADJ_30MHZ_SDBND_SEL; reg &= ~DWC3_GFLADJ_30MHZ_MASK; reg |= dwc->fladj; } } else if (dwc->dis_u2_freeclk_exists_quirk) { reg &= ~DWC3_GFLADJ_REFCLK_LPM_SEL; } dwc3_writel(dwc->regs, DWC3_GFLADJ, reg); reg = dwc3_readl(dwc->regs, DWC3_GCTL); if (dwc->dis_u2_freeclk_exists_quirk) reg |= DWC3_GCTL_SOFITPSYNC; else reg &= ~DWC3_GCTL_SOFITPSYNC; if (exynos->config.adj_sof_accuracy) reg &= ~DWC3_GCTL_SOFITPSYNC; if (exynos->config.suspend_clk_freq) { reg &= ~DWC3_GCTL_PWRDNSCALE_MASK; reg |= DWC3_GCTL_PWRDNSCALE( exynos->config.suspend_clk_freq / (16 * 1000)); } dwc3_writel(dwc->regs, DWC3_GCTL, reg); dwc3_exynos_set_sclk_clock(exynos->dev); return 0; } int dwc3_gadget_run_stop_vbus(struct dwc3_exynos *exynos, struct dwc3 *dwc, int is_on, int suspend) { u32 reg; u32 timeout = 1000; int retries = 1000; int ret = 0; if (pm_runtime_suspended(dwc->dev)) return 0; reg = dwc3_readl(dwc->regs, DWC3_DCTL); if (is_on) { reg = dwc3_readl(dwc->regs, DWC3_DCTL); if (dwc->revision <= DWC3_REVISION_187A) { reg &= ~DWC3_DCTL_TRGTULST_MASK; reg |= DWC3_DCTL_TRGTULST_RX_DET; } if (dwc->revision >= DWC3_REVISION_194A) reg &= ~DWC3_DCTL_KEEP_CONNECT; reg |= DWC3_DCTL_RUN_STOP; if (dwc->has_hibernation) reg |= DWC3_DCTL_KEEP_CONNECT; dwc->pullups_connected = true; } else { reg = dwc3_readl(dwc->regs, DWC3_DCTL); reg &= ~DWC3_DCTL_RUN_STOP; if (dwc->has_hibernation && !suspend) reg &= ~DWC3_DCTL_KEEP_CONNECT; dwc->pullups_connected = false; } dwc3_writel(dwc->regs, DWC3_DCTL, reg); do { reg = dwc3_readl(dwc->regs, DWC3_DSTS); if (is_on) { if (!(reg & DWC3_DSTS_DEVCTRLHLT)) break; } else { if (reg & DWC3_DSTS_DEVCTRLHLT) break; } timeout--; if (!timeout) { if (is_on) { reg = dwc3_readl(dwc->regs, DWC3_DCTL); reg |= DWC3_DCTL_CSFTRST; dwc3_writel(dwc->regs, DWC3_DCTL, reg); dev_err(dwc->dev, "gadget run/stop timeout, DCTL : 0x%x\n", reg); reg = dwc3_readl(dwc->regs, DWC3_DSTS); dev_err(dwc->dev, "gadget run/stop timeout, DSTS : 0x%x\n", reg); do { reg = dwc3_readl(dwc->regs, DWC3_DCTL); if (!(reg & DWC3_DCTL_CSFTRST)) { dev_info(dwc->dev, "gadget run/stop DCTL softreset, DCTL : 0x%x\n", reg); goto good; } udelay(1); } while (--retries); return -ETIMEDOUT; } /* Do nothing in DCTL stop timeout */ dev_err(dwc->dev, "gadget DCTL stop timeout, DSTS: 0x%x\n", reg); /* Does it need??? */ if (!exynos->vbus_state) dwc3_soft_reset(dwc); goto good; } udelay(1); } while (1); good: return ret; } int dwc3_gadget_vbus_session(struct dwc3_exynos *exynos, int is_active) { struct dwc3 *dwc = exynos->dwc; unsigned long flags; int ret = 0; is_active = !!is_active; spin_lock_irqsave(&dwc->lock, flags); ret = dwc3_gadget_run_stop_vbus(exynos, dwc, !!is_active, false); spin_unlock_irqrestore(&dwc->lock, flags); if (ret) dev_err(dwc->dev, "dwc3 gadget run/stop error:%d\n", ret); return ret; } void dwc3_exynos_gadget_disconnect_proc(struct dwc3 *dwc) { int reg; reg = dwc3_readl(dwc->regs, DWC3_DCTL); reg &= ~DWC3_DCTL_INITU1ENA; dwc3_writel(dwc->regs, DWC3_DCTL, reg); reg &= ~DWC3_DCTL_INITU2ENA; dwc3_writel(dwc->regs, DWC3_DCTL, reg); if (dwc->gadget_driver && dwc->gadget_driver->disconnect) dwc->gadget_driver->disconnect(dwc->gadget); dwc->gadget->speed = USB_SPEED_UNKNOWN; dwc->setup_packet_pending = false; usb_gadget_set_state(dwc->gadget, USB_STATE_NOTATTACHED); dwc->connected = false; } int dwc3_core_susphy_set(struct dwc3 *dwc, int on) { u32 reg; reg = dwc3_readl(dwc->regs, DWC3_GUSB3PIPECTL(0)); if (on) reg |= DWC3_GUSB3PIPECTL_SUSPHY; else reg &= ~DWC3_GUSB3PIPECTL_SUSPHY; dwc3_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg); reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0)); if (on) reg |= DWC3_GUSB2PHYCFG_SUSPHY; else reg &= ~DWC3_GUSB2PHYCFG_SUSPHY; dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg); return 0; } /* -------------------------------------------------------------------------- */ static struct dwc3_exynos *dwc3_exynos_match(struct device *dev) { const struct of_device_id *matches = NULL; struct dwc3_exynos *exynos = NULL; if (!dev) return NULL; matches = exynos_dwc3_match; if (of_match_device(matches, dev)) exynos = dev_get_drvdata(dev); return exynos; } bool dwc3_exynos_rsw_available(struct device *dev) { struct dwc3_exynos *exynos; exynos = dwc3_exynos_match(dev); if (!exynos) return false; return true; } EXPORT_SYMBOL_GPL(dwc3_exynos_rsw_available); int dwc3_exynos_rsw_start(struct device *dev) { struct dwc3_exynos *exynos = dev_get_drvdata(dev); struct dwc3_exynos_rsw *rsw = &exynos->rsw; dev_info(dev, "%s\n", __func__); /* B-device by default */ rsw->fsm->id = 1; rsw->fsm->b_sess_vld = 0; return 0; } EXPORT_SYMBOL_GPL(dwc3_exynos_rsw_start); void dwc3_exynos_rsw_stop(struct device *dev) { dev_info(dev, "%s\n", __func__); } EXPORT_SYMBOL_GPL(dwc3_exynos_rsw_stop); int dwc3_exynos_get_idle_ip_index(struct device *dev) { struct dwc3_exynos *exynos = dev_get_drvdata(dev); return exynos->idle_ip_index; } int dwc3_exynos_set_sclk_clock(struct device *dev) { struct dwc3_exynos *exynos = dev_get_drvdata(dev); dev_info(dev, "Set USB Source clock to 26Mhz\n"); clk_set_rate(exynos->sclk_clock, 19500000); dev_info(dev, "Changed USB Source clock %d\n", clk_get_rate(exynos->sclk_clock)); return 0; } int dwc3_exynos_set_bus_clock(struct device *dev, int clk_level) { struct dwc3_exynos *exynos = dev_get_drvdata(dev); if (!IS_ERR_OR_NULL(exynos->bus_clock)) { if (clk_level < 0) { dev_info(dev, "Set USB Bus clock to 66Mhz\n"); clk_set_rate(exynos->bus_clock, 66666666); } else if (clk_level == 1) { dev_info(dev, "Set USB Bus clock to 266Mhz\n"); clk_set_rate(exynos->bus_clock, 266625000); } else if (clk_level == 0) { dev_info(dev, "Set USB Bus clock to 266Mhz\n"); clk_set_rate(exynos->bus_clock, 266625000); } else { dev_info(dev, "Unsupported clock level"); } dev_info(dev, "Changed USB Bus clock %d\n", clk_get_rate(exynos->bus_clock)); } return 0; } static void dwc3_exynos_rsw_work(struct work_struct *w) { struct dwc3_exynos_rsw *rsw = container_of(w, struct dwc3_exynos_rsw, work); struct dwc3_exynos *exynos = container_of(rsw, struct dwc3_exynos, rsw); dev_info(exynos->dev, "%s\n", __func__); dwc3_otg_run_sm(rsw->fsm); } int dwc3_exynos_rsw_setup(struct device *dev, struct otg_fsm *fsm) { struct dwc3_exynos *exynos = dev_get_drvdata(dev); struct dwc3_exynos_rsw *rsw = &exynos->rsw; dev_dbg(dev, "%s\n", __func__); INIT_WORK(&rsw->work, dwc3_exynos_rsw_work); rsw->fsm = fsm; return 0; } EXPORT_SYMBOL_GPL(dwc3_exynos_rsw_setup); void dwc3_exynos_rsw_exit(struct device *dev) { struct dwc3_exynos *exynos = dev_get_drvdata(dev); struct dwc3_exynos_rsw *rsw = &exynos->rsw; dev_dbg(dev, "%s\n", __func__); cancel_work_sync(&rsw->work); rsw->fsm = NULL; } EXPORT_SYMBOL_GPL(dwc3_exynos_rsw_exit); /** * dwc3_exynos_id_event - receive ID pin state change event. * @state : New ID pin state. */ int dwc3_exynos_id_event(struct device *dev, int state) { struct dwc3_exynos *exynos; struct dwc3_exynos_rsw *rsw; struct otg_fsm *fsm; dev_dbg(dev, "EVENT: ID: %d\n", state); exynos = dev_get_drvdata(dev); if (!exynos) return -ENOENT; rsw = &exynos->rsw; fsm = rsw->fsm; if (!fsm) return -ENOENT; if (fsm->id != state) { fsm->id = state; schedule_work(&rsw->work); } return 0; } EXPORT_SYMBOL_GPL(dwc3_exynos_id_event); /** * dwc3_exynos_vbus_event - receive VBus change event. * vbus_active : New VBus state, true if active, false otherwise. */ int dwc3_exynos_vbus_event(struct device *dev, bool vbus_active) { struct dwc3_exynos *exynos; struct dwc3_exynos_rsw *rsw; struct otg_fsm *fsm; dev_dbg(dev, "EVENT: VBUS: %sactive\n", vbus_active ? "" : "in"); exynos = dev_get_drvdata(dev); if (!exynos) { dev_err(dev, "%s: exynos is NULL!!\n", __func__); return -ENOENT; } rsw = &exynos->rsw; fsm = rsw->fsm; if (!fsm) { dev_err(dev, "%s: fsm is NULL!!\n", __func__); return -ENOENT; } if (fsm->b_sess_vld != vbus_active) { fsm->b_sess_vld = vbus_active; schedule_work(&rsw->work); } else { dev_err(dev, "%s: vbus state is unmatched!! \ fsm->b_sess_vld: %d, vbus_active: %d\n", __func__, fsm->b_sess_vld, vbus_active); } return 0; } EXPORT_SYMBOL_GPL(dwc3_exynos_vbus_event); int dwc3_gadget_speed(struct device *dev) { struct dwc3_exynos *exynos; struct usb_gadget *gadget; exynos = dev_get_drvdata(dev); if (!exynos) { dev_err(dev, "%s: exynos is NULL!!\n", __func__); return 0; } gadget = exynos->dwc->gadget; if (!gadget) { dev_err(dev, "%s: gadget is NULL!!\n", __func__); return 0; } return gadget->speed; } EXPORT_SYMBOL(dwc3_gadget_speed); /** * dwc3_exynos_phy_enable - received combo phy control. */ int dwc3_exynos_phy_enable(int owner, bool on) { struct dwc3_exynos *exynos; struct dwc3_exynos_rsw *rsw; struct otg_fsm *fsm; struct device_node *np = NULL; struct platform_device *pdev = NULL; struct dwc3 *dwc; struct device *dev; struct device *exynos_dev; struct dwc3_otg *dotg; int wait_counter; int ret = 0; pr_info("%s owner=%d (usb:0 dp:1) on=%d +\n", __func__, owner, on); np = of_find_compatible_node(NULL, NULL, "samsung,exynos-dwusb"); if (np) { pdev = of_find_device_by_node(np); if (!pdev) { pr_err("%s we can't get platform device\n", __func__); ret = -ENODEV; goto err; } of_node_put(np); } else { pr_err("%s we can't get np\n", __func__); ret = -ENODEV; goto err; } exynos = platform_get_drvdata(pdev); if (!exynos) { pr_err("%s we can't get drvdata\n", __func__); ret = -ENOENT; goto err; } rsw = &exynos->rsw; fsm = rsw->fsm; if (!fsm) { pr_err("%s we can't get fsm\n", __func__); ret = -ENOENT; goto err; } dwc = exynos->dwc; dev = dwc->dev; exynos_dev = exynos->dev; dotg = exynos->dotg; if (on) { pr_info("exynos RPM Usage Count: %d\n", exynos->dev->power.usage_count); wait_counter = 0; if (dotg->dwc3_suspended != USB_NORMAL) { while (!pm_runtime_suspended(exynos->dev)) { msleep(5); pr_info("%s: wait AP resume, %d!\n", __func__, wait_counter++); if (wait_counter >= 3) { pr_info("%s: Can't wait AP resume break!\n", __func__); break; } } } dotg->dwc3_suspended = USB_NORMAL; ret = pm_runtime_get_sync(exynos_dev); if (ret < 0) { dev_err(dwc->dev, "%s: failed to initialize exynos: %d\n", __func__, ret); } ret = pm_runtime_get_sync(dev); if (ret < 0) { dev_err(dwc->dev, "%s: failed to initialize core\n", __func__); } usb_power_notify_control(1, 1); } else { usb_power_notify_control(1, 0); pm_runtime_put_sync_suspend(dev); pm_runtime_put_sync_suspend(exynos_dev); } err: pr_info("%s -\n", __func__); return ret; } EXPORT_SYMBOL_GPL(dwc3_exynos_phy_enable); static int dwc3_exynos_register_phys(struct dwc3_exynos *exynos) { struct platform_device *pdev; int ret; pdev = platform_device_alloc("usb_phy_generic", PLATFORM_DEVID_AUTO); if (!pdev) return -ENOMEM; exynos->usb2_phy = pdev; pdev = platform_device_alloc("usb_phy_generic", PLATFORM_DEVID_AUTO); if (!pdev) { ret = -ENOMEM; goto err1; } exynos->usb3_phy = pdev; ret = platform_device_add(exynos->usb2_phy); if (ret) goto err2; ret = platform_device_add(exynos->usb3_phy); if (ret) goto err3; return 0; err3: platform_device_del(exynos->usb2_phy); err2: platform_device_put(exynos->usb3_phy); err1: platform_device_put(exynos->usb2_phy); return ret; } static int dwc3_exynos_remove_child(struct device *dev, void *unused) { struct platform_device *pdev = to_platform_device(dev); platform_device_unregister(pdev); return 0; } static int dwc3_exynos_host_init(struct dwc3_exynos *exynos) { struct dwc3 *dwc = exynos->dwc; struct device *dev = exynos->dev; struct property_entry props[4]; struct platform_device *xhci; struct resource *res; struct platform_device *dwc3_pdev = to_platform_device(dwc->dev); int prop_idx = 0; int ret = 0; #if IS_ENABLED(CONFIG_SND_EXYNOS_USB_AUDIO_MODULE) dma_addr_t dma; #endif /* Configuration xhci resources */ res = platform_get_resource(dwc3_pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(dev, "missing memory resource\n"); return -ENODEV; } dwc->xhci_resources[0].start = res->start; dwc->xhci_resources[0].end = dwc->xhci_resources[0].start + DWC3_XHCI_REGS_END; dwc->xhci_resources[0].flags = res->flags; dwc->xhci_resources[0].name = res->name; res = platform_get_resource(dwc3_pdev, IORESOURCE_IRQ, 0); if (!res) { dev_err(dev, "missing irq resource\n"); return -ENODEV; } dwc->xhci_resources[1].start = dwc->irq_gadget; dwc->xhci_resources[1].end = dwc->irq_gadget; dwc->xhci_resources[1].flags = res->flags; dwc->xhci_resources[1].name = res->name; xhci = platform_device_alloc("xhci-hcd-exynos", PLATFORM_DEVID_AUTO); if (!xhci) { dev_err(dwc->dev, "couldn't allocate xHCI device\n"); return -ENOMEM; } xhci->dev.parent = dwc->dev; ret = dma_set_mask_and_coherent(&xhci->dev, DMA_BIT_MASK(36)); if (ret) { pr_err("xhci dma set mask ret = %d\n", ret); return ret; } ret = platform_device_add_resources(xhci, dwc->xhci_resources, DWC3_XHCI_RESOURCES_NUM); if (ret) { dev_err(dwc->dev, "couldn't add resources to xHCI device\n"); goto err; } memset(props, 0, sizeof(struct property_entry) * ARRAY_SIZE(props)); if (dwc->usb3_lpm_capable) props[prop_idx++] = PROPERTY_ENTRY_BOOL("usb3-lpm-capable"); if (dwc->usb2_lpm_disable) props[prop_idx++] = PROPERTY_ENTRY_BOOL("usb2-lpm-disable"); /** * WORKAROUND: dwc3 revisions <=3.00a have a limitation * where Port Disable command doesn't work. * * The suggested workaround is that we avoid Port Disable * completely. * * This following flag tells XHCI to do just that. */ if (DWC3_VER_IS_PRIOR(DWC3, 310A)) props[prop_idx++] = PROPERTY_ENTRY_BOOL("quirk-broken-port-ped"); if (prop_idx) { ret = platform_device_add_properties(xhci, props); if (ret) { dev_err(dwc->dev, "failed to add properties to xHCI\n"); goto err; } } dwc->xhci = xhci; #if IS_ENABLED(CONFIG_SND_EXYNOS_USB_AUDIO_MODULE) /* In data buf alloc */ xhci_data.in_data_addr = dma_alloc_coherent(dev, (PAGE_SIZE * 256), &dma, GFP_KERNEL); xhci_data.in_data_dma = dma; dev_info(dev, "// IN Data address = 0x%llx (DMA), %p (virt)", (unsigned long long)xhci_data.in_data_dma, xhci_data.in_data_addr); /* Out data buf alloc */ xhci_data.out_data_addr = dma_alloc_coherent(dev, (PAGE_SIZE * 256), &dma, GFP_KERNEL); xhci_data.out_data_dma = dma; dev_info(dev, "// OUT Data address = 0x%llx (DMA), %p (virt)", (unsigned long long)xhci_data.out_data_dma, xhci_data.out_data_addr); #endif /* pre dma_alloc */ xhci_pre_alloc.pre_dma_alloc = dma_alloc_coherent(dev, SZ_2M, &xhci_pre_alloc.dma, GFP_KERNEL); if (!xhci_pre_alloc.pre_dma_alloc) { dev_err(dev, "%s: dma_alloc fail!\n", __func__); return -ENOMEM; } return 0; err: platform_device_put(xhci); return ret; } static u32 fixed_usb_idle_ip_index = 0; static int dwc3_exynos_get_properties(struct dwc3_exynos *exynos) { struct device *dev = exynos->dev; struct device_node *node = dev->of_node; u32 value; int ret = 0; if (!of_property_read_u32(node, "exynos,adj-sof-accuracy", &value)) { exynos->config.adj_sof_accuracy = value ? true : false; dev_info(dev, "adj-sof-accuracy set from %s node", node->name); } else { dev_err(dev, "can't get adj-sof-accuracy from %s node", node->name); return -EINVAL; } if (!of_property_read_u32(node, "exynos,enable_sprs_transfer", &value)) { exynos->config.sparse_transfer_control = value ? true : false; } else { dev_err(dev, "can't get sprs-xfer-ctrl from %s node", node->name); return -EINVAL; } if (!of_property_read_u32(node, "exynos,usb_host_device_timeout", &value)) { exynos->config.usb_host_device_timeout = value; } else { dev_info(dev, "usb_host_device_timeout is not defined...\n"); exynos->config.usb_host_device_timeout = 0x0; } if (!of_property_read_u32(node, "exynos,suspend_clk_freq", &value)) { exynos->config.suspend_clk_freq = value; } else { dev_info(dev, "Set suspend clock freq to 26Mhz(Default)\n"); exynos->config.suspend_clk_freq = 26000000; } if (of_property_read_bool(node, "lazy-vbus-up")) { exynos->lazy_vbus_up = 1; dev_info(dev, "lazy-vbus-up is enabled!!\n"); } else exynos->lazy_vbus_up = 0; exynos->config.no_extra_delay = device_property_read_bool(dev, "exynos,no_extra_delay"); exynos->config.ux_exit_in_px_quirk = device_property_read_bool(dev, "exynos,ux_exit_in_px_quirk"); exynos->config.elastic_buf_mode_quirk = device_property_read_bool(dev, "exynos,elastic_buf_mode_quirk"); exynos->config.force_gen1 = device_property_read_bool(dev, "exynos,force_gen1"); exynos->config.u1u2_exitfail_to_recov_quirk = device_property_read_bool( dev, "exynos,u1u2_exitfail_quirk"); exynos->config.usb20_pkt_retry_disable = device_property_read_bool( dev, "exynos,usb20_pkt_retry_disable"); return ret; } #if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) /** * dwc3_gadget_get_cmply_link_state - Gets current state of USB Link * @dwc: pointer to our context structure * * extern module can check dwc3 core link state This function will * return 1 link is on compliance of loopback mode else 0. */ static int dwc3_gadget_get_cmply_link_state(void *dev) { struct dwc3 *dwc = (struct dwc3 *)dev; u32 reg; u32 ret = -ENODEV; if (dwc->pullups_connected) { reg = dwc3_otg_get_link_state(dwc); pr_info("%s: link state = %d\n", __func__, reg); if ((reg == DWC3_LINK_STATE_CMPLY) || (reg == DWC3_LINK_STATE_LPBK)) ret = 1; else ret = 0; } return ret; } static struct typec_manager_gadget_ops typec_manager_dwc3_gadget_ops = { .get_cmply_link_state = dwc3_gadget_get_cmply_link_state, }; #endif struct kprobe_data { void *data0; void *data1; void *data2; int data3; }; static int entry_dwc3_gadget_conndone_interrupt(struct kretprobe_instance *ri, struct pt_regs *regs) { struct kprobe_data *data = (struct kprobe_data *)ri->data; struct dwc3 *dwc = (struct dwc3 *)regs->regs[0]; data->data0 = dwc; return 0; } static int ret_dwc3_gadget_conndone_interrupt(struct kretprobe_instance *ri, struct pt_regs *regs) { struct kprobe_data *data = (struct kprobe_data *)ri->data; struct dwc3 *dwc = (struct dwc3 *)data->data0; pr_info("usb: dwc3_gadget_conndone_interrupt (%d)\n", dwc->speed); switch (dwc->speed) { case DWC3_DSTS_SUPERSPEED_PLUS: #if defined(CONFIG_USB_NOTIFY_PROC_LOG) store_usblog_notify(NOTIFY_USBSTATE, (void *)"USB_STATE=ENUM:CONNDONE:PSS", NULL); #endif break; case DWC3_DSTS_SUPERSPEED: #if defined(CONFIG_USB_NOTIFY_PROC_LOG) store_usblog_notify(NOTIFY_USBSTATE, (void *)"USB_STATE=ENUM:CONNDONE:SS", NULL); #endif break; case DWC3_DSTS_HIGHSPEED: #if defined(CONFIG_USB_NOTIFY_PROC_LOG) store_usblog_notify(NOTIFY_USBSTATE, (void *)"USB_STATE=ENUM:CONNDONE:HS", NULL); #endif break; case DWC3_DSTS_FULLSPEED: #if defined(CONFIG_USB_NOTIFY_PROC_LOG) store_usblog_notify(NOTIFY_USBSTATE, (void *)"USB_STATE=ENUM:CONNDONE:FS", NULL); #endif break; case DWC3_DSTS_LOWSPEED: #if defined(CONFIG_USB_NOTIFY_PROC_LOG) store_usblog_notify(NOTIFY_USBSTATE, (void *)"USB_STATE=ENUM:CONNDONE:LS", NULL); #endif break; } dwc->link_state = DWC3_LINK_STATE_U0; return 0; } static int entry_dwc3_gadget_reset_interrupt(struct kretprobe_instance *ri, struct pt_regs *regs) { struct dwc3 *dwc = (struct dwc3 *)regs->regs[0]; #if IS_ENABLED(CONFIG_USB_CONFIGFS_F_SS_MON_GADGET) usb_reset_notify(dwc->gadget); #endif pr_info("usb: dwc3_gadget_reset_interrupt (Speed:%d)\n", dwc->gadget->speed); return 0; } static int entry_dwc3_gadget_vbus_draw(struct kretprobe_instance *ri, struct pt_regs *regs) { unsigned int mA = (unsigned int)regs->regs[1]; switch (mA) { case 2: #if IS_ENABLED(CONFIG_USB_CONFIGFS_F_SS_MON_GADGET) pr_info("usb: dwc3_gadget_vbus_draw: suspend\n"); make_suspend_current_event(); #endif break; case 100: break; case 500: break; case 900: break; default: break; } return 0; } static int entry_dwc3_gadget_run_stop(struct kretprobe_instance *ri, struct pt_regs *regs) { struct dwc3 *dwc = (struct dwc3 *)regs->regs[0]; struct kprobe_data *data = (struct kprobe_data *)ri->data; int is_on = (int)regs->regs[1]; data->data0 = dwc; data->data3 = is_on; pr_info("usb: dwc3_gadget_run_stop : is_on = %d\n", is_on); return 0; } static int ret_dwc3_gadget_run_stop(struct kretprobe_instance *ri, struct pt_regs *regs) { unsigned long long retval = regs_return_value(regs); struct kprobe_data *data = (struct kprobe_data *)ri->data; struct dwc3 *dwc = data->data0; int is_on; is_on = data->data3; #if IS_ENABLED(CONFIG_USB_CONFIGFS_F_SS_MON_GADGET) vbus_session_notify(dwc->gadget, is_on, retval); #endif if (retval) { pr_info("usb: dwc3_gadget_run_stop : dwc3_gadget %s failed (%d)\n", is_on ? "ON" : "OFF", retval); } return 0; } #define ENTRY_RET(name) {\ .handler = ret_##name,\ .entry_handler = entry_##name,\ .data_size = sizeof(struct kprobe_data),\ .maxactive = 8,\ .kp.symbol_name = #name,\ } #define ENTRY(name) {\ .entry_handler = entry_##name,\ .data_size = sizeof(struct kprobe_data),\ .maxactive = 8,\ .kp.symbol_name = #name,\ } static struct kretprobe dwc3_exynos_probes[] = { ENTRY(dwc3_gadget_reset_interrupt), ENTRY_RET(dwc3_gadget_conndone_interrupt), ENTRY_RET(dwc3_gadget_run_stop), ENTRY(dwc3_gadget_vbus_draw), }; static int dwc3_exyons_kretprobe_init(void) { int ret; int i; for (i = 0; i < ARRAY_SIZE(dwc3_exynos_probes) ; i++) { ret = register_kretprobe(&dwc3_exynos_probes[i]); if (ret < 0) { pr_err("register_kretprobe failed, returned %d\n", ret); return ret; } } return 0; } void dwc3_exynos_kretprobe_exit(void) { int i; for (i = 0; i < ARRAY_SIZE(dwc3_exynos_probes); i++) unregister_kretprobe(&dwc3_exynos_probes[i]); } static void dwc3_init_kprobe_work(struct work_struct *data) { dwc3_exyons_kretprobe_init(); } static int dwc3_exynos_probe(struct platform_device *pdev) { struct dwc3_exynos *exynos; struct device *dev = &pdev->dev; struct platform_device *dwc3_pdev; struct device_node *node = dev->of_node, *dwc3_np; #ifdef USB_USE_IOCOHERENCY struct regmap *reg_sysreg; #endif int ret; struct phy *temp_usb_phy; int wait_counter; temp_usb_phy = devm_phy_get(dev, "usb2-phy"); if (IS_ERR(temp_usb_phy)) { pr_info("USB phy is not probed - defered return!\n"); return -EPROBE_DEFER; } exynos = devm_kzalloc(dev, sizeof(*exynos), GFP_KERNEL); if (!exynos) return -ENOMEM; ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(36)); if (ret) { pr_err("dma set mask ret = %d\n", ret); return ret; } platform_set_drvdata(pdev, exynos); exynos->dev = dev; if (fixed_usb_idle_ip_index == 0) { exynos->idle_ip_index = exynos_get_idle_ip_index(dev_name(dev), 0); pr_info("%s, usb idle ip = %d\n", __func__, exynos->idle_ip_index); fixed_usb_idle_ip_index = exynos->idle_ip_index; /* Dose it need?? */ exynos_update_ip_idle_status(exynos->idle_ip_index, 0); } else exynos->idle_ip_index = fixed_usb_idle_ip_index; ret = dwc3_exynos_clk_get(exynos); if (ret) return ret; ret = dwc3_exynos_clk_prepare(exynos); if (ret) return ret; ret = dwc3_exynos_clk_enable(exynos); if (ret) { dwc3_exynos_clk_unprepare(exynos); return ret; } ret = dwc3_exynos_register_phys(exynos); if (ret) { dev_err(dev, "couldn't register PHYs\n"); goto vdd33_err; } ret = dwc3_exynos_get_properties(exynos); if (ret) { dev_err(dev, "couldn't get properties.\n"); goto vdd33_err; } pm_runtime_enable(dev); ret = pm_runtime_get_sync(dev); if (ret < 0) goto vdd33_err; pm_runtime_forbid(dev); dwc3_exynos_set_sclk_clock(dev); dwc3_np = of_get_child_by_name(node, "dwc3"); if (!dwc3_np) { dev_err(dev, "failed to find dwc3 core child!\n"); goto vdd33_err; } /* PHY enable for configurations */ exynos_usbdrd_ldo_manual_control(1); exynos_usbdrd_phy_conn(temp_usb_phy, 1); if (node) { ret = of_platform_populate(node, NULL, NULL, dev); if (ret) { dev_err(dev, "failed to add dwc3 core\n"); goto populate_err; } } else { dev_err(dev, "no device node, failed to add dwc3 core\n"); ret = -ENODEV; goto populate_err; } dwc3_pdev = of_find_device_by_node(dwc3_np); exynos->dwc = platform_get_drvdata(dwc3_pdev); if (exynos->dwc == NULL) { ret = -EPROBE_DEFER; goto populate_err; } /* dwc3 core configurations */ pm_runtime_allow(exynos->dwc->dev); ret = dma_set_mask_and_coherent(exynos->dwc->dev, DMA_BIT_MASK(36)); if (ret) { pr_err("dwc3 core dma_set_mask returned FAIL!(%d)\n", ret); goto populate_err; } exynos->dwc->gadget->sg_supported = false; //exynos->dwc->imod_interval = 100; pm_runtime_dont_use_autosuspend(exynos->dwc->dev); #ifdef USB_USE_IOCOHERENCY dev_info(dev,"Configure USB sharability.\n"); reg_sysreg = syscon_regmap_lookup_by_phandle(dev->of_node, "samsung,sysreg-usb"); if (IS_ERR(reg_sysreg)) { dev_err(dev, "Failed to lookup Sysreg regmap\n"); } regmap_update_bits(reg_sysreg, 0x704, 0x6, 0x6); #endif ret = pm_runtime_put_sync(dev); pr_info("%s, pm_runtime_put_sync = %d\n",__func__, ret); pm_runtime_allow(dev); /* Wait for end of dwc3 idle */ wait_counter = 0; while (exynos->dwc->current_dr_role != DWC3_EXYNOS_IGNORE_CORE_OPS) { wait_counter++; msleep(20); if (wait_counter > 10) { pr_err("Can't wait dwc3 idle!!!!\n"); break; } } /* PHY disable */ exynos_usbdrd_phy_conn(temp_usb_phy, 0); exynos_usbdrd_ldo_manual_control(0); /* USB host initialization. */ ret = dwc3_exynos_host_init(exynos); if (ret) { pr_err("USB host pre-initialization fail!\n"); goto populate_err; } dev_info(dev, "Configuration exynos OTG\n"); dwc3_exynos_otg_init(exynos->dwc, exynos); dwc3_otg_start(exynos->dwc, exynos); otg_set_peripheral(&exynos->dotg->otg, exynos->dwc->gadget); #if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) typec_manager_dwc3_gadget_ops.driver_data = exynos->dwc; probe_typec_manager_gadget_ops(&typec_manager_dwc3_gadget_ops); #endif INIT_WORK(&exynos->int_kprobe_work, dwc3_init_kprobe_work); schedule_work(&exynos->int_kprobe_work); return 0; populate_err: platform_device_unregister(exynos->usb2_phy); platform_device_unregister(exynos->usb3_phy); vdd33_err: dwc3_exynos_clk_disable(exynos); dwc3_exynos_clk_unprepare(exynos); exynos_update_ip_idle_status(exynos->idle_ip_index, 1); pm_runtime_disable(&pdev->dev); pr_info("%s err = %d\n", __func__, ret); return ret; } static int dwc3_exynos_remove(struct platform_device *pdev) { struct dwc3_exynos *exynos = platform_get_drvdata(pdev); pr_info("%s\n", __func__); pm_runtime_get_sync(&pdev->dev); pm_runtime_put_sync(&pdev->dev); pm_runtime_allow(&pdev->dev); pm_runtime_disable(&pdev->dev); device_for_each_child(&pdev->dev, NULL, dwc3_exynos_remove_child); platform_device_unregister(exynos->usb2_phy); platform_device_unregister(exynos->usb3_phy); dwc3_exynos_kretprobe_exit(); pm_runtime_disable(&pdev->dev); if (!pm_runtime_status_suspended(&pdev->dev)) { dwc3_exynos_clk_disable(exynos); pm_runtime_set_suspended(&pdev->dev); } dwc3_exynos_clk_unprepare(exynos); return 0; } #ifdef CONFIG_PM static int dwc3_exynos_runtime_suspend(struct device *dev) { struct dwc3_exynos *exynos = dev_get_drvdata(dev); struct dwc3 *dwc; dev_info(dev, "%s\n", __func__); if (!exynos) return 0; dwc = exynos->dwc; if (pm_runtime_suspended(dev)) { dev_info(dev, "%s, already suspended\n", __func__); return 0; } dwc3_exynos_clk_disable(exynos); /* inform what USB state is idle to IDLE_IP */ exynos_update_ip_idle_status(exynos->idle_ip_index, 1); /* After disconnecting cable, it will ignore core operations like * dwc3_suspend/resume in core.c */ exynos->dwc->current_dr_role = DWC3_EXYNOS_IGNORE_CORE_OPS; return 0; } static int dwc3_exynos_runtime_resume(struct device *dev) { struct dwc3_exynos *exynos = dev_get_drvdata(dev); struct dwc3_otg *dotg; struct dwc3_exynos_rsw *rsw; struct otg_fsm *fsm; int ret = 0; dev_info(dev, "%s\n", __func__); if (!exynos) return 0; if (pm_runtime_active(dev)) { dev_info(dev, "%s, already active\n", __func__); return 0; } dotg = exynos->dotg; if (dotg && dotg->ready) { rsw = &exynos->rsw; fsm = rsw->fsm; exynos->dwc->current_dr_role = DWC3_GCTL_PRTCAP_DEVICE; } exynos_update_ip_idle_status(exynos->idle_ip_index, 0); ret = dwc3_exynos_clk_enable(exynos); if (ret) { dev_err(dev, "%s: clk_enable failed\n", __func__); return ret; } pm_runtime_mark_last_busy(dev); return 0; } #endif #ifdef CONFIG_PM_SLEEP static int dwc3_exynos_suspend(struct device *dev) { struct dwc3_exynos *exynos = dev_get_drvdata(dev); dev_info(dev, "%s\n", __func__); pr_info("exynos RPM Usage Count: %d\n", dev->power.usage_count); if (pm_runtime_suspended(dev)) { dev_info(dev, "%s, already suspended\n", __func__); return 0; } dwc3_exynos_clk_disable(exynos); pr_info("exynos RPM Usage Count: %d\n", dev->power.usage_count); return 0; } static int dwc3_exynos_resume(struct device *dev) { struct dwc3_exynos *exynos = dev_get_drvdata(dev); int ret; dev_info(dev, "%s\n", __func__); pr_info("exynos RPM Usage Count: %d\n", dev->power.usage_count); if (!exynos) return 0; ret = dwc3_exynos_clk_enable(exynos); if (ret) { dev_err(dev, "%s: clk_enable failed\n", __func__); return ret; } /* runtime set active to reflect active state. */ pm_runtime_disable(dev); pm_runtime_set_active(dev); pm_runtime_enable(dev); pr_info("exynos RPM Usage Count: %d\n", dev->power.usage_count); #ifdef CONFIG_SND_EXYNOS_USB_AUDIO if (exynos->vbus_state || otg_connection) dwc3_exynos_set_sclk_clock(dev); #endif return 0; } static const struct dev_pm_ops dwc3_exynos_dev_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(dwc3_exynos_suspend, dwc3_exynos_resume) SET_RUNTIME_PM_OPS(dwc3_exynos_runtime_suspend, dwc3_exynos_runtime_resume, NULL) }; #define DEV_PM_OPS (&dwc3_exynos_dev_pm_ops) #else #define DEV_PM_OPS NULL #endif /* CONFIG_PM_SLEEP */ static struct platform_driver dwc3_exynos_driver = { .probe = dwc3_exynos_probe, .remove = dwc3_exynos_remove, .driver = { .name = "exynos-dwc3", .of_match_table = exynos_dwc3_match, .pm = DEV_PM_OPS, }, }; module_platform_driver(dwc3_exynos_driver); MODULE_SOFTDEP("pre:phy-exynos-usbdrd-super"); MODULE_AUTHOR("Anton Tikhomirov "); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("DesignWare USB3 EXYNOS Glue Layer");