Home | History | Annotate | Download | only in host
      1 // SPDX-License-Identifier: GPL-2.0+
      2 /*
      3  * Qualcomm EHCI driver
      4  *
      5  * (C) Copyright 2015 Mateusz Kulikowski <mateusz.kulikowski (at) gmail.com>
      6  *
      7  * Based on Linux driver
      8  */
      9 
     10 #include <common.h>
     11 #include <dm.h>
     12 #include <errno.h>
     13 #include <fdtdec.h>
     14 #include <linux/libfdt.h>
     15 #include <usb.h>
     16 #include <usb/ehci-ci.h>
     17 #include <usb/ulpi.h>
     18 #include <wait_bit.h>
     19 #include <asm/gpio.h>
     20 #include <asm/io.h>
     21 #include <linux/compat.h>
     22 #include "ehci.h"
     23 
     24 /* PHY viewport regs */
     25 #define ULPI_MISC_A_READ         0x96
     26 #define ULPI_MISC_A_SET          0x97
     27 #define ULPI_MISC_A_CLEAR        0x98
     28 #define ULPI_MISC_A_VBUSVLDEXTSEL    (1 << 1)
     29 #define ULPI_MISC_A_VBUSVLDEXT       (1 << 0)
     30 
     31 #define GEN2_SESS_VLD_CTRL_EN (1 << 7)
     32 
     33 #define SESS_VLD_CTRL         (1 << 25)
     34 
     35 struct msm_ehci_priv {
     36 	struct ehci_ctrl ctrl; /* Needed by EHCI */
     37 	struct usb_ehci *ehci; /* Start of IP core*/
     38 	struct ulpi_viewport ulpi_vp; /* ULPI Viewport */
     39 };
     40 
     41 int __weak board_prepare_usb(enum usb_init_type type)
     42 {
     43 	return 0;
     44 }
     45 
     46 static void setup_usb_phy(struct msm_ehci_priv *priv)
     47 {
     48 	/* Select and enable external configuration with USB PHY */
     49 	ulpi_write(&priv->ulpi_vp, (u8 *)ULPI_MISC_A_SET,
     50 		   ULPI_MISC_A_VBUSVLDEXTSEL | ULPI_MISC_A_VBUSVLDEXT);
     51 }
     52 
     53 static void reset_usb_phy(struct msm_ehci_priv *priv)
     54 {
     55 	/* Disable VBUS mimicing in the controller. */
     56 	ulpi_write(&priv->ulpi_vp, (u8 *)ULPI_MISC_A_CLEAR,
     57 		   ULPI_MISC_A_VBUSVLDEXTSEL | ULPI_MISC_A_VBUSVLDEXT);
     58 }
     59 
     60 
     61 static int msm_init_after_reset(struct ehci_ctrl *dev)
     62 {
     63 	struct msm_ehci_priv *p = container_of(dev, struct msm_ehci_priv, ctrl);
     64 	struct usb_ehci *ehci = p->ehci;
     65 
     66 	/* select ULPI phy */
     67 	writel(PORT_PTS_ULPI, &ehci->portsc);
     68 	setup_usb_phy(p);
     69 
     70 	/* Enable sess_vld */
     71 	setbits_le32(&ehci->genconfig2, GEN2_SESS_VLD_CTRL_EN);
     72 
     73 	/* Enable external vbus configuration in the LINK */
     74 	setbits_le32(&ehci->usbcmd, SESS_VLD_CTRL);
     75 
     76 	/* USB_OTG_HS_AHB_BURST */
     77 	writel(0x0, &ehci->sbuscfg);
     78 
     79 	/* USB_OTG_HS_AHB_MODE: HPROT_MODE */
     80 	/* Bus access related config. */
     81 	writel(0x08, &ehci->sbusmode);
     82 
     83 	/* set mode to host controller */
     84 	writel(CM_HOST, &ehci->usbmode);
     85 
     86 	return 0;
     87 }
     88 
     89 static const struct ehci_ops msm_ehci_ops = {
     90 	.init_after_reset = msm_init_after_reset
     91 };
     92 
     93 static int ehci_usb_probe(struct udevice *dev)
     94 {
     95 	struct msm_ehci_priv *p = dev_get_priv(dev);
     96 	struct usb_ehci *ehci = p->ehci;
     97 	struct ehci_hccr *hccr;
     98 	struct ehci_hcor *hcor;
     99 	int ret;
    100 
    101 	hccr = (struct ehci_hccr *)((phys_addr_t)&ehci->caplength);
    102 	hcor = (struct ehci_hcor *)((phys_addr_t)hccr +
    103 			HC_LENGTH(ehci_readl(&(hccr)->cr_capbase)));
    104 
    105 	ret = board_prepare_usb(USB_INIT_HOST);
    106 	if (ret < 0)
    107 		return ret;
    108 
    109 	return ehci_register(dev, hccr, hcor, &msm_ehci_ops, 0, USB_INIT_HOST);
    110 }
    111 
    112 static int ehci_usb_remove(struct udevice *dev)
    113 {
    114 	struct msm_ehci_priv *p = dev_get_priv(dev);
    115 	struct usb_ehci *ehci = p->ehci;
    116 	int ret;
    117 
    118 	ret = ehci_deregister(dev);
    119 	if (ret)
    120 		return ret;
    121 
    122 	/* Stop controller. */
    123 	clrbits_le32(&ehci->usbcmd, CMD_RUN);
    124 
    125 	reset_usb_phy(p);
    126 
    127 	ret = board_prepare_usb(USB_INIT_DEVICE); /* Board specific hook */
    128 	if (ret < 0)
    129 		return ret;
    130 
    131 	/* Reset controller */
    132 	setbits_le32(&ehci->usbcmd, CMD_RESET);
    133 
    134 	/* Wait for reset */
    135 	if (wait_for_bit_le32(&ehci->usbcmd, CMD_RESET, false, 30, false)) {
    136 		printf("Stuck on USB reset.\n");
    137 		return -ETIMEDOUT;
    138 	}
    139 
    140 	return 0;
    141 }
    142 
    143 static int ehci_usb_ofdata_to_platdata(struct udevice *dev)
    144 {
    145 	struct msm_ehci_priv *priv = dev_get_priv(dev);
    146 
    147 	priv->ulpi_vp.port_num = 0;
    148 	priv->ehci = (void *)devfdt_get_addr(dev);
    149 
    150 	if (priv->ehci == (void *)FDT_ADDR_T_NONE)
    151 		return -EINVAL;
    152 
    153 	/* Warning: this will not work if viewport address is > 64 bit due to
    154 	 * ULPI design.
    155 	 */
    156 	priv->ulpi_vp.viewport_addr = (phys_addr_t)&priv->ehci->ulpi_viewpoint;
    157 
    158 	return 0;
    159 }
    160 
    161 static const struct udevice_id ehci_usb_ids[] = {
    162 	{ .compatible = "qcom,ehci-host", },
    163 	{ }
    164 };
    165 
    166 U_BOOT_DRIVER(usb_ehci) = {
    167 	.name	= "ehci_msm",
    168 	.id	= UCLASS_USB,
    169 	.of_match = ehci_usb_ids,
    170 	.ofdata_to_platdata = ehci_usb_ofdata_to_platdata,
    171 	.probe = ehci_usb_probe,
    172 	.remove = ehci_usb_remove,
    173 	.ops	= &ehci_usb_ops,
    174 	.priv_auto_alloc_size = sizeof(struct msm_ehci_priv),
    175 	.flags	= DM_FLAG_ALLOC_PRIV_DMA,
    176 };
    177