Home | History | Annotate | Download | only in host
      1 // SPDX-License-Identifier: GPL-2.0+
      2 /*
      3  * STiH407 family DWC3 specific Glue layer
      4  *
      5  * Copyright (C) 2017, STMicroelectronics - All Rights Reserved
      6  * Author(s): Patrice Chotard, <patrice.chotard (at) st.com> for STMicroelectronics.
      7  */
      8 
      9 #include <common.h>
     10 #include <asm/io.h>
     11 #include <dm.h>
     12 #include <errno.h>
     13 #include <fdtdec.h>
     14 #include <linux/libfdt.h>
     15 #include <dm/lists.h>
     16 #include <regmap.h>
     17 #include <reset-uclass.h>
     18 #include <syscon.h>
     19 #include <usb.h>
     20 
     21 #include <linux/usb/dwc3.h>
     22 #include <linux/usb/otg.h>
     23 #include <dwc3-sti-glue.h>
     24 
     25 DECLARE_GLOBAL_DATA_PTR;
     26 
     27 /*
     28  * struct sti_dwc3_glue_platdata - dwc3 STi glue driver private structure
     29  * @syscfg_base:	addr for the glue syscfg
     30  * @glue_base:		addr for the glue registers
     31  * @syscfg_offset:	usb syscfg control offset
     32  * @powerdown_ctl:	rest controller for powerdown signal
     33  * @softreset_ctl:	reset controller for softreset signal
     34  * @mode:		drd static host/device config
     35  */
     36 struct sti_dwc3_glue_platdata {
     37 	phys_addr_t syscfg_base;
     38 	phys_addr_t glue_base;
     39 	phys_addr_t syscfg_offset;
     40 	struct reset_ctl powerdown_ctl;
     41 	struct reset_ctl softreset_ctl;
     42 	enum usb_dr_mode mode;
     43 };
     44 
     45 static int sti_dwc3_glue_drd_init(struct sti_dwc3_glue_platdata *plat)
     46 {
     47 	unsigned long val;
     48 
     49 	val = readl(plat->syscfg_base + plat->syscfg_offset);
     50 
     51 	val &= USB3_CONTROL_MASK;
     52 
     53 	switch (plat->mode) {
     54 	case USB_DR_MODE_PERIPHERAL:
     55 		val &= ~(USB3_DELAY_VBUSVALID
     56 			| USB3_SEL_FORCE_OPMODE | USB3_FORCE_OPMODE(0x3)
     57 			| USB3_SEL_FORCE_DPPULLDOWN2 | USB3_FORCE_DPPULLDOWN2
     58 			| USB3_SEL_FORCE_DMPULLDOWN2 | USB3_FORCE_DMPULLDOWN2);
     59 
     60 		val |= USB3_DEVICE_NOT_HOST | USB3_FORCE_VBUSVALID;
     61 		break;
     62 
     63 	case USB_DR_MODE_HOST:
     64 		val &= ~(USB3_DEVICE_NOT_HOST | USB3_FORCE_VBUSVALID
     65 			| USB3_SEL_FORCE_OPMODE	| USB3_FORCE_OPMODE(0x3)
     66 			| USB3_SEL_FORCE_DPPULLDOWN2 | USB3_FORCE_DPPULLDOWN2
     67 			| USB3_SEL_FORCE_DMPULLDOWN2 | USB3_FORCE_DMPULLDOWN2);
     68 
     69 		val |= USB3_DELAY_VBUSVALID;
     70 		break;
     71 
     72 	default:
     73 		pr_err("Unsupported mode of operation %d\n", plat->mode);
     74 		return -EINVAL;
     75 	}
     76 	writel(val, plat->syscfg_base + plat->syscfg_offset);
     77 
     78 	return 0;
     79 }
     80 
     81 static void sti_dwc3_glue_init(struct sti_dwc3_glue_platdata *plat)
     82 {
     83 	unsigned long reg;
     84 
     85 	reg = readl(plat->glue_base + CLKRST_CTRL);
     86 
     87 	reg |= AUX_CLK_EN | EXT_CFG_RESET_N | XHCI_REVISION;
     88 	reg &= ~SW_PIPEW_RESET_N;
     89 
     90 	writel(reg, plat->glue_base + CLKRST_CTRL);
     91 
     92 	/* configure mux for vbus, powerpresent and bvalid signals */
     93 	reg = readl(plat->glue_base + USB2_VBUS_MNGMNT_SEL1);
     94 
     95 	reg |= SEL_OVERRIDE_VBUSVALID(USB2_VBUS_UTMIOTG) |
     96 	       SEL_OVERRIDE_POWERPRESENT(USB2_VBUS_UTMIOTG) |
     97 	       SEL_OVERRIDE_BVALID(USB2_VBUS_UTMIOTG);
     98 
     99 	writel(reg, plat->glue_base + USB2_VBUS_MNGMNT_SEL1);
    100 
    101 	setbits_le32(plat->glue_base + CLKRST_CTRL, SW_PIPEW_RESET_N);
    102 }
    103 
    104 static int sti_dwc3_glue_ofdata_to_platdata(struct udevice *dev)
    105 {
    106 	struct sti_dwc3_glue_platdata *plat = dev_get_platdata(dev);
    107 	struct udevice *syscon;
    108 	struct regmap *regmap;
    109 	int ret;
    110 	u32 reg[4];
    111 
    112 	ret = fdtdec_get_int_array(gd->fdt_blob, dev_of_offset(dev),
    113 				   "reg", reg, ARRAY_SIZE(reg));
    114 	if (ret) {
    115 		pr_err("unable to find st,stih407-dwc3 reg property(%d)\n", ret);
    116 		return ret;
    117 	}
    118 
    119 	plat->glue_base = reg[0];
    120 	plat->syscfg_offset = reg[2];
    121 
    122 	/* get corresponding syscon phandle */
    123 	ret = uclass_get_device_by_phandle(UCLASS_SYSCON, dev, "st,syscfg",
    124 					   &syscon);
    125 	if (ret) {
    126 		pr_err("unable to find syscon device (%d)\n", ret);
    127 		return ret;
    128 	}
    129 
    130 	/* get syscfg-reg base address */
    131 	regmap = syscon_get_regmap(syscon);
    132 	if (!regmap) {
    133 		pr_err("unable to find regmap\n");
    134 		return -ENODEV;
    135 	}
    136 	plat->syscfg_base = regmap->ranges[0].start;
    137 
    138 	/* get powerdown reset */
    139 	ret = reset_get_by_name(dev, "powerdown", &plat->powerdown_ctl);
    140 	if (ret) {
    141 		pr_err("can't get powerdown reset for %s (%d)", dev->name, ret);
    142 		return ret;
    143 	}
    144 
    145 	/* get softreset reset */
    146 	ret = reset_get_by_name(dev, "softreset", &plat->softreset_ctl);
    147 	if (ret)
    148 		pr_err("can't get soft reset for %s (%d)", dev->name, ret);
    149 
    150 	return ret;
    151 };
    152 
    153 static int sti_dwc3_glue_bind(struct udevice *dev)
    154 {
    155 	struct sti_dwc3_glue_platdata *plat = dev_get_platdata(dev);
    156 	int dwc3_node;
    157 
    158 	/* check if one subnode is present */
    159 	dwc3_node = fdt_first_subnode(gd->fdt_blob, dev_of_offset(dev));
    160 	if (dwc3_node <= 0) {
    161 		pr_err("Can't find subnode for %s\n", dev->name);
    162 		return -ENODEV;
    163 	}
    164 
    165 	/* check if the subnode compatible string is the dwc3 one*/
    166 	if (fdt_node_check_compatible(gd->fdt_blob, dwc3_node,
    167 				      "snps,dwc3") != 0) {
    168 		pr_err("Can't find dwc3 subnode for %s\n", dev->name);
    169 		return -ENODEV;
    170 	}
    171 
    172 	/* retrieve the DWC3 dual role mode */
    173 	plat->mode = usb_get_dr_mode(dwc3_node);
    174 	if (plat->mode == USB_DR_MODE_UNKNOWN)
    175 		/* by default set dual role mode to HOST */
    176 		plat->mode = USB_DR_MODE_HOST;
    177 
    178 	return dm_scan_fdt_dev(dev);
    179 }
    180 
    181 static int sti_dwc3_glue_probe(struct udevice *dev)
    182 {
    183 	struct sti_dwc3_glue_platdata *plat = dev_get_platdata(dev);
    184 	int ret;
    185 
    186 	/* deassert both powerdown and softreset */
    187 	ret = reset_deassert(&plat->powerdown_ctl);
    188 	if (ret < 0) {
    189 		pr_err("DWC3 powerdown reset deassert failed: %d", ret);
    190 		return ret;
    191 	}
    192 
    193 	ret = reset_deassert(&plat->softreset_ctl);
    194 	if (ret < 0) {
    195 		pr_err("DWC3 soft reset deassert failed: %d", ret);
    196 		goto softreset_err;
    197 	}
    198 
    199 	ret = sti_dwc3_glue_drd_init(plat);
    200 	if (ret)
    201 		goto init_err;
    202 
    203 	sti_dwc3_glue_init(plat);
    204 
    205 	return 0;
    206 
    207 init_err:
    208 	ret = reset_assert(&plat->softreset_ctl);
    209 	if (ret < 0) {
    210 		pr_err("DWC3 soft reset deassert failed: %d", ret);
    211 		return ret;
    212 	}
    213 
    214 softreset_err:
    215 	ret = reset_assert(&plat->powerdown_ctl);
    216 	if (ret < 0)
    217 		pr_err("DWC3 powerdown reset deassert failed: %d", ret);
    218 
    219 	return ret;
    220 }
    221 
    222 static int sti_dwc3_glue_remove(struct udevice *dev)
    223 {
    224 	struct sti_dwc3_glue_platdata *plat = dev_get_platdata(dev);
    225 	int ret;
    226 
    227 	/* assert both powerdown and softreset */
    228 	ret = reset_assert(&plat->powerdown_ctl);
    229 	if (ret < 0) {
    230 		pr_err("DWC3 powerdown reset deassert failed: %d", ret);
    231 		return ret;
    232 	}
    233 
    234 	ret = reset_assert(&plat->softreset_ctl);
    235 	if (ret < 0)
    236 		pr_err("DWC3 soft reset deassert failed: %d", ret);
    237 
    238 	return ret;
    239 }
    240 
    241 static const struct udevice_id sti_dwc3_glue_ids[] = {
    242 	{ .compatible = "st,stih407-dwc3" },
    243 	{ }
    244 };
    245 
    246 U_BOOT_DRIVER(dwc3_sti_glue) = {
    247 	.name = "dwc3_sti_glue",
    248 	.id = UCLASS_MISC,
    249 	.of_match = sti_dwc3_glue_ids,
    250 	.ofdata_to_platdata = sti_dwc3_glue_ofdata_to_platdata,
    251 	.probe = sti_dwc3_glue_probe,
    252 	.remove = sti_dwc3_glue_remove,
    253 	.bind = sti_dwc3_glue_bind,
    254 	.platdata_auto_alloc_size = sizeof(struct sti_dwc3_glue_platdata),
    255 	.flags = DM_FLAG_ALLOC_PRIV_DMA,
    256 };
    257