15fd54aceSGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0 2b3f4e727SChunfeng Yun /* 3b3f4e727SChunfeng Yun * mtu3_dr.c - dual role switch and host glue layer 4b3f4e727SChunfeng Yun * 5b3f4e727SChunfeng Yun * Copyright (C) 2016 MediaTek Inc. 6b3f4e727SChunfeng Yun * 7b3f4e727SChunfeng Yun * Author: Chunfeng Yun <chunfeng.yun@mediatek.com> 8b3f4e727SChunfeng Yun */ 9b3f4e727SChunfeng Yun 10b3f4e727SChunfeng Yun #include <linux/clk.h> 11b3f4e727SChunfeng Yun #include <linux/iopoll.h> 12b3f4e727SChunfeng Yun #include <linux/irq.h> 13b3f4e727SChunfeng Yun #include <linux/kernel.h> 14b3f4e727SChunfeng Yun #include <linux/mfd/syscon.h> 15b3f4e727SChunfeng Yun #include <linux/of_device.h> 16b3f4e727SChunfeng Yun #include <linux/regmap.h> 17b3f4e727SChunfeng Yun 18b3f4e727SChunfeng Yun #include "mtu3.h" 19b3f4e727SChunfeng Yun #include "mtu3_dr.h" 20b3f4e727SChunfeng Yun 21f0ede2c6SChunfeng Yun /* mt8173 etc */ 22f0ede2c6SChunfeng Yun #define PERI_WK_CTRL1 0x4 23f0ede2c6SChunfeng Yun #define WC1_IS_C(x) (((x) & 0xf) << 26) /* cycle debounce */ 24f0ede2c6SChunfeng Yun #define WC1_IS_EN BIT(25) 25f0ede2c6SChunfeng Yun #define WC1_IS_P BIT(6) /* polarity for ip sleep */ 26f0ede2c6SChunfeng Yun 27f0ede2c6SChunfeng Yun /* mt2712 etc */ 28f0ede2c6SChunfeng Yun #define PERI_SSUSB_SPM_CTRL 0x0 29f0ede2c6SChunfeng Yun #define SSC_IP_SLEEP_EN BIT(4) 30f0ede2c6SChunfeng Yun #define SSC_SPM_INT_EN BIT(1) 31f0ede2c6SChunfeng Yun 32f0ede2c6SChunfeng Yun enum ssusb_uwk_vers { 33f0ede2c6SChunfeng Yun SSUSB_UWK_V1 = 1, 34f0ede2c6SChunfeng Yun SSUSB_UWK_V2, 35f0ede2c6SChunfeng Yun }; 36b3f4e727SChunfeng Yun 37b3f4e727SChunfeng Yun /* 38b3f4e727SChunfeng Yun * ip-sleep wakeup mode: 39b3f4e727SChunfeng Yun * all clocks can be turn off, but power domain should be kept on 40b3f4e727SChunfeng Yun */ 41f0ede2c6SChunfeng Yun static void ssusb_wakeup_ip_sleep_set(struct ssusb_mtk *ssusb, bool enable) 42b3f4e727SChunfeng Yun { 43f0ede2c6SChunfeng Yun u32 reg, msk, val; 44b3f4e727SChunfeng Yun 45f0ede2c6SChunfeng Yun switch (ssusb->uwk_vers) { 46f0ede2c6SChunfeng Yun case SSUSB_UWK_V1: 47f0ede2c6SChunfeng Yun reg = ssusb->uwk_reg_base + PERI_WK_CTRL1; 48f0ede2c6SChunfeng Yun msk = WC1_IS_EN | WC1_IS_C(0xf) | WC1_IS_P; 49f0ede2c6SChunfeng Yun val = enable ? (WC1_IS_EN | WC1_IS_C(0x8)) : 0; 50f0ede2c6SChunfeng Yun break; 51f0ede2c6SChunfeng Yun case SSUSB_UWK_V2: 52f0ede2c6SChunfeng Yun reg = ssusb->uwk_reg_base + PERI_SSUSB_SPM_CTRL; 53f0ede2c6SChunfeng Yun msk = SSC_IP_SLEEP_EN | SSC_SPM_INT_EN; 54f0ede2c6SChunfeng Yun val = enable ? msk : 0; 55f0ede2c6SChunfeng Yun break; 56f0ede2c6SChunfeng Yun default: 57f0ede2c6SChunfeng Yun return; 58b90c6d10SFengguang Wu } 59f0ede2c6SChunfeng Yun regmap_update_bits(ssusb->uwk, reg, msk, val); 60b3f4e727SChunfeng Yun } 61b3f4e727SChunfeng Yun 62b3f4e727SChunfeng Yun int ssusb_wakeup_of_property_parse(struct ssusb_mtk *ssusb, 63b3f4e727SChunfeng Yun struct device_node *dn) 64b3f4e727SChunfeng Yun { 65f0ede2c6SChunfeng Yun struct of_phandle_args args; 66f0ede2c6SChunfeng Yun int ret; 67b3f4e727SChunfeng Yun 68f0ede2c6SChunfeng Yun /* wakeup function is optional */ 69f0ede2c6SChunfeng Yun ssusb->uwk_en = of_property_read_bool(dn, "wakeup-source"); 70f0ede2c6SChunfeng Yun if (!ssusb->uwk_en) 71b3f4e727SChunfeng Yun return 0; 72b3f4e727SChunfeng Yun 73f0ede2c6SChunfeng Yun ret = of_parse_phandle_with_fixed_args(dn, 74f0ede2c6SChunfeng Yun "mediatek,syscon-wakeup", 2, 0, &args); 75f0ede2c6SChunfeng Yun if (ret) 76f0ede2c6SChunfeng Yun return ret; 77f0ede2c6SChunfeng Yun 78f0ede2c6SChunfeng Yun ssusb->uwk_reg_base = args.args[0]; 79f0ede2c6SChunfeng Yun ssusb->uwk_vers = args.args[1]; 80f0ede2c6SChunfeng Yun ssusb->uwk = syscon_node_to_regmap(args.np); 81f0ede2c6SChunfeng Yun of_node_put(args.np); 82f0ede2c6SChunfeng Yun dev_info(ssusb->dev, "uwk - reg:0x%x, version:%d\n", 83f0ede2c6SChunfeng Yun ssusb->uwk_reg_base, ssusb->uwk_vers); 84f0ede2c6SChunfeng Yun 85f0ede2c6SChunfeng Yun return PTR_ERR_OR_ZERO(ssusb->uwk); 86b3f4e727SChunfeng Yun } 87b3f4e727SChunfeng Yun 88f0ede2c6SChunfeng Yun void ssusb_wakeup_set(struct ssusb_mtk *ssusb, bool enable) 89f0ede2c6SChunfeng Yun { 90f0ede2c6SChunfeng Yun if (ssusb->uwk_en) 91f0ede2c6SChunfeng Yun ssusb_wakeup_ip_sleep_set(ssusb, enable); 92b3f4e727SChunfeng Yun } 93b3f4e727SChunfeng Yun 94b3f4e727SChunfeng Yun static void host_ports_num_get(struct ssusb_mtk *ssusb) 95b3f4e727SChunfeng Yun { 96b3f4e727SChunfeng Yun u32 xhci_cap; 97b3f4e727SChunfeng Yun 98b3f4e727SChunfeng Yun xhci_cap = mtu3_readl(ssusb->ippc_base, U3D_SSUSB_IP_XHCI_CAP); 99b3f4e727SChunfeng Yun ssusb->u2_ports = SSUSB_IP_XHCI_U2_PORT_NUM(xhci_cap); 100b3f4e727SChunfeng Yun ssusb->u3_ports = SSUSB_IP_XHCI_U3_PORT_NUM(xhci_cap); 101b3f4e727SChunfeng Yun 102b3f4e727SChunfeng Yun dev_dbg(ssusb->dev, "host - u2_ports:%d, u3_ports:%d\n", 103b3f4e727SChunfeng Yun ssusb->u2_ports, ssusb->u3_ports); 104b3f4e727SChunfeng Yun } 105b3f4e727SChunfeng Yun 106b3f4e727SChunfeng Yun /* only configure ports will be used later */ 107b3f4e727SChunfeng Yun int ssusb_host_enable(struct ssusb_mtk *ssusb) 108b3f4e727SChunfeng Yun { 109b3f4e727SChunfeng Yun void __iomem *ibase = ssusb->ippc_base; 110b3f4e727SChunfeng Yun int num_u3p = ssusb->u3_ports; 111b3f4e727SChunfeng Yun int num_u2p = ssusb->u2_ports; 112*18106234SColin Ian King int u3_ports_disabled; 113b3f4e727SChunfeng Yun u32 check_clk; 114b3f4e727SChunfeng Yun u32 value; 115b3f4e727SChunfeng Yun int i; 116b3f4e727SChunfeng Yun 117b3f4e727SChunfeng Yun /* power on host ip */ 118b3f4e727SChunfeng Yun mtu3_clrbits(ibase, U3D_SSUSB_IP_PW_CTRL1, SSUSB_IP_HOST_PDN); 119b3f4e727SChunfeng Yun 120076f1a89SChunfeng Yun /* power on and enable u3 ports except skipped ones */ 121*18106234SColin Ian King u3_ports_disabled = 0; 122b3f4e727SChunfeng Yun for (i = 0; i < num_u3p; i++) { 123076f1a89SChunfeng Yun if ((0x1 << i) & ssusb->u3p_dis_msk) { 124*18106234SColin Ian King u3_ports_disabled++; 125076f1a89SChunfeng Yun continue; 126076f1a89SChunfeng Yun } 127076f1a89SChunfeng Yun 128b3f4e727SChunfeng Yun value = mtu3_readl(ibase, SSUSB_U3_CTRL(i)); 129b3f4e727SChunfeng Yun value &= ~(SSUSB_U3_PORT_PDN | SSUSB_U3_PORT_DIS); 130b3f4e727SChunfeng Yun value |= SSUSB_U3_PORT_HOST_SEL; 131b3f4e727SChunfeng Yun mtu3_writel(ibase, SSUSB_U3_CTRL(i), value); 132b3f4e727SChunfeng Yun } 133b3f4e727SChunfeng Yun 134b3f4e727SChunfeng Yun /* power on and enable all u2 ports */ 135b3f4e727SChunfeng Yun for (i = 0; i < num_u2p; i++) { 136b3f4e727SChunfeng Yun value = mtu3_readl(ibase, SSUSB_U2_CTRL(i)); 137b3f4e727SChunfeng Yun value &= ~(SSUSB_U2_PORT_PDN | SSUSB_U2_PORT_DIS); 138b3f4e727SChunfeng Yun value |= SSUSB_U2_PORT_HOST_SEL; 139b3f4e727SChunfeng Yun mtu3_writel(ibase, SSUSB_U2_CTRL(i), value); 140b3f4e727SChunfeng Yun } 141b3f4e727SChunfeng Yun 142b3f4e727SChunfeng Yun check_clk = SSUSB_XHCI_RST_B_STS; 143*18106234SColin Ian King if (num_u3p > u3_ports_disabled) 144b3f4e727SChunfeng Yun check_clk = SSUSB_U3_MAC_RST_B_STS; 145b3f4e727SChunfeng Yun 146b3f4e727SChunfeng Yun return ssusb_check_clocks(ssusb, check_clk); 147b3f4e727SChunfeng Yun } 148b3f4e727SChunfeng Yun 149b3f4e727SChunfeng Yun int ssusb_host_disable(struct ssusb_mtk *ssusb, bool suspend) 150b3f4e727SChunfeng Yun { 151b3f4e727SChunfeng Yun void __iomem *ibase = ssusb->ippc_base; 152b3f4e727SChunfeng Yun int num_u3p = ssusb->u3_ports; 153b3f4e727SChunfeng Yun int num_u2p = ssusb->u2_ports; 154b3f4e727SChunfeng Yun u32 value; 155b3f4e727SChunfeng Yun int ret; 156b3f4e727SChunfeng Yun int i; 157b3f4e727SChunfeng Yun 158076f1a89SChunfeng Yun /* power down and disable u3 ports except skipped ones */ 159b3f4e727SChunfeng Yun for (i = 0; i < num_u3p; i++) { 160076f1a89SChunfeng Yun if ((0x1 << i) & ssusb->u3p_dis_msk) 161076f1a89SChunfeng Yun continue; 162076f1a89SChunfeng Yun 163b3f4e727SChunfeng Yun value = mtu3_readl(ibase, SSUSB_U3_CTRL(i)); 164b3f4e727SChunfeng Yun value |= SSUSB_U3_PORT_PDN; 165b3f4e727SChunfeng Yun value |= suspend ? 0 : SSUSB_U3_PORT_DIS; 166b3f4e727SChunfeng Yun mtu3_writel(ibase, SSUSB_U3_CTRL(i), value); 167b3f4e727SChunfeng Yun } 168b3f4e727SChunfeng Yun 169b3f4e727SChunfeng Yun /* power down and disable all u2 ports */ 170b3f4e727SChunfeng Yun for (i = 0; i < num_u2p; i++) { 171b3f4e727SChunfeng Yun value = mtu3_readl(ibase, SSUSB_U2_CTRL(i)); 172b3f4e727SChunfeng Yun value |= SSUSB_U2_PORT_PDN; 173b3f4e727SChunfeng Yun value |= suspend ? 0 : SSUSB_U2_PORT_DIS; 174b3f4e727SChunfeng Yun mtu3_writel(ibase, SSUSB_U2_CTRL(i), value); 175b3f4e727SChunfeng Yun } 176b3f4e727SChunfeng Yun 177b3f4e727SChunfeng Yun /* power down host ip */ 178b3f4e727SChunfeng Yun mtu3_setbits(ibase, U3D_SSUSB_IP_PW_CTRL1, SSUSB_IP_HOST_PDN); 179b3f4e727SChunfeng Yun 180b3f4e727SChunfeng Yun if (!suspend) 181b3f4e727SChunfeng Yun return 0; 182b3f4e727SChunfeng Yun 183b3f4e727SChunfeng Yun /* wait for host ip to sleep */ 184b3f4e727SChunfeng Yun ret = readl_poll_timeout(ibase + U3D_SSUSB_IP_PW_STS1, value, 185b3f4e727SChunfeng Yun (value & SSUSB_IP_SLEEP_STS), 100, 100000); 186b3f4e727SChunfeng Yun if (ret) 187b3f4e727SChunfeng Yun dev_err(ssusb->dev, "ip sleep failed!!!\n"); 188b3f4e727SChunfeng Yun 189b3f4e727SChunfeng Yun return ret; 190b3f4e727SChunfeng Yun } 191b3f4e727SChunfeng Yun 192b3f4e727SChunfeng Yun static void ssusb_host_setup(struct ssusb_mtk *ssusb) 193b3f4e727SChunfeng Yun { 194c776f2c3SChunfeng Yun struct otg_switch_mtk *otg_sx = &ssusb->otg_switch; 195c776f2c3SChunfeng Yun 196b3f4e727SChunfeng Yun host_ports_num_get(ssusb); 197b3f4e727SChunfeng Yun 198b3f4e727SChunfeng Yun /* 199b3f4e727SChunfeng Yun * power on host and power on/enable all ports 200b3f4e727SChunfeng Yun * if support OTG, gadget driver will switch port0 to device mode 201b3f4e727SChunfeng Yun */ 202b3f4e727SChunfeng Yun ssusb_host_enable(ssusb); 203d0ed062aSChunfeng Yun 204c776f2c3SChunfeng Yun if (otg_sx->manual_drd_enabled) 205c776f2c3SChunfeng Yun ssusb_set_force_mode(ssusb, MTU3_DR_FORCE_HOST); 206c776f2c3SChunfeng Yun 207d0ed062aSChunfeng Yun /* if port0 supports dual-role, works as host mode by default */ 208d0ed062aSChunfeng Yun ssusb_set_vbus(&ssusb->otg_switch, 1); 209b3f4e727SChunfeng Yun } 210b3f4e727SChunfeng Yun 211b3f4e727SChunfeng Yun static void ssusb_host_cleanup(struct ssusb_mtk *ssusb) 212b3f4e727SChunfeng Yun { 213d0ed062aSChunfeng Yun if (ssusb->is_host) 214d0ed062aSChunfeng Yun ssusb_set_vbus(&ssusb->otg_switch, 0); 215d0ed062aSChunfeng Yun 216b3f4e727SChunfeng Yun ssusb_host_disable(ssusb, false); 217b3f4e727SChunfeng Yun } 218b3f4e727SChunfeng Yun 219b3f4e727SChunfeng Yun /* 220b3f4e727SChunfeng Yun * If host supports multiple ports, the VBUSes(5V) of ports except port0 221b3f4e727SChunfeng Yun * which supports OTG are better to be enabled by default in DTS. 222b3f4e727SChunfeng Yun * Because the host driver will keep link with devices attached when system 223b3f4e727SChunfeng Yun * enters suspend mode, so no need to control VBUSes after initialization. 224b3f4e727SChunfeng Yun */ 225b3f4e727SChunfeng Yun int ssusb_host_init(struct ssusb_mtk *ssusb, struct device_node *parent_dn) 226b3f4e727SChunfeng Yun { 227b3f4e727SChunfeng Yun struct device *parent_dev = ssusb->dev; 228b3f4e727SChunfeng Yun int ret; 229b3f4e727SChunfeng Yun 230b3f4e727SChunfeng Yun ssusb_host_setup(ssusb); 231b3f4e727SChunfeng Yun 232b3f4e727SChunfeng Yun ret = of_platform_populate(parent_dn, NULL, NULL, parent_dev); 233b3f4e727SChunfeng Yun if (ret) { 234d9241ff2SRob Herring dev_dbg(parent_dev, "failed to create child devices at %pOF\n", 235d9241ff2SRob Herring parent_dn); 236b3f4e727SChunfeng Yun return ret; 237b3f4e727SChunfeng Yun } 238b3f4e727SChunfeng Yun 239b3f4e727SChunfeng Yun dev_info(parent_dev, "xHCI platform device register success...\n"); 240b3f4e727SChunfeng Yun 241b3f4e727SChunfeng Yun return 0; 242b3f4e727SChunfeng Yun } 243b3f4e727SChunfeng Yun 244b3f4e727SChunfeng Yun void ssusb_host_exit(struct ssusb_mtk *ssusb) 245b3f4e727SChunfeng Yun { 246b3f4e727SChunfeng Yun of_platform_depopulate(ssusb->dev); 247b3f4e727SChunfeng Yun ssusb_host_cleanup(ssusb); 248b3f4e727SChunfeng Yun } 249