// SPDX-License-Identifier: GPL-2.0 /* * dwc3-rtk.c - Realtek DWC3 Specific Glue layer * * Copyright (C) 2023 Realtek Semiconductor Corporation * */ #include #include #include #include #include #include #include #include #include #include #include "core.h" #define WRAP_CTR_REG 0x0 #define DISABLE_MULTI_REQ BIT(1) #define DESC_R2W_MULTI_DISABLE BIT(9) #define FORCE_PIPE3_PHY_STATUS_TO_0 BIT(13) #define WRAP_USB2_PHY_UTMI_REG 0x8 #define TXHSVM_EN BIT(3) #define WRAP_PHY_PIPE_REG 0xC #define RESET_DISABLE_PIPE3_P0 BIT(0) #define CLOCK_ENABLE_FOR_PIPE3_PCLK BIT(1) #define WRAP_USB_HMAC_CTR0_REG 0x60 #define U3PORT_DIS BIT(8) #define WRAP_USB2_PHY_REG 0x70 #define USB2_PHY_EN_PHY_PLL_PORT0 BIT(12) #define USB2_PHY_EN_PHY_PLL_PORT1 BIT(13) #define USB2_PHY_SWITCH_MASK 0x707 #define USB2_PHY_SWITCH_DEVICE 0x0 #define USB2_PHY_SWITCH_HOST 0x606 #define WRAP_APHY_REG 0x128 #define USB3_MBIAS_ENABLE BIT(1) /* pm control */ #define WRAP_USB_DBUS_PWR_CTRL_REG 0x160 #define USB_DBUS_PWR_CTRL_REG 0x0 #define DBUS_PWR_CTRL_EN BIT(0) struct dwc3_rtk { struct device *dev; void __iomem *regs; size_t regs_size; void __iomem *pm_base; struct dwc3 *dwc; enum usb_role cur_role; struct usb_role_switch *role_switch; }; static void switch_usb2_role(struct dwc3_rtk *rtk, enum usb_role role) { void __iomem *reg; int val; reg = rtk->regs + WRAP_USB2_PHY_REG; val = ~USB2_PHY_SWITCH_MASK & readl(reg); switch (role) { case USB_ROLE_DEVICE: writel(USB2_PHY_SWITCH_DEVICE | val, reg); break; case USB_ROLE_HOST: writel(USB2_PHY_SWITCH_HOST | val, reg); break; default: dev_dbg(rtk->dev, "%s: role=%d\n", __func__, role); break; } } static void switch_dwc3_role(struct dwc3_rtk *rtk, enum usb_role role) { if (!rtk->dwc->role_sw) return; usb_role_switch_set_role(rtk->dwc->role_sw, role); } static enum usb_role dwc3_rtk_get_role(struct dwc3_rtk *rtk) { enum usb_role role; role = rtk->cur_role; if (rtk->dwc && rtk->dwc->role_sw) role = usb_role_switch_get_role(rtk->dwc->role_sw); else dev_dbg(rtk->dev, "%s not usb_role_switch role=%d\n", __func__, role); return role; } static void dwc3_rtk_set_role(struct dwc3_rtk *rtk, enum usb_role role) { rtk->cur_role = role; switch_dwc3_role(rtk, role); mdelay(10); switch_usb2_role(rtk, role); } #if IS_ENABLED(CONFIG_USB_ROLE_SWITCH) static int dwc3_usb_role_switch_set(struct usb_role_switch *sw, enum usb_role role) { struct dwc3_rtk *rtk = usb_role_switch_get_drvdata(sw); dwc3_rtk_set_role(rtk, role); return 0; } static enum usb_role dwc3_usb_role_switch_get(struct usb_role_switch *sw) { struct dwc3_rtk *rtk = usb_role_switch_get_drvdata(sw); return dwc3_rtk_get_role(rtk); } static int dwc3_rtk_setup_role_switch(struct dwc3_rtk *rtk) { struct usb_role_switch_desc dwc3_role_switch = {NULL}; dwc3_role_switch.name = dev_name(rtk->dev); dwc3_role_switch.driver_data = rtk; dwc3_role_switch.allow_userspace_control = true; dwc3_role_switch.fwnode = dev_fwnode(rtk->dev); dwc3_role_switch.set = dwc3_usb_role_switch_set; dwc3_role_switch.get = dwc3_usb_role_switch_get; rtk->role_switch = usb_role_switch_register(rtk->dev, &dwc3_role_switch); if (IS_ERR(rtk->role_switch)) return PTR_ERR(rtk->role_switch); return 0; } static int dwc3_rtk_remove_role_switch(struct dwc3_rtk *rtk) { if (rtk->role_switch) usb_role_switch_unregister(rtk->role_switch); rtk->role_switch = NULL; return 0; } #else #define dwc3_rtk_setup_role_switch(x) 0 #define dwc3_rtk_remove_role_switch(x) 0 #endif static const char *const speed_names[] = { [USB_SPEED_UNKNOWN] = "UNKNOWN", [USB_SPEED_LOW] = "low-speed", [USB_SPEED_FULL] = "full-speed", [USB_SPEED_HIGH] = "high-speed", [USB_SPEED_WIRELESS] = "wireless", [USB_SPEED_SUPER] = "super-speed", [USB_SPEED_SUPER_PLUS] = "super-speed-plus", }; static enum usb_device_speed __get_dwc3_maximum_speed(struct device_node *np) { struct device_node *dwc3_np; const char *maximum_speed; int ret; dwc3_np = of_get_compatible_child(np, "snps,dwc3"); if (!dwc3_np) return USB_SPEED_UNKNOWN; ret = of_property_read_string(dwc3_np, "maximum-speed", &maximum_speed); if (ret < 0) goto out; ret = match_string(speed_names, ARRAY_SIZE(speed_names), maximum_speed); out: of_node_put(dwc3_np); return (ret < 0) ? USB_SPEED_UNKNOWN : ret; } static int dwc3_rtk_init(struct dwc3_rtk *rtk) { struct device *dev = rtk->dev; void __iomem *reg; int val; enum usb_device_speed maximum_speed; const struct soc_device_attribute rtk_soc_kylin_a00[] = { { .family = "Realtek Kylin", .revision = "A00", }, { /* empty */ } }; const struct soc_device_attribute rtk_soc_hercules[] = { { .family = "Realtek Hercules", }, { /* empty */ } }; const struct soc_device_attribute rtk_soc_thor[] = { { .family = "Realtek Thor", }, { /* empty */ } }; if (soc_device_match(rtk_soc_kylin_a00)) { reg = rtk->regs + WRAP_CTR_REG; val = readl(reg); writel(DISABLE_MULTI_REQ | val, reg); dev_info(dev, "[bug fixed] 1295/1296 A00: add workaround to disable multiple request for D-Bus"); } if (soc_device_match(rtk_soc_hercules)) { reg = rtk->regs + WRAP_USB2_PHY_REG; val = readl(reg); writel(USB2_PHY_EN_PHY_PLL_PORT1 | val, reg); dev_info(dev, "[bug fixed] 1395 add workaround to disable usb2 port 2 suspend!"); } reg = rtk->regs + WRAP_USB2_PHY_UTMI_REG; val = readl(reg); writel(TXHSVM_EN | val, reg); maximum_speed = __get_dwc3_maximum_speed(dev->of_node); if (maximum_speed != USB_SPEED_UNKNOWN && maximum_speed <= USB_SPEED_HIGH) { if (soc_device_match(rtk_soc_thor)) { reg = rtk->regs + WRAP_USB_HMAC_CTR0_REG; val = readl(reg); writel(U3PORT_DIS | val, reg); } else { reg = rtk->regs + WRAP_CTR_REG; val = readl(reg); writel(FORCE_PIPE3_PHY_STATUS_TO_0 | val, reg); reg = rtk->regs + WRAP_PHY_PIPE_REG; val = ~CLOCK_ENABLE_FOR_PIPE3_PCLK & readl(reg); writel(RESET_DISABLE_PIPE3_P0 | val, reg); reg = rtk->regs + WRAP_USB_HMAC_CTR0_REG; val = readl(reg); writel(U3PORT_DIS | val, reg); reg = rtk->regs + WRAP_APHY_REG; val = readl(reg); writel(~USB3_MBIAS_ENABLE & val, reg); dev_dbg(rtk->dev, "%s: disable usb 3.0 phy\n", __func__); } } reg = rtk->regs + WRAP_CTR_REG; val = readl(reg); writel(DESC_R2W_MULTI_DISABLE | val, reg); /* Set phy Dp/Dm initial state to host mode to avoid the Dp glitch */ reg = rtk->regs + WRAP_USB2_PHY_REG; val = ~USB2_PHY_SWITCH_MASK & readl(reg); writel(USB2_PHY_SWITCH_HOST | val, reg); if (rtk->pm_base) { reg = rtk->pm_base + USB_DBUS_PWR_CTRL_REG; val = DBUS_PWR_CTRL_EN | readl(reg); writel(val, reg); } return 0; } static int dwc3_rtk_probe_dwc3_core(struct dwc3_rtk *rtk) { struct device *dev = rtk->dev; struct device_node *node = dev->of_node; struct platform_device *dwc3_pdev; struct device *dwc3_dev; struct device_node *dwc3_node; enum usb_dr_mode dr_mode; int ret = 0; ret = dwc3_rtk_init(rtk); if (ret) return -EINVAL; ret = of_platform_populate(node, NULL, NULL, dev); if (ret) { dev_err(dev, "failed to add dwc3 core\n"); return ret; } dwc3_node = of_get_compatible_child(node, "snps,dwc3"); if (!dwc3_node) { dev_err(dev, "failed to find dwc3 core node\n"); ret = -ENODEV; goto depopulate; } dwc3_pdev = of_find_device_by_node(dwc3_node); if (!dwc3_pdev) { dev_err(dev, "failed to find dwc3 core platform_device\n"); ret = -ENODEV; goto err_node_put; } dwc3_dev = &dwc3_pdev->dev; rtk->dwc = platform_get_drvdata(dwc3_pdev); if (!rtk->dwc) { dev_err(dev, "failed to find dwc3 core\n"); ret = -ENODEV; goto err_pdev_put; } dr_mode = usb_get_dr_mode(dwc3_dev); if (dr_mode != rtk->dwc->dr_mode) { dev_info(dev, "dts set dr_mode=%d, but dwc3 set dr_mode=%d\n", dr_mode, rtk->dwc->dr_mode); dr_mode = rtk->dwc->dr_mode; } switch (dr_mode) { case USB_DR_MODE_PERIPHERAL: rtk->cur_role = USB_ROLE_DEVICE; break; case USB_DR_MODE_HOST: rtk->cur_role = USB_ROLE_HOST; break; default: dev_dbg(rtk->dev, "%s: dr_mode=%d\n", __func__, dr_mode); break; } if (device_property_read_bool(dwc3_dev, "usb-role-switch")) { ret = dwc3_rtk_setup_role_switch(rtk); if (ret) { dev_err(dev, "dwc3_rtk_setup_role_switch fail=%d\n", ret); goto err_pdev_put; } rtk->cur_role = dwc3_rtk_get_role(rtk); } switch_usb2_role(rtk, rtk->cur_role); platform_device_put(dwc3_pdev); of_node_put(dwc3_node); return 0; err_pdev_put: platform_device_put(dwc3_pdev); err_node_put: of_node_put(dwc3_node); depopulate: of_platform_depopulate(dev); return ret; } static int dwc3_rtk_probe(struct platform_device *pdev) { struct dwc3_rtk *rtk; struct device *dev = &pdev->dev; struct resource *res; void __iomem *regs; int ret = 0; rtk = devm_kzalloc(dev, sizeof(*rtk), GFP_KERNEL); if (!rtk) { ret = -ENOMEM; goto out; } platform_set_drvdata(pdev, rtk); rtk->dev = dev; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(dev, "missing memory resource\n"); ret = -ENODEV; goto out; } regs = devm_ioremap_resource(dev, res); if (IS_ERR(regs)) { ret = PTR_ERR(regs); goto out; } rtk->regs = regs; rtk->regs_size = resource_size(res); res = platform_get_resource(pdev, IORESOURCE_MEM, 1); if (res) { rtk->pm_base = devm_ioremap_resource(dev, res); if (IS_ERR(rtk->pm_base)) { ret = PTR_ERR(rtk->pm_base); goto out; } } ret = dwc3_rtk_probe_dwc3_core(rtk); out: return ret; } static void dwc3_rtk_remove(struct platform_device *pdev) { struct dwc3_rtk *rtk = platform_get_drvdata(pdev); rtk->dwc = NULL; dwc3_rtk_remove_role_switch(rtk); of_platform_depopulate(rtk->dev); } static void dwc3_rtk_shutdown(struct platform_device *pdev) { struct dwc3_rtk *rtk = platform_get_drvdata(pdev); of_platform_depopulate(rtk->dev); } static const struct of_device_id rtk_dwc3_match[] = { { .compatible = "realtek,rtd-dwc3" }, {}, }; MODULE_DEVICE_TABLE(of, rtk_dwc3_match); #ifdef CONFIG_PM_SLEEP static int dwc3_rtk_suspend(struct device *dev) { return 0; } static int dwc3_rtk_resume(struct device *dev) { struct dwc3_rtk *rtk = dev_get_drvdata(dev); dwc3_rtk_init(rtk); switch_usb2_role(rtk, rtk->cur_role); /* runtime set active to reflect active state. */ pm_runtime_disable(dev); pm_runtime_set_active(dev); pm_runtime_enable(dev); return 0; } static const struct dev_pm_ops dwc3_rtk_dev_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(dwc3_rtk_suspend, dwc3_rtk_resume) }; #define DEV_PM_OPS (&dwc3_rtk_dev_pm_ops) #else #define DEV_PM_OPS NULL #endif /* CONFIG_PM_SLEEP */ static struct platform_driver dwc3_rtk_driver = { .probe = dwc3_rtk_probe, .remove_new = dwc3_rtk_remove, .driver = { .name = "rtk-dwc3", .of_match_table = rtk_dwc3_match, .pm = DEV_PM_OPS, }, .shutdown = dwc3_rtk_shutdown, }; module_platform_driver(dwc3_rtk_driver); MODULE_AUTHOR("Stanley Chang "); MODULE_DESCRIPTION("DesignWare USB3 Realtek Glue Layer"); MODULE_ALIAS("platform:rtk-dwc3"); MODULE_LICENSE("GPL"); MODULE_SOFTDEP("pre: phy_rtk_usb2 phy_rtk_usb3");