| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * dwc3-exynos.c - Samsung Exynos DWC3 Specific Glue layer | |
| * | |
| * Copyright (C) 2022 Samsung Electronics Co., Ltd. | |
| */ | |
| #include <dwc3/core.h> /* $(srctree)/drivers/usb/dwc3/core.h */ | |
| #include <dwc3/io.h> /* $(srctree)/drivers/usb/dwc3/io.h */ | |
| #include <dwc3/gadget.h> /* $(srctree)/drivers/usb/dwc3/gadget.h */ | |
| #include <linux/clk.h> | |
| #include <linux/dma-mapping.h> | |
| #include <linux/extcon.h> | |
| #include <linux/io.h> | |
| #include <linux/kernel.h> | |
| #include <linux/module.h> | |
| #include <linux/mutex.h> | |
| #include <linux/of.h> | |
| #include <linux/of_device.h> | |
| #include <linux/of_platform.h> | |
| #include <linux/pinctrl/consumer.h> | |
| #include <linux/platform_device.h> | |
| #include <linux/pm_runtime.h> | |
| #include <linux/slab.h> | |
| #include <linux/suspend.h> | |
| #include <linux/usb/dwc3-exynos.h> | |
| #include <linux/usb/gadget.h> | |
| #include <linux/usb/of.h> | |
| #include <linux/usb/usb_phy_generic.h> | |
| #include <linux/workqueue.h> | |
| #include <soc/google/exynos-cpupm.h> | |
| #include "core-exynos.h" | |
| #include "dwc3-exynos-ldo.h" | |
| #include "exynos-otg.h" | |
| #if IS_ENABLED(CONFIG_USB_XHCI_GOOG_DMA) | |
| #include "xhci-goog-dma.h" | |
| #endif | |
| static const struct of_device_id exynos_dwc3_match[] = { | |
| { | |
| .compatible = "samsung,exynos9-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 = 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)) { | |
| 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"); | |
| else | |
| clk_count--; | |
| } | |
| } | |
| clk_ids[clk_count] = NULL; | |
| exynos->clocks = devm_kmalloc(exynos->dev, | |
| (clk_count + 1) * sizeof(struct clk *), | |
| GFP_KERNEL); | |
| if (!exynos->clocks) | |
| return -ENOMEM; | |
| for (i = 0; clk_ids[i]; 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_enable(struct dwc3_exynos *exynos) | |
| { | |
| int i; | |
| int ret; | |
| for (i = 0; exynos->clocks[i]; i++) { | |
| ret = clk_prepare_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_unprepare(exynos->clocks[i]); | |
| return ret; | |
| } | |
| static void dwc3_exynos_clk_disable_unprepare(struct dwc3_exynos *exynos) | |
| { | |
| int i; | |
| for (i = 0; exynos->clocks[i]; i++){ | |
| clk_disable_unprepare(exynos->clocks[i]); | |
| } | |
| } | |
| static void dwc3_core_config(struct dwc3 *dwc, struct dwc3_exynos *exynos) | |
| { | |
| u32 reg; | |
| /* AHB bus configuration */ | |
| reg = dwc3_exynos_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_exynos_writel(dwc->regs, DWC3_GSBUSCFG0, reg); | |
| reg = dwc3_exynos_readl(dwc->regs, DWC3_GSBUSCFG1); | |
| if (DWC3_VER_IS(DWC31, 170A)) { | |
| /* | |
| * 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_exynos_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. | |
| */ | |
| reg = dwc3_exynos_readl(dwc->regs, DWC3_GUCTL); | |
| reg |= (DWC3_GUCTL_USBHSTINAUTORETRYEN); | |
| /* fix ITP interval time to 125us */ | |
| if (DWC3_VER_IS(DWC31, 170A)) { | |
| reg &= ~DWC3_GUCTL_REFCLKPER_MASK; | |
| reg |= DWC3_GUCTL_REFCLKPER(0xF); | |
| } else if (DWC3_VER_IS_WITHIN(DWC31, 180A, 190A)) { | |
| reg &= ~DWC3_GUCTL_REFCLKPER_MASK; | |
| reg |= DWC3_GUCTL_REFCLKPER(0x34); | |
| } | |
| 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_exynos_writel(dwc->regs, DWC3_GUCTL, reg); | |
| if (DWC3_VER_IS_WITHIN(DWC3, 190A, 210A)) { | |
| reg = dwc3_exynos_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_exynos_writel(dwc->regs, DWC3_GRXTHRCFG, 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_exynos_readl(dwc->regs, DWC3_GUSB3PIPECTL(0)); | |
| reg &= ~(DWC3_GUSB3PIPECTL_DEP1P2P3_MASK); | |
| reg |= (DWC3_GUSB3PIPECTL_DEP1P2P3_EN); | |
| dwc3_exynos_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg); | |
| } | |
| if (!DWC3_VER_IS_PRIOR(DWC3, 250A)) { | |
| reg = dwc3_exynos_readl(dwc->regs, DWC3_GUSB3PIPECTL(0)); | |
| reg |= DWC3_GUSB3PIPECTL_DISRXDETINP3; | |
| dwc3_exynos_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg); | |
| } | |
| reg = dwc3_exynos_readl(dwc->regs, DWC3_GUSB3PIPECTL(0)); | |
| if (exynos->config.ux_exit_in_px_quirk) | |
| reg |= DWC3_GUSB3PIPECTL_UX_EXIT_PX; | |
| if (exynos->config.elastic_buf_mode_quirk) | |
| reg |= DWC3_ELASTIC_BUFFER_MODE; | |
| dwc3_exynos_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg); | |
| if (DWC3_VER_IS(DWC31, 170A)) { | |
| reg = dwc3_exynos_readl(dwc->regs, DWC3_LLUCTL); | |
| reg |= (DWC3_PENDING_HP_TIMER_US(0xb) | DWC3_EN_US_HP_TIMER); | |
| reg |= DWC3_FORCE_GEN1; | |
| dwc3_exynos_writel(dwc->regs, DWC3_LLUCTL, reg); | |
| reg = dwc3_exynos_readl(dwc->regs, DWC3_LSKIPFREQ); | |
| reg |= (DWC3_PM_ENTRY_TIMER_US(0x9) | | |
| DWC3_PM_LC_TIMER_US(0x5) | DWC3_EN_PM_TIMER_US); | |
| dwc3_exynos_writel(dwc->regs, DWC3_LSKIPFREQ, reg); | |
| } else if (DWC3_VER_IS_WITHIN(DWC31, 180A, 190A)) { | |
| reg = dwc3_exynos_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(0x0)); | |
| if (exynos->config.force_gen1 && exynos->force_speed != USB_SPEED_SUPER_PLUS) | |
| reg |= DWC3_FORCE_GEN1; | |
| dwc3_exynos_writel(dwc->regs, DWC3_LLUCTL, reg); | |
| reg = dwc3_exynos_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_exynos_writel(dwc->regs, DWC3_LSKIPFREQ, reg); | |
| reg = dwc3_exynos_readl(dwc->regs, DWC3_GUSB3PIPECTL(0)); | |
| reg &= ~DWC3_GUSB3PIPECTL_DISRXDETINP3; | |
| reg &= ~DWC3_GUSB3PIPECTL_RX_DETOPOLL; | |
| dwc3_exynos_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg); | |
| /* | |
| * Use Default Value | |
| * reg = dwc3_exynos_readl(dwc->regs, DWC3_LU3LFPSRXTIM); | |
| * dwc3_exynos_writel(dwc->regs, DWC3_LU3LFPSRXTIM, reg); | |
| */ | |
| reg = dwc3_exynos_readl(dwc->regs, DWC3_GSBUSCFG0); | |
| reg |= (DWC3_GSBUSCFG0_INCR8BRSTEN | | |
| DWC3_GSBUSCFG0_INCR4BRSTEN); | |
| dwc3_exynos_writel(dwc->regs, DWC3_GSBUSCFG0, reg); | |
| reg = dwc3_exynos_readl(dwc->regs, DWC3_BU31RHBDBG); | |
| reg |= DWC3_BU31RHBDBG_TOUTCTL; | |
| dwc3_exynos_writel(dwc->regs, DWC3_BU31RHBDBG, reg); | |
| reg = dwc3_exynos_readl(dwc->regs, DWC3_GUCTL1); | |
| reg &= ~DWC3_GUCTL1_IP_GAP_ADD_ON_MASK; | |
| reg |= DWC3_GUCTL1_IP_GAP_ADD_ON(0x1); | |
| dwc3_exynos_writel(dwc->regs, DWC3_GUCTL1, reg); | |
| } | |
| /* | |
| * WORKAROUND: DWC3 revisions 2.10a and earlier have a bug | |
| * Race Condition in PORTSC Write Followed by Read | |
| * If the software quickly does a read to the PORTSC, | |
| * some fields (port status change related fields | |
| * like OCC, etc.) may not have correct value | |
| * due to the current way of handling these bits. | |
| * After clearing the status register (for example, OCC) bit | |
| * by writing PORTSC tregister, software can insert some delay | |
| * (for example, 5 mac2_clk -> UTMI clock = 60 MHz -> | |
| * (16.66 ns x 5 = 84ns)) before reading the PORTSC to check status. | |
| */ | |
| if (DWC3_VER_IS_PRIOR(DWC3, 220A)) { | |
| reg = dwc3_exynos_readl(dwc->regs, DWC3_GUSB2PHYCFG(0)); | |
| reg |= (DWC3_GUSB2PHYCFG_PHYIF(UTMI_PHYIF_16_BIT)); | |
| dwc3_exynos_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg); | |
| } | |
| } | |
| static void dwc3_exynos_phy_setup(struct dwc3 *dwc, struct dwc3_exynos *exynos) | |
| { | |
| u32 reg; | |
| reg = dwc3_exynos_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_exynos_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg); | |
| reg = dwc3_exynos_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_exynos_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg); | |
| } | |
| /* Exynos Specific Configurations */ | |
| int dwc3_exynos_core_init(struct dwc3 *dwc, struct dwc3_exynos *exynos) | |
| { | |
| u32 reg; | |
| u32 dft; | |
| dwc3_exynos_phy_setup(dwc, exynos); | |
| dwc3_core_config(dwc, exynos); | |
| if (DWC3_VER_IS_WITHIN(DWC31, 180A, 190A)) { | |
| /* FOR ref_clk 19.2MHz */ | |
| reg = dwc3_exynos_readl(dwc->regs, DWC3_GFLADJ); | |
| dft = reg & DWC3_GFLADJ_30MHZ_MASK; | |
| if (dft != dwc->fladj) { | |
| reg &= ~DWC3_GFLADJ_30MHZ_MASK; | |
| reg |= dwc->fladj; | |
| } | |
| reg &= ~DWC3_GFLADJ_REFCLK_240MHZ_DECR_MASK; | |
| reg |= DWC3_GFLADJ_REFCLK_240MHZ_DECR(0xc); | |
| reg |= DWC3_GFLADJ_REFCLK_240MHZDECR_PLS1; | |
| reg |= DWC3_GFLADJ_REFCLK_LPM_SEL; | |
| reg &= ~DWC3_GFLADJ_REFCLK_FLADJ_MASK; | |
| reg |= DWC3_GFLADJ_30MHZ_SDBND_SEL; | |
| dwc3_exynos_writel(dwc->regs, DWC3_GFLADJ, reg); | |
| } | |
| reg = dwc3_exynos_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; | |
| dwc3_exynos_writel(dwc->regs, DWC3_GCTL, reg); | |
| return 0; | |
| } | |
| void dwc3_exynos_gadget_disconnect_proc(struct dwc3 *dwc) | |
| { | |
| int reg; | |
| reg = dwc3_exynos_readl(dwc->regs, DWC3_DCTL); | |
| reg &= ~DWC3_DCTL_INITU1ENA; | |
| dwc3_exynos_writel(dwc->regs, DWC3_DCTL, reg); | |
| reg &= ~DWC3_DCTL_INITU2ENA; | |
| dwc3_exynos_writel(dwc->regs, DWC3_DCTL, reg); | |
| 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_exynos_readl(dwc->regs, DWC3_GUSB3PIPECTL(0)); | |
| if (on) | |
| reg |= DWC3_GUSB3PIPECTL_SUSPHY; | |
| else | |
| reg &= ~DWC3_GUSB3PIPECTL_SUSPHY; | |
| dwc3_exynos_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg); | |
| reg = dwc3_exynos_readl(dwc->regs, DWC3_GUSB2PHYCFG(0)); | |
| if (on) | |
| reg |= DWC3_GUSB2PHYCFG_SUSPHY; | |
| else | |
| reg &= ~DWC3_GUSB2PHYCFG_SUSPHY; | |
| dwc3_exynos_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg); | |
| 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_dbg(dev, "Set USB Bus clock to 66Mhz\n"); | |
| clk_set_rate(exynos->bus_clock, 66666666); | |
| } else if (clk_level == 1) { | |
| dev_dbg(dev, "Set USB Bus clock to 177Mhz\n"); | |
| clk_set_rate(exynos->bus_clock, 177750000); | |
| } else if (clk_level == 0) { | |
| dev_dbg(dev, "Set USB Bus clock to 266Mhz\n"); | |
| clk_set_rate(exynos->bus_clock, 266625000); | |
| } else { | |
| dev_dbg(dev, "Unsupported clock level"); | |
| } | |
| dev_dbg(dev, "Changed USB Bus clock %lu\n", | |
| clk_get_rate(exynos->bus_clock)); | |
| } | |
| return 0; | |
| } | |
| int dwc3_exynos_host_event(struct device *dev, int action) | |
| { | |
| struct dwc3_exynos *exynos; | |
| struct dwc3_otg *dotg; | |
| exynos = dev_get_drvdata(dev); | |
| if (!exynos) | |
| return -ENOENT; | |
| dotg = exynos->dotg; | |
| if (!dotg) | |
| return -ENOENT; | |
| if (dotg->host_on != action) { | |
| dotg->host_on = action; | |
| dwc3_exynos_set_role(dotg); | |
| } | |
| return 0; | |
| } | |
| int dwc3_exynos_device_event(struct device *dev, bool action) | |
| { | |
| struct dwc3_exynos *exynos; | |
| struct dwc3_otg *dotg; | |
| exynos = dev_get_drvdata(dev); | |
| if (!exynos) | |
| return -ENOENT; | |
| dotg = exynos->dotg; | |
| if (!dotg) | |
| return -ENOENT; | |
| if (dotg->device_on != action) { | |
| dotg->device_on = action; | |
| dwc3_exynos_set_role(dotg); | |
| } | |
| return 0; | |
| } | |
| bool dwc3_exynos_check_usb_suspend(struct dwc3_otg *dotg) | |
| { | |
| int wait_counter = 0; | |
| do { | |
| if (!dotg->dwc3_suspended) | |
| break; | |
| wait_counter++; | |
| msleep(20); | |
| } while (wait_counter < DWC3_EXYNOS_MAX_WAIT_COUNT); | |
| return wait_counter < DWC3_EXYNOS_MAX_WAIT_COUNT; | |
| } | |
| /* | |
| * dwc3_exynos_phy_enable - received combo phy control. | |
| */ | |
| int dwc3_exynos_phy_enable(int owner, bool on) | |
| { | |
| struct dwc3_exynos *exynos; | |
| struct device_node *np = NULL; | |
| struct platform_device *pdev = NULL; | |
| struct dwc3 *dwc; | |
| struct device *dev; | |
| struct dwc3_otg *dotg; | |
| int ret = 0; | |
| pr_info("%s owner=%d on=%d +\n", __func__, owner, on); | |
| np = of_find_compatible_node(NULL, NULL, "samsung,exynos9-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; | |
| } | |
| dwc = exynos->dwc; | |
| dev = dwc->dev; | |
| dotg = exynos->dotg; | |
| if (on) { | |
| if (!exynos->phy_owner_bits && !dwc3_exynos_check_usb_suspend(dotg)) | |
| dev_err(dev, "too long to wait for dwc3 suspended\n"); | |
| mutex_lock(&dotg->lock); | |
| exynos->need_dr_role = 1; | |
| ret = pm_runtime_resume_and_get(dev); | |
| if (ret < 0) { | |
| dev_err(dwc->dev, "%s: failed to initialize core: %d\n", | |
| __func__, ret); | |
| } | |
| exynos->need_dr_role = 0; | |
| exynos->phy_owner_bits |= BIT(owner); | |
| mutex_unlock(&dotg->lock); | |
| } else { | |
| mutex_lock(&dotg->lock); | |
| if (!dotg->otg_connection) | |
| exynos->dwc->current_dr_role = DWC3_GCTL_PRTCAP_DEVICE; | |
| pm_runtime_put_sync_suspend(dev); | |
| exynos->phy_owner_bits &= ~BIT(owner); | |
| mutex_unlock(&dotg->lock); | |
| } | |
| err: | |
| 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; | |
| } | |
| #if IS_ENABLED(CONFIG_USB_XHCI_GOOG_DMA) | |
| static struct xhci_goog_dma_coherent_mem **dwc3_exynos_get_dma_coherent_mem(struct device *dev) | |
| { | |
| struct xhci_goog_dma_coherent_mem **dma_mem = NULL; | |
| struct device *dwc3_exynos = dev->parent; | |
| struct dwc3_exynos *exynos = dev_get_drvdata(dwc3_exynos); | |
| if (exynos) { | |
| if (!exynos->mem) { | |
| exynos->mem = devm_kzalloc(dev, | |
| XHCI_GOOG_DMA_RMEM_MAX*sizeof(struct xhci_goog_dma_coherent_mem *), | |
| GFP_KERNEL); | |
| } | |
| dma_mem = exynos->mem; | |
| } | |
| return dma_mem; | |
| } | |
| static void dwc3_exynos_put_dma_coherent_mem(struct device *dev) | |
| { | |
| struct device *dwc3_exynos = dev->parent; | |
| struct dwc3_exynos *exynos = dev_get_drvdata(dwc3_exynos); | |
| if (exynos) { | |
| if (exynos->mem) { | |
| dev_dbg(dev, "Free the DMA memory.\n"); | |
| exynos->mem[XHCI_GOOG_DMA_RMEM_SRAM] = NULL; | |
| exynos->mem[XHCI_GOOG_DMA_RMEM_DRAM] = NULL; | |
| devm_kfree(dev, exynos->mem); | |
| exynos->mem = NULL; | |
| } | |
| } | |
| } | |
| #endif | |
| 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; | |
| int irq; | |
| /* 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; | |
| irq = platform_get_irq(dwc3_pdev, 0); | |
| if (irq < 0) { | |
| dev_err(dev, "missing irq resource\n"); | |
| return irq; | |
| } | |
| dwc->xhci_resources[1].start = irq; | |
| dwc->xhci_resources[1].end = irq; | |
| dwc->xhci_resources[1].flags = IORESOURCE_IRQ | irq_get_trigger_type(irq); | |
| dwc->xhci_resources[1].name = of_node_full_name(dwc3_pdev->dev.of_node); | |
| #if IS_ENABLED(CONFIG_USB_XHCI_GOOG_DMA) | |
| xhci_goog_register_get_cb(dwc3_exynos_get_dma_coherent_mem); | |
| xhci_goog_register_put_cb(dwc3_exynos_put_dma_coherent_mem); | |
| #endif | |
| 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); | |
| goto err; | |
| } | |
| 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 (dwc->revision <= DWC3_REVISION_300A) | |
| props[prop_idx++] = PROPERTY_ENTRY_BOOL("quirk-broken-port-ped"); | |
| if (prop_idx) { | |
| ret = device_create_managed_software_node(&xhci->dev, props, NULL); | |
| if (ret) { | |
| dev_err(dwc->dev, "failed to add properties to xHCI\n"); | |
| goto err; | |
| } | |
| } | |
| dwc->xhci = xhci; | |
| return 0; | |
| err: | |
| platform_device_put(xhci); | |
| return ret; | |
| } | |
| EXPORT_SYMBOL_GPL(dwc3_exynos_host_init); | |
| void dwc3_exynos_host_exit(struct dwc3_exynos *exynos) | |
| { | |
| struct dwc3 *dwc = exynos->dwc; | |
| platform_device_unregister(dwc->xhci); | |
| #if IS_ENABLED(CONFIG_USB_XHCI_GOOG_DMA) | |
| xhci_goog_unregister_get_cb(); | |
| xhci_goog_unregister_put_cb(); | |
| #endif | |
| } | |
| EXPORT_SYMBOL_GPL(dwc3_exynos_host_exit); | |
| static int dwc3_exynos_device_notifier(struct notifier_block *nb, | |
| unsigned long action, void *dev) | |
| { | |
| struct dwc3_exynos *exynos = container_of(nb, struct dwc3_exynos, device_nb); | |
| dev_info(exynos->dev, "turn %s USB gadget\n", action ? "on" : "off"); | |
| if (!exynos->usb_data_enabled) { | |
| dev_info(exynos->dev, "skip the notification due to USB enumeration disabled\n"); | |
| return NOTIFY_OK; | |
| } | |
| dwc3_exynos_device_event(exynos->dev, action); | |
| return NOTIFY_OK; | |
| } | |
| static int dwc3_exynos_host_notifier(struct notifier_block *nb, | |
| unsigned long action, void *dev) | |
| { | |
| struct dwc3_exynos *exynos = container_of(nb, struct dwc3_exynos, host_nb); | |
| dev_info(exynos->dev, "turn %s USB host\n", action ? "on" : "off"); | |
| if (!exynos->usb_data_enabled) { | |
| dev_info(exynos->dev, "skip the notification due to USB enumeration disabled\n"); | |
| return NOTIFY_OK; | |
| } | |
| dwc3_exynos_host_event(exynos->dev, action); | |
| return NOTIFY_OK; | |
| } | |
| static int dwc3_exynos_extcon_register(struct dwc3_exynos *exynos) | |
| { | |
| struct device *dev = exynos->dev; | |
| int ret = 0; | |
| if (!of_property_read_bool(dev->of_node, "extcon")) | |
| return -EINVAL; | |
| exynos->edev = extcon_get_edev_by_phandle(dev, 0); | |
| if (IS_ERR_OR_NULL(exynos->edev)) { | |
| dev_err(exynos->dev, "couldn't get extcon\n"); | |
| return exynos->edev ? PTR_ERR(exynos->edev) : -ENODEV; | |
| } | |
| exynos->device_nb.notifier_call = dwc3_exynos_device_notifier; | |
| ret = extcon_register_notifier(exynos->edev, EXTCON_USB, &exynos->device_nb); | |
| if (ret < 0) { | |
| dev_err(exynos->dev, "couldn't register notifier for EXTCON_USB\n"); | |
| return ret; | |
| } | |
| exynos->host_nb.notifier_call = dwc3_exynos_host_notifier; | |
| ret = extcon_register_notifier(exynos->edev, EXTCON_USB_HOST, &exynos->host_nb); | |
| if (ret < 0) | |
| dev_err(exynos->dev, "couldn't register notifier for EXTCON_USB_HOST\n"); | |
| return ret; | |
| } | |
| 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, "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, "is_not_vbus_pad", &value)) { | |
| exynos->config.is_not_vbus_pad = value ? true : false; | |
| dev_info(dev, "is_not_vbus_pad 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, "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, "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, "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; | |
| } | |
| exynos->config.no_extra_delay = device_property_read_bool(dev, | |
| "no_extra_delay"); | |
| exynos->config.ux_exit_in_px_quirk = device_property_read_bool(dev, | |
| "ux_exit_in_px_quirk"); | |
| exynos->config.elastic_buf_mode_quirk = device_property_read_bool(dev, | |
| "elastic_buf_mode_quirk"); | |
| exynos->config.force_gen1 = device_property_read_bool(dev, | |
| "force_gen1"); | |
| exynos->config.u1u2_exitfail_to_recov_quirk = device_property_read_bool( | |
| dev, "u1u2_exitfail_quirk"); | |
| return ret; | |
| } | |
| /* -------------------------------------------------------------------------- */ | |
| /* Current data role is updated after the data role change has been completed */ | |
| static ssize_t | |
| dwc3_exynos_otg_state_show(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| struct dwc3_exynos *exynos = dev_get_drvdata(dev); | |
| struct dwc3_otg *dotg; | |
| int ret = 0; | |
| mutex_lock(&exynos->dotg_lock); | |
| dotg = exynos->dotg; | |
| if (!dotg) { | |
| mutex_unlock(&exynos->dotg_lock); | |
| return -ENOENT; | |
| } | |
| ret = sysfs_emit(buf, "%s\n", usb_role_string(dotg->current_role)); | |
| mutex_unlock(&exynos->dotg_lock); | |
| return ret; | |
| } | |
| static DEVICE_ATTR_RO(dwc3_exynos_otg_state); | |
| static ssize_t | |
| dwc3_exynos_otg_b_sess_show(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| struct dwc3_exynos *exynos = dev_get_drvdata(dev); | |
| struct dwc3_otg *dotg; | |
| int ret = 0; | |
| mutex_lock(&exynos->dotg_lock); | |
| dotg = exynos->dotg; | |
| if (!dotg) { | |
| mutex_unlock(&exynos->dotg_lock); | |
| return -ENOENT; | |
| } | |
| ret = sysfs_emit(buf, "%d\n", dotg->device_on); | |
| mutex_unlock(&exynos->dotg_lock); | |
| return ret; | |
| } | |
| static ssize_t | |
| dwc3_exynos_otg_b_sess_store(struct device *dev, | |
| struct device_attribute *attr, const char *buf, size_t n) | |
| { | |
| struct dwc3_exynos *exynos = dev_get_drvdata(dev); | |
| struct dwc3_otg *dotg; | |
| int b_sess_vld; | |
| if (kstrtoint(buf, 10, &b_sess_vld) != 0) | |
| return -EINVAL; | |
| mutex_lock(&exynos->dotg_lock); | |
| dotg = exynos->dotg; | |
| if (!dotg) { | |
| mutex_unlock(&exynos->dotg_lock); | |
| return -ENOENT; | |
| } | |
| dwc3_exynos_device_event(exynos->dev, !!b_sess_vld); | |
| dwc3_exynos_wait_role(dotg); | |
| mutex_unlock(&exynos->dotg_lock); | |
| return n; | |
| } | |
| static DEVICE_ATTR_RW(dwc3_exynos_otg_b_sess); | |
| static ssize_t | |
| dwc3_exynos_otg_id_show(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| struct dwc3_exynos *exynos = dev_get_drvdata(dev); | |
| struct dwc3_otg *dotg; | |
| int ret = 0; | |
| mutex_lock(&exynos->dotg_lock); | |
| dotg = exynos->dotg; | |
| if (!dotg) { | |
| mutex_unlock(&exynos->dotg_lock); | |
| return -ENOENT; | |
| } | |
| // id state is true when host mode is off, vice versa. | |
| ret = sysfs_emit(buf, "%d\n", !dotg->host_on); | |
| mutex_unlock(&exynos->dotg_lock); | |
| return ret; | |
| } | |
| static ssize_t | |
| dwc3_exynos_otg_id_store(struct device *dev, | |
| struct device_attribute *attr, const char *buf, size_t n) | |
| { | |
| struct dwc3_exynos *exynos = dev_get_drvdata(dev); | |
| struct dwc3_otg *dotg; | |
| int id; | |
| if (kstrtoint(buf, 10, &id) != 0) | |
| return -EINVAL; | |
| mutex_lock(&exynos->dotg_lock); | |
| dotg = exynos->dotg; | |
| if (!dotg) { | |
| mutex_unlock(&exynos->dotg_lock); | |
| return -ENOENT; | |
| } | |
| dwc3_exynos_host_event(exynos->dev, !id); | |
| dwc3_exynos_wait_role(dotg); | |
| mutex_unlock(&exynos->dotg_lock); | |
| return n; | |
| } | |
| static DEVICE_ATTR_RW(dwc3_exynos_otg_id); | |
| static ssize_t dwc3_exynos_extra_delay_show(struct device *dev,struct device_attribute *attr, | |
| char *buf) | |
| { | |
| struct dwc3_exynos *exynos = dev_get_drvdata(dev); | |
| return sysfs_emit(buf, "%d\n", exynos->extra_delay); | |
| } | |
| static ssize_t dwc3_exynos_extra_delay_store(struct device *dev, struct device_attribute *attr, | |
| const char *buf, size_t n) | |
| { | |
| struct dwc3_exynos *exynos = dev_get_drvdata(dev); | |
| bool extra_delay; | |
| if (kstrtobool(buf, &extra_delay)) | |
| return -EINVAL; | |
| exynos->extra_delay = extra_delay; | |
| return n; | |
| } | |
| static DEVICE_ATTR_RW(dwc3_exynos_extra_delay); | |
| static ssize_t usb_data_enabled_show(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| struct dwc3_exynos *exynos = dev_get_drvdata(dev); | |
| return sysfs_emit(buf, "%s\n", exynos->usb_data_enabled ? "enabled" : "disabled"); | |
| } | |
| static ssize_t usb_data_enabled_store(struct device *dev, struct device_attribute *attr, | |
| const char *buf, size_t n) | |
| { | |
| struct dwc3_exynos *exynos = dev_get_drvdata(dev); | |
| bool enabled = true; | |
| if (kstrtobool(buf, &enabled)) | |
| return -EINVAL; | |
| exynos->usb_data_enabled = enabled; | |
| return n; | |
| } | |
| static DEVICE_ATTR_RW(usb_data_enabled); | |
| static ssize_t force_speed_show(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| struct dwc3_exynos *exynos = dev_get_drvdata(dev); | |
| return sysfs_emit(buf, "%s\n", usb_speed_string(exynos->force_speed)); | |
| } | |
| static ssize_t force_speed_store(struct device *dev, struct device_attribute *attr, const char *buf, | |
| size_t n) | |
| { | |
| struct dwc3_exynos *exynos = dev_get_drvdata(dev); | |
| struct dwc3_otg *dotg; | |
| bool toggle_gadget; | |
| if (sysfs_streq(buf, "super-speed-plus")) { | |
| exynos->force_speed = USB_SPEED_SUPER_PLUS; | |
| } else if (sysfs_streq(buf, "super-speed")) { | |
| exynos->force_speed = USB_SPEED_SUPER; | |
| } else if (sysfs_streq(buf, "high-speed")) { | |
| exynos->force_speed = USB_SPEED_HIGH; | |
| } else if (sysfs_streq(buf, "full-speed")) { | |
| exynos->force_speed = USB_SPEED_FULL; | |
| } else { | |
| return -EINVAL; | |
| } | |
| mutex_lock(&exynos->dotg_lock); | |
| dotg = exynos->dotg; | |
| if (!dotg) { | |
| mutex_unlock(&exynos->dotg_lock); | |
| return -ENOENT; | |
| } | |
| toggle_gadget = dotg->device_on; | |
| if (toggle_gadget) { | |
| dwc3_exynos_device_event(exynos->dev, 0); | |
| dwc3_exynos_wait_role(dotg); | |
| } | |
| exynos->dwc->maximum_speed = exynos->force_speed; | |
| exynos->dwc->gadget->max_speed = exynos->dwc->maximum_speed; | |
| if (toggle_gadget) { | |
| dwc3_exynos_device_event(exynos->dev, 1); | |
| dwc3_exynos_wait_role(dotg); | |
| } | |
| mutex_unlock(&exynos->dotg_lock); | |
| return n; | |
| } | |
| static DEVICE_ATTR_RW(force_speed); | |
| static ssize_t dwc3_exynos_gadget_state_show(struct device *dev, struct device_attribute *attr, | |
| char *buf) | |
| { | |
| struct dwc3_exynos *exynos = dev_get_drvdata(dev); | |
| return sysfs_emit(buf, "%d\n", exynos->gadget_state); | |
| } | |
| static DEVICE_ATTR_RO(dwc3_exynos_gadget_state); | |
| /* New data role is updated before the data role change is executed */ | |
| static ssize_t | |
| new_data_role_show(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| struct dwc3_exynos *exynos = dev_get_drvdata(dev); | |
| struct dwc3_otg *dotg; | |
| int ret = 0; | |
| mutex_lock(&exynos->dotg_lock); | |
| dotg = exynos->dotg; | |
| if (!dotg) { | |
| mutex_unlock(&exynos->dotg_lock); | |
| return -ENOENT; | |
| } | |
| ret = sysfs_emit(buf, "%s", usb_role_string(dotg->desired_role)); | |
| mutex_unlock(&exynos->dotg_lock); | |
| return ret; | |
| } | |
| static DEVICE_ATTR_RO(new_data_role); | |
| static struct attribute *dwc3_exynos_otg_attrs[] = { | |
| &dev_attr_dwc3_exynos_otg_id.attr, | |
| &dev_attr_dwc3_exynos_otg_b_sess.attr, | |
| &dev_attr_dwc3_exynos_otg_state.attr, | |
| &dev_attr_dwc3_exynos_extra_delay.attr, | |
| &dev_attr_usb_data_enabled.attr, | |
| &dev_attr_force_speed.attr, | |
| &dev_attr_dwc3_exynos_gadget_state.attr, | |
| &dev_attr_new_data_role.attr, | |
| NULL | |
| }; | |
| ATTRIBUTE_GROUPS(dwc3_exynos_otg); | |
| 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; | |
| int ret; | |
| struct phy *temp_usb_phy; | |
| struct device_node *s2mpu_np; | |
| struct platform_device *s2mpu_pdev; | |
| temp_usb_phy = devm_phy_get(dev, "usb2-phy"); | |
| if (IS_ERR(temp_usb_phy)) { | |
| dev_dbg(dev, "USB phy is not probed - deferred return!\n"); | |
| return -EPROBE_DEFER; | |
| } | |
| s2mpu_np = of_parse_phandle(dev->of_node, "s2mpus", 0); | |
| if (s2mpu_np) { | |
| s2mpu_pdev = of_find_device_by_node(s2mpu_np); | |
| of_node_put(s2mpu_np); | |
| if (s2mpu_pdev) { | |
| device_link_add(dev, &s2mpu_pdev->dev, | |
| DL_FLAG_AUTOREMOVE_CONSUMER | DL_FLAG_PM_RUNTIME); | |
| } | |
| } | |
| #if IS_ENABLED(CONFIG_EXYNOS_PD_HSI0) | |
| if (!exynos_pd_hsi0_get_ldo_status()) { | |
| dev_err(dev, "pd-hsi0 is not powered, deferred probe!"); | |
| return -EPROBE_DEFER; | |
| } | |
| #endif | |
| exynos = devm_kzalloc(dev, sizeof(*exynos), GFP_KERNEL); | |
| if (!exynos) | |
| return -ENOMEM; | |
| ret = dma_set_mask(dev, DMA_BIT_MASK(36)); | |
| if (ret) { | |
| dev_err(dev, "dma set mask ret = %d\n", ret); | |
| return ret; | |
| } | |
| platform_set_drvdata(pdev, exynos); | |
| exynos->dev = dev; | |
| exynos->idle_ip_index = exynos_get_idle_ip_index(dev_name(dev)); | |
| exynos_update_ip_idle_status(exynos->idle_ip_index, 0); | |
| ret = dwc3_exynos_clk_get(exynos); | |
| if (ret) | |
| return ret; | |
| /* | |
| * Directly check for the property as EIVAL and other err values can be returned in extcon | |
| * APIs in which case we want to defer probe. | |
| */ | |
| if (of_property_read_bool(dev->of_node, "extcon")) { | |
| ret = dwc3_exynos_extcon_register(exynos); | |
| if (ret < 0) { | |
| dev_err(dev, "failed to register extcon (%d)\n", ret); | |
| ret = -EPROBE_DEFER; | |
| goto vdd33_err; | |
| } | |
| } else { | |
| dev_warn(dev, "no extcon found\n"); | |
| } | |
| ret = dwc3_exynos_register_phys(exynos); | |
| if (ret) { | |
| dev_err(dev, "couldn't register PHYs\n"); | |
| goto extcon_unregister; | |
| } | |
| ret = dwc3_exynos_get_properties(exynos); | |
| if (ret) { | |
| dev_err(dev, "couldn't get properties.\n"); | |
| goto extcon_unregister; | |
| } | |
| pm_runtime_enable(dev); | |
| ret = pm_runtime_get_sync(dev); | |
| if (ret < 0) | |
| goto extcon_unregister; | |
| pm_runtime_forbid(dev); | |
| dwc3_np = of_get_child_by_name(node, "dwc3"); | |
| if (!dwc3_np) { | |
| dev_err(dev, "failed to find dwc3 core child!\n"); | |
| ret = -EEXIST; | |
| goto extcon_unregister; | |
| } | |
| exynos_usbdrd_s2mpu_manual_control(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) | |
| 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) { | |
| dev_err(dev, "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); | |
| /* set the initial value */ | |
| mutex_init(&exynos->dotg_lock); | |
| exynos->usb_data_enabled = true; | |
| exynos_usbdrd_phy_tune(exynos->dwc->usb2_generic_phy, 0); | |
| exynos_usbdrd_phy_tune(exynos->dwc->usb3_generic_phy, 0); | |
| ret = pm_runtime_put(dev); | |
| pm_runtime_allow(dev); | |
| ret = dwc3_exynos_otg_init(exynos->dwc, exynos); | |
| if (ret < 0) { | |
| dev_err(dev, "failed to initialize dwc3_exynos_otg\n"); | |
| goto populate_err; | |
| } | |
| /* disconnect gadget in probe */ | |
| usb_udc_vbus_handler(exynos->dwc->gadget, false); | |
| if (of_property_read_bool(dev->of_node, "extcon")) { | |
| /* | |
| * To avoid missing notification in kernel booting check extcon state to run state | |
| * machine. | |
| */ | |
| if (extcon_get_state(exynos->edev, EXTCON_USB) > 0) | |
| dwc3_exynos_device_event(exynos->dev, 1); | |
| else if (extcon_get_state(exynos->edev, EXTCON_USB_HOST) > 0) | |
| dwc3_exynos_host_event(exynos->dev, 1); | |
| } else { | |
| dev_warn(exynos->dev, "Couldn't find extcon. Enable vbus event forcibly."); | |
| dwc3_exynos_device_event(exynos->dev, 1); | |
| } | |
| return 0; | |
| populate_err: | |
| platform_device_unregister(exynos->usb2_phy); | |
| platform_device_unregister(exynos->usb3_phy); | |
| extcon_unregister: | |
| if (exynos->edev) { | |
| extcon_unregister_notifier(exynos->edev, EXTCON_USB, &exynos->device_nb); | |
| extcon_unregister_notifier(exynos->edev, EXTCON_USB_HOST, &exynos->host_nb); | |
| } | |
| vdd33_err: | |
| dwc3_exynos_clk_disable_unprepare(exynos); | |
| exynos_update_ip_idle_status(exynos->idle_ip_index, 1); | |
| pm_runtime_put_sync(&pdev->dev); | |
| pm_runtime_disable(&pdev->dev); | |
| dev_err(dev, "%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); | |
| struct dwc3 *dwc = exynos->dwc; | |
| mutex_lock(&exynos->dotg_lock); | |
| dwc3_exynos_otg_exit(dwc, exynos); | |
| mutex_unlock(&exynos->dotg_lock); | |
| pm_runtime_get_sync(&pdev->dev); | |
| dwc3_ulpi_exit(dwc); | |
| 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); | |
| pm_runtime_disable(&pdev->dev); | |
| if (!pm_runtime_status_suspended(&pdev->dev)) { | |
| dwc3_exynos_clk_disable_unprepare(exynos); | |
| pm_runtime_set_suspended(&pdev->dev); | |
| } | |
| return 0; | |
| } | |
| static void dwc3_exynos_shutdown(struct platform_device *pdev) | |
| { | |
| struct dwc3_exynos *exynos = platform_get_drvdata(pdev); | |
| /* | |
| * According to extcon state, turn off USB gadget or USB host | |
| * during the shutdown process. | |
| */ | |
| if (extcon_get_state(exynos->edev, EXTCON_USB) > 0) | |
| dwc3_exynos_device_event(exynos->dev, 0); | |
| else if (extcon_get_state(exynos->edev, EXTCON_USB_HOST) > 0) | |
| dwc3_exynos_host_event(exynos->dev, 0); | |
| /* unregister the notifiers for USB and USB_HOST*/ | |
| extcon_unregister_notifier(exynos->edev, EXTCON_USB, &exynos->device_nb); | |
| extcon_unregister_notifier(exynos->edev, EXTCON_USB_HOST, &exynos->host_nb); | |
| dwc3_exynos_remove(pdev); | |
| return; | |
| } | |
| #ifdef CONFIG_PM | |
| static int dwc3_exynos_runtime_suspend(struct device *dev) | |
| { | |
| struct dwc3_exynos *exynos = dev_get_drvdata(dev); | |
| struct dwc3 *dwc; | |
| unsigned long flags; | |
| if (!exynos) | |
| return 0; | |
| if (pm_runtime_suspended(dev)) | |
| return 0; | |
| dwc3_exynos_clk_disable_unprepare(exynos); | |
| /* inform what USB state is idle to IDLE_IP */ | |
| exynos_update_ip_idle_status(exynos->idle_ip_index, 1); | |
| dwc = exynos->dwc; | |
| spin_lock_irqsave(&dwc->lock, flags); | |
| /* After disconnecting cable, it will ignore core operations like | |
| * dwc3_suspend/resume in core.c | |
| */ | |
| dwc->current_dr_role = DWC3_EXYNOS_IGNORE_CORE_OPS; | |
| spin_unlock_irqrestore(&dwc->lock, flags); | |
| return 0; | |
| } | |
| static int dwc3_exynos_runtime_resume(struct device *dev) | |
| { | |
| struct dwc3_exynos *exynos = dev_get_drvdata(dev); | |
| int ret = 0; | |
| if (!exynos) | |
| return 0; | |
| if (exynos->need_dr_role) | |
| exynos->dwc->current_dr_role = DWC3_GCTL_PRTCAP_DEVICE; | |
| /* inform what USB state is not idle to IDLE_IP */ | |
| exynos_update_ip_idle_status(exynos->idle_ip_index, 0); | |
| ret = dwc3_exynos_clk_prepare_enable(exynos); | |
| if (ret) { | |
| dev_err(dev, "%s: clk_prepare_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); | |
| if (pm_runtime_suspended(dev)) | |
| return 0; | |
| dwc3_exynos_clk_disable_unprepare(exynos); | |
| return 0; | |
| } | |
| static int dwc3_exynos_resume(struct device *dev) | |
| { | |
| struct dwc3_exynos *exynos = dev_get_drvdata(dev); | |
| int ret; | |
| ret = dwc3_exynos_clk_prepare_enable(exynos); | |
| if (ret) { | |
| dev_err(dev, "%s: clk_prepare_enable failed\n", __func__); | |
| return ret; | |
| } | |
| pm_runtime_disable(dev); | |
| pm_runtime_set_active(dev); | |
| pm_runtime_enable(dev); | |
| 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, | |
| .shutdown = dwc3_exynos_shutdown, | |
| .driver = { | |
| .name = "exynos-dwc3", | |
| .of_match_table = exynos_dwc3_match, | |
| .dev_groups = dwc3_exynos_otg_groups, | |
| .pm = DEV_PM_OPS, | |
| }, | |
| }; | |
| module_platform_driver(dwc3_exynos_driver); | |
| MODULE_SOFTDEP("pre: phy-exynos-usbdrd-super"); | |
| MODULE_AUTHOR("Anton Tikhomirov <[email protected]>"); | |
| MODULE_LICENSE("GPL v2"); | |
| MODULE_DESCRIPTION("DesignWare USB3 EXYNOS Glue Layer"); |