|  | // SPDX-License-Identifier: GPL-2.0+ | 
|  | /* | 
|  | * STiH407 family DWC3 specific Glue layer | 
|  | * | 
|  | * Copyright (C) 2017, STMicroelectronics - All Rights Reserved | 
|  | * Author(s): Patrice Chotard, <patrice.chotard@st.com> for STMicroelectronics. | 
|  | */ | 
|  |  | 
|  | #include <common.h> | 
|  | #include <asm/io.h> | 
|  | #include <dm.h> | 
|  | #include <errno.h> | 
|  | #include <fdtdec.h> | 
|  | #include <linux/libfdt.h> | 
|  | #include <dm/lists.h> | 
|  | #include <regmap.h> | 
|  | #include <reset-uclass.h> | 
|  | #include <syscon.h> | 
|  | #include <usb.h> | 
|  |  | 
|  | #include <linux/usb/dwc3.h> | 
|  | #include <linux/usb/otg.h> | 
|  | #include <dwc3-sti-glue.h> | 
|  |  | 
|  | DECLARE_GLOBAL_DATA_PTR; | 
|  |  | 
|  | /* | 
|  | * struct sti_dwc3_glue_platdata - dwc3 STi glue driver private structure | 
|  | * @syscfg_base:	addr for the glue syscfg | 
|  | * @glue_base:		addr for the glue registers | 
|  | * @syscfg_offset:	usb syscfg control offset | 
|  | * @powerdown_ctl:	rest controller for powerdown signal | 
|  | * @softreset_ctl:	reset controller for softreset signal | 
|  | * @mode:		drd static host/device config | 
|  | */ | 
|  | struct sti_dwc3_glue_platdata { | 
|  | phys_addr_t syscfg_base; | 
|  | phys_addr_t glue_base; | 
|  | phys_addr_t syscfg_offset; | 
|  | struct reset_ctl powerdown_ctl; | 
|  | struct reset_ctl softreset_ctl; | 
|  | enum usb_dr_mode mode; | 
|  | }; | 
|  |  | 
|  | static int sti_dwc3_glue_drd_init(struct sti_dwc3_glue_platdata *plat) | 
|  | { | 
|  | unsigned long val; | 
|  |  | 
|  | val = readl(plat->syscfg_base + plat->syscfg_offset); | 
|  |  | 
|  | val &= USB3_CONTROL_MASK; | 
|  |  | 
|  | switch (plat->mode) { | 
|  | case USB_DR_MODE_PERIPHERAL: | 
|  | val &= ~(USB3_DELAY_VBUSVALID | 
|  | | USB3_SEL_FORCE_OPMODE | USB3_FORCE_OPMODE(0x3) | 
|  | | USB3_SEL_FORCE_DPPULLDOWN2 | USB3_FORCE_DPPULLDOWN2 | 
|  | | USB3_SEL_FORCE_DMPULLDOWN2 | USB3_FORCE_DMPULLDOWN2); | 
|  |  | 
|  | val |= USB3_DEVICE_NOT_HOST | USB3_FORCE_VBUSVALID; | 
|  | break; | 
|  |  | 
|  | case USB_DR_MODE_HOST: | 
|  | val &= ~(USB3_DEVICE_NOT_HOST | USB3_FORCE_VBUSVALID | 
|  | | USB3_SEL_FORCE_OPMODE	| USB3_FORCE_OPMODE(0x3) | 
|  | | USB3_SEL_FORCE_DPPULLDOWN2 | USB3_FORCE_DPPULLDOWN2 | 
|  | | USB3_SEL_FORCE_DMPULLDOWN2 | USB3_FORCE_DMPULLDOWN2); | 
|  |  | 
|  | val |= USB3_DELAY_VBUSVALID; | 
|  | break; | 
|  |  | 
|  | default: | 
|  | pr_err("Unsupported mode of operation %d\n", plat->mode); | 
|  | return -EINVAL; | 
|  | } | 
|  | writel(val, plat->syscfg_base + plat->syscfg_offset); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void sti_dwc3_glue_init(struct sti_dwc3_glue_platdata *plat) | 
|  | { | 
|  | unsigned long reg; | 
|  |  | 
|  | reg = readl(plat->glue_base + CLKRST_CTRL); | 
|  |  | 
|  | reg |= AUX_CLK_EN | EXT_CFG_RESET_N | XHCI_REVISION; | 
|  | reg &= ~SW_PIPEW_RESET_N; | 
|  |  | 
|  | writel(reg, plat->glue_base + CLKRST_CTRL); | 
|  |  | 
|  | /* configure mux for vbus, powerpresent and bvalid signals */ | 
|  | reg = readl(plat->glue_base + USB2_VBUS_MNGMNT_SEL1); | 
|  |  | 
|  | reg |= SEL_OVERRIDE_VBUSVALID(USB2_VBUS_UTMIOTG) | | 
|  | SEL_OVERRIDE_POWERPRESENT(USB2_VBUS_UTMIOTG) | | 
|  | SEL_OVERRIDE_BVALID(USB2_VBUS_UTMIOTG); | 
|  |  | 
|  | writel(reg, plat->glue_base + USB2_VBUS_MNGMNT_SEL1); | 
|  |  | 
|  | setbits_le32(plat->glue_base + CLKRST_CTRL, SW_PIPEW_RESET_N); | 
|  | } | 
|  |  | 
|  | static int sti_dwc3_glue_ofdata_to_platdata(struct udevice *dev) | 
|  | { | 
|  | struct sti_dwc3_glue_platdata *plat = dev_get_platdata(dev); | 
|  | struct udevice *syscon; | 
|  | struct regmap *regmap; | 
|  | int ret; | 
|  | u32 reg[4]; | 
|  |  | 
|  | ret = fdtdec_get_int_array(gd->fdt_blob, dev_of_offset(dev), | 
|  | "reg", reg, ARRAY_SIZE(reg)); | 
|  | if (ret) { | 
|  | pr_err("unable to find st,stih407-dwc3 reg property(%d)\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | plat->glue_base = reg[0]; | 
|  | plat->syscfg_offset = reg[2]; | 
|  |  | 
|  | /* get corresponding syscon phandle */ | 
|  | ret = uclass_get_device_by_phandle(UCLASS_SYSCON, dev, "st,syscfg", | 
|  | &syscon); | 
|  | if (ret) { | 
|  | pr_err("unable to find syscon device (%d)\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* get syscfg-reg base address */ | 
|  | regmap = syscon_get_regmap(syscon); | 
|  | if (!regmap) { | 
|  | pr_err("unable to find regmap\n"); | 
|  | return -ENODEV; | 
|  | } | 
|  | plat->syscfg_base = regmap->ranges[0].start; | 
|  |  | 
|  | /* get powerdown reset */ | 
|  | ret = reset_get_by_name(dev, "powerdown", &plat->powerdown_ctl); | 
|  | if (ret) { | 
|  | pr_err("can't get powerdown reset for %s (%d)", dev->name, ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* get softreset reset */ | 
|  | ret = reset_get_by_name(dev, "softreset", &plat->softreset_ctl); | 
|  | if (ret) | 
|  | pr_err("can't get soft reset for %s (%d)", dev->name, ret); | 
|  |  | 
|  | return ret; | 
|  | }; | 
|  |  | 
|  | static int sti_dwc3_glue_bind(struct udevice *dev) | 
|  | { | 
|  | struct sti_dwc3_glue_platdata *plat = dev_get_platdata(dev); | 
|  | int dwc3_node; | 
|  |  | 
|  | /* check if one subnode is present */ | 
|  | dwc3_node = fdt_first_subnode(gd->fdt_blob, dev_of_offset(dev)); | 
|  | if (dwc3_node <= 0) { | 
|  | pr_err("Can't find subnode for %s\n", dev->name); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | /* check if the subnode compatible string is the dwc3 one*/ | 
|  | if (fdt_node_check_compatible(gd->fdt_blob, dwc3_node, | 
|  | "snps,dwc3") != 0) { | 
|  | pr_err("Can't find dwc3 subnode for %s\n", dev->name); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | /* retrieve the DWC3 dual role mode */ | 
|  | plat->mode = usb_get_dr_mode(dwc3_node); | 
|  | if (plat->mode == USB_DR_MODE_UNKNOWN) | 
|  | /* by default set dual role mode to HOST */ | 
|  | plat->mode = USB_DR_MODE_HOST; | 
|  |  | 
|  | return dm_scan_fdt_dev(dev); | 
|  | } | 
|  |  | 
|  | static int sti_dwc3_glue_probe(struct udevice *dev) | 
|  | { | 
|  | struct sti_dwc3_glue_platdata *plat = dev_get_platdata(dev); | 
|  | int ret; | 
|  |  | 
|  | /* deassert both powerdown and softreset */ | 
|  | ret = reset_deassert(&plat->powerdown_ctl); | 
|  | if (ret < 0) { | 
|  | pr_err("DWC3 powerdown reset deassert failed: %d", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | ret = reset_deassert(&plat->softreset_ctl); | 
|  | if (ret < 0) { | 
|  | pr_err("DWC3 soft reset deassert failed: %d", ret); | 
|  | goto softreset_err; | 
|  | } | 
|  |  | 
|  | ret = sti_dwc3_glue_drd_init(plat); | 
|  | if (ret) | 
|  | goto init_err; | 
|  |  | 
|  | sti_dwc3_glue_init(plat); | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | init_err: | 
|  | ret = reset_assert(&plat->softreset_ctl); | 
|  | if (ret < 0) { | 
|  | pr_err("DWC3 soft reset deassert failed: %d", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | softreset_err: | 
|  | ret = reset_assert(&plat->powerdown_ctl); | 
|  | if (ret < 0) | 
|  | pr_err("DWC3 powerdown reset deassert failed: %d", ret); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int sti_dwc3_glue_remove(struct udevice *dev) | 
|  | { | 
|  | struct sti_dwc3_glue_platdata *plat = dev_get_platdata(dev); | 
|  | int ret; | 
|  |  | 
|  | /* assert both powerdown and softreset */ | 
|  | ret = reset_assert(&plat->powerdown_ctl); | 
|  | if (ret < 0) { | 
|  | pr_err("DWC3 powerdown reset deassert failed: %d", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | ret = reset_assert(&plat->softreset_ctl); | 
|  | if (ret < 0) | 
|  | pr_err("DWC3 soft reset deassert failed: %d", ret); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static const struct udevice_id sti_dwc3_glue_ids[] = { | 
|  | { .compatible = "st,stih407-dwc3" }, | 
|  | { } | 
|  | }; | 
|  |  | 
|  | U_BOOT_DRIVER(dwc3_sti_glue) = { | 
|  | .name = "dwc3_sti_glue", | 
|  | .id = UCLASS_MISC, | 
|  | .of_match = sti_dwc3_glue_ids, | 
|  | .ofdata_to_platdata = sti_dwc3_glue_ofdata_to_platdata, | 
|  | .probe = sti_dwc3_glue_probe, | 
|  | .remove = sti_dwc3_glue_remove, | 
|  | .bind = sti_dwc3_glue_bind, | 
|  | .platdata_auto_alloc_size = sizeof(struct sti_dwc3_glue_platdata), | 
|  | .flags = DM_FLAG_ALLOC_PRIV_DMA, | 
|  | }; |