+// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2012 Oleksandr Tymoshenko <gonzo@freebsd.org>
* Copyright (C) 2014 Marek Vasut <marex@denx.de>
- *
- * SPDX-License-Identifier: GPL-2.0+
*/
#include <common.h>
#include <usbroothubdes.h>
#include <wait_bit.h>
#include <asm/io.h>
+#include <power/regulator.h>
+#include <reset.h>
#include "dwc2.h"
-DECLARE_GLOBAL_DATA_PTR;
-
/* Use only HC channel 0. */
#define DWC2_HC_CHANNEL 0
#define DWC2_STATUS_BUF_SIZE 64
-#define DWC2_DATA_BUF_SIZE (64 * 1024)
+#define DWC2_DATA_BUF_SIZE (CONFIG_USB_DWC2_BUFFER_SIZE * 1024)
#define MAX_DEVICE 16
#define MAX_ENDPOINT 16
struct dwc2_priv {
-#ifdef CONFIG_DM_USB
+#if CONFIG_IS_ENABLED(DM_USB)
uint8_t aligned_buffer[DWC2_DATA_BUF_SIZE] __aligned(ARCH_DMA_MINALIGN);
uint8_t status_buffer[DWC2_STATUS_BUF_SIZE] __aligned(ARCH_DMA_MINALIGN);
+#ifdef CONFIG_DM_REGULATOR
+ struct udevice *vbus_supply;
+#endif
#else
uint8_t *aligned_buffer;
uint8_t *status_buffer;
struct dwc2_core_regs *regs;
int root_hub_devnum;
bool ext_vbus;
+ /*
+ * The hnp/srp capability must be disabled if the platform
+ * does't support hnp/srp. Otherwise the force mode can't work.
+ */
+ bool hnp_srp_disable;
bool oc_disable;
+
+ struct reset_ctl_bulk resets;
};
-#ifndef CONFIG_DM_USB
+#if !CONFIG_IS_ENABLED(DM_USB)
/* We need cacheline-aligned buffers for DMA transfers and dcache support */
DEFINE_ALIGN_BUFFER(uint8_t, aligned_buffer_addr, DWC2_DATA_BUF_SIZE,
ARCH_DMA_MINALIGN);
writel(DWC2_GRSTCTL_TXFFLSH | (num << DWC2_GRSTCTL_TXFNUM_OFFSET),
®s->grstctl);
- ret = wait_for_bit(__func__, ®s->grstctl, DWC2_GRSTCTL_TXFFLSH,
- false, 1000, false);
+ ret = wait_for_bit_le32(®s->grstctl, DWC2_GRSTCTL_TXFFLSH,
+ false, 1000, false);
if (ret)
- printf("%s: Timeout!\n", __func__);
+ dev_info(dev, "%s: Timeout!\n", __func__);
/* Wait for 3 PHY Clocks */
udelay(1);
int ret;
writel(DWC2_GRSTCTL_RXFFLSH, ®s->grstctl);
- ret = wait_for_bit(__func__, ®s->grstctl, DWC2_GRSTCTL_RXFFLSH,
- false, 1000, false);
+ ret = wait_for_bit_le32(®s->grstctl, DWC2_GRSTCTL_RXFFLSH,
+ false, 1000, false);
if (ret)
- printf("%s: Timeout!\n", __func__);
+ dev_info(dev, "%s: Timeout!\n", __func__);
/* Wait for 3 PHY Clocks */
udelay(1);
int ret;
/* Wait for AHB master IDLE state. */
- ret = wait_for_bit(__func__, ®s->grstctl, DWC2_GRSTCTL_AHBIDLE,
- true, 1000, false);
+ ret = wait_for_bit_le32(®s->grstctl, DWC2_GRSTCTL_AHBIDLE,
+ true, 1000, false);
if (ret)
- printf("%s: Timeout!\n", __func__);
+ dev_info(dev, "%s: Timeout!\n", __func__);
/* Core Soft Reset */
writel(DWC2_GRSTCTL_CSFTRST, ®s->grstctl);
- ret = wait_for_bit(__func__, ®s->grstctl, DWC2_GRSTCTL_CSFTRST,
- false, 1000, false);
+ ret = wait_for_bit_le32(®s->grstctl, DWC2_GRSTCTL_CSFTRST,
+ false, 1000, false);
if (ret)
- printf("%s: Timeout!\n", __func__);
+ dev_info(dev, "%s: Timeout!\n", __func__);
/*
* Wait for core to come out of reset.
mdelay(100);
}
+#if CONFIG_IS_ENABLED(DM_USB) && defined(CONFIG_DM_REGULATOR)
+static int dwc_vbus_supply_init(struct udevice *dev)
+{
+ struct dwc2_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ ret = device_get_supply_regulator(dev, "vbus-supply",
+ &priv->vbus_supply);
+ if (ret) {
+ debug("%s: No vbus supply\n", dev->name);
+ return 0;
+ }
+
+ ret = regulator_set_enable(priv->vbus_supply, true);
+ if (ret) {
+ dev_err(dev, "Error enabling vbus supply\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int dwc_vbus_supply_exit(struct udevice *dev)
+{
+ struct dwc2_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ if (priv->vbus_supply) {
+ ret = regulator_set_enable(priv->vbus_supply, false);
+ if (ret) {
+ dev_err(dev, "Error disabling vbus supply\n");
+ return ret;
+ }
+ }
+
+ return 0;
+}
+#else
+static int dwc_vbus_supply_init(struct udevice *dev)
+{
+ return 0;
+}
+
+#if CONFIG_IS_ENABLED(DM_USB)
+static int dwc_vbus_supply_exit(struct udevice *dev)
+{
+ return 0;
+}
+#endif
+#endif
+
/*
* This function initializes the DWC_otg controller registers for
* host mode.
* request queues. Host channels are reset to ensure that they are ready for
* performing transfers.
*
+ * @param dev USB Device (NULL if driver model is not being used)
* @param regs Programming view of DWC_otg controller
*
*/
-static void dwc_otg_core_host_init(struct dwc2_core_regs *regs)
+static void dwc_otg_core_host_init(struct udevice *dev,
+ struct dwc2_core_regs *regs)
{
uint32_t nptxfifosize = 0;
uint32_t ptxfifosize = 0;
clrsetbits_le32(®s->hc_regs[i].hcchar,
DWC2_HCCHAR_EPDIR,
DWC2_HCCHAR_CHEN | DWC2_HCCHAR_CHDIS);
- ret = wait_for_bit(__func__, ®s->hc_regs[i].hcchar,
- DWC2_HCCHAR_CHEN, false, 1000, false);
+ ret = wait_for_bit_le32(®s->hc_regs[i].hcchar,
+ DWC2_HCCHAR_CHEN, false, 1000, false);
if (ret)
- printf("%s: Timeout!\n", __func__);
+ dev_info("%s: Timeout!\n", __func__);
}
/* Turn on the vbus power. */
writel(hprt0, ®s->hprt0);
}
}
+
+ if (dev)
+ dwc_vbus_supply_init(dev);
}
/*
usbcfg &= ~DWC2_GUSBCFG_DDRSEL;
#endif
} else { /* UTMI+ interface */
-#if (CONFIG_DWC2_UTMI_PHY_WIDTH == 16)
+#if (CONFIG_DWC2_UTMI_WIDTH == 16)
usbcfg |= DWC2_GUSBCFG_PHYIF;
#endif
}
usbcfg |= DWC2_GUSBCFG_ULPI_CLK_SUS_M;
}
#endif
+ if (priv->hnp_srp_disable)
+ usbcfg |= DWC2_GUSBCFG_FORCEHOSTMODE;
+
writel(usbcfg, ®s->gusbcfg);
/* Program the GAHBCFG Register. */
writel(ahbcfg, ®s->gahbcfg);
- /* Program the GUSBCFG register for HNP/SRP. */
- setbits_le32(®s->gusbcfg, DWC2_GUSBCFG_HNPCAP | DWC2_GUSBCFG_SRPCAP);
+ /* Program the capabilities in GUSBCFG Register */
+ usbcfg = 0;
+ if (!priv->hnp_srp_disable)
+ usbcfg |= DWC2_GUSBCFG_HNPCAP | DWC2_GUSBCFG_SRPCAP;
#ifdef CONFIG_DWC2_IC_USB_CAP
- setbits_le32(®s->gusbcfg, DWC2_GUSBCFG_IC_USB_CAP);
+ usbcfg |= DWC2_GUSBCFG_IC_USB_CAP;
#endif
+
+ setbits_le32(®s->gusbcfg, usbcfg);
}
/*
int ret;
uint32_t hcint, hctsiz;
- ret = wait_for_bit(__func__, &hc_regs->hcint, DWC2_HCINT_CHHLTD, true,
- 1000, false);
+ ret = wait_for_bit_le32(&hc_regs->hcint, DWC2_HCINT_CHHLTD, true,
+ 2000, false);
if (ret)
return ret;
(*pid << DWC2_HCTSIZ_PID_OFFSET),
&hc_regs->hctsiz);
- if (!in && xfer_len) {
- memcpy(aligned_buffer, buffer, xfer_len);
-
- flush_dcache_range((unsigned long)aligned_buffer,
- (unsigned long)aligned_buffer +
- roundup(xfer_len, ARCH_DMA_MINALIGN));
+ if (xfer_len) {
+ if (in) {
+ invalidate_dcache_range(
+ (uintptr_t)aligned_buffer,
+ (uintptr_t)aligned_buffer +
+ roundup(xfer_len, ARCH_DMA_MINALIGN));
+ } else {
+ memcpy(aligned_buffer, buffer, xfer_len);
+ flush_dcache_range(
+ (uintptr_t)aligned_buffer,
+ (uintptr_t)aligned_buffer +
+ roundup(xfer_len, ARCH_DMA_MINALIGN));
+ }
}
writel(phys_to_bus((unsigned long)aligned_buffer), &hc_regs->hcdma);
timeout = get_timer(0) + USB_TIMEOUT_MS(pipe);
for (;;) {
if (get_timer(0) > timeout) {
- printf("Timeout poll on interrupt endpoint\n");
+ dev_err(dev, "Timeout poll on interrupt endpoint\n");
return -ETIMEDOUT;
}
ret = _submit_bulk_msg(priv, dev, pipe, buffer, len);
}
}
-static int dwc2_init_common(struct dwc2_priv *priv)
+static int dwc2_reset(struct udevice *dev)
+{
+ int ret;
+ struct dwc2_priv *priv = dev_get_priv(dev);
+
+ ret = reset_get_bulk(dev, &priv->resets);
+ if (ret) {
+ dev_warn(dev, "Can't get reset: %d\n", ret);
+ /* Return 0 if error due to !CONFIG_DM_RESET and reset
+ * DT property is not present.
+ */
+ if (ret == -ENOENT || ret == -ENOTSUPP)
+ return 0;
+ else
+ return ret;
+ }
+
+ ret = reset_deassert_bulk(&priv->resets);
+ if (ret) {
+ reset_release_bulk(&priv->resets);
+ dev_err(dev, "Failed to reset: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int dwc2_init_common(struct udevice *dev, struct dwc2_priv *priv)
{
struct dwc2_core_regs *regs = priv->regs;
uint32_t snpsid;
int i, j;
+ int ret;
+
+ ret = dwc2_reset(dev);
+ if (ret)
+ return ret;
snpsid = readl(®s->gsnpsid);
- printf("Core Release: %x.%03x\n", snpsid >> 12 & 0xf, snpsid & 0xfff);
+ dev_info(dev, "Core Release: %x.%03x\n",
+ snpsid >> 12 & 0xf, snpsid & 0xfff);
if ((snpsid & DWC2_SNPSID_DEVID_MASK) != DWC2_SNPSID_DEVID_VER_2xx &&
(snpsid & DWC2_SNPSID_DEVID_MASK) != DWC2_SNPSID_DEVID_VER_3xx) {
- printf("SNPSID invalid (not DWC2 OTG device): %08x\n", snpsid);
+ dev_info(dev, "SNPSID invalid (not DWC2 OTG device): %08x\n",
+ snpsid);
return -ENODEV;
}
#endif
dwc_otg_core_init(priv);
- dwc_otg_core_host_init(regs);
+ dwc_otg_core_host_init(dev, regs);
clrsetbits_le32(®s->hprt0, DWC2_HPRT0_PRTENA |
DWC2_HPRT0_PRTCONNDET | DWC2_HPRT0_PRTENCHNG |
DWC2_HPRT0_PRTRST);
}
-#ifndef CONFIG_DM_USB
+#if !CONFIG_IS_ENABLED(DM_USB)
int submit_control_msg(struct usb_device *dev, unsigned long pipe, void *buffer,
int len, struct devrequest *setup)
{
if (board_usb_init(index, USB_INIT_HOST))
return -1;
- return dwc2_init_common(priv);
+ return dwc2_init_common(NULL, priv);
}
int usb_lowlevel_stop(int index)
}
#endif
-#ifdef CONFIG_DM_USB
+#if CONFIG_IS_ENABLED(DM_USB)
static int dwc2_submit_control_msg(struct udevice *dev, struct usb_device *udev,
unsigned long pipe, void *buffer, int length,
struct devrequest *setup)
static int dwc2_usb_ofdata_to_platdata(struct udevice *dev)
{
struct dwc2_priv *priv = dev_get_priv(dev);
- const void *prop;
fdt_addr_t addr;
- addr = dev_get_addr(dev);
+ addr = dev_read_addr(dev);
if (addr == FDT_ADDR_T_NONE)
return -EINVAL;
priv->regs = (struct dwc2_core_regs *)addr;
- prop = fdt_getprop(gd->fdt_blob, dev_of_offset(dev),
- "disable-over-current", NULL);
- if (prop)
- priv->oc_disable = true;
+ priv->oc_disable = dev_read_bool(dev, "disable-over-current");
+ priv->hnp_srp_disable = dev_read_bool(dev, "hnp-srp-disable");
return 0;
}
bus_priv->desc_before_addr = true;
- return dwc2_init_common(priv);
+ return dwc2_init_common(dev, priv);
}
static int dwc2_usb_remove(struct udevice *dev)
{
struct dwc2_priv *priv = dev_get_priv(dev);
+ int ret;
+
+ ret = dwc_vbus_supply_exit(dev);
+ if (ret)
+ return ret;
dwc2_uninit_common(priv->regs);
+ reset_release_bulk(&priv->resets);
+
return 0;
}
static const struct udevice_id dwc2_usb_ids[] = {
{ .compatible = "brcm,bcm2835-usb" },
+ { .compatible = "brcm,bcm2708-usb" },
{ .compatible = "snps,dwc2" },
{ }
};