18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * mtu3_dr.c - dual role switch and host glue layer 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2016 MediaTek Inc. 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Author: Chunfeng Yun <chunfeng.yun@mediatek.com> 88c2ecf20Sopenharmony_ci */ 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include <linux/clk.h> 118c2ecf20Sopenharmony_ci#include <linux/iopoll.h> 128c2ecf20Sopenharmony_ci#include <linux/irq.h> 138c2ecf20Sopenharmony_ci#include <linux/kernel.h> 148c2ecf20Sopenharmony_ci#include <linux/mfd/syscon.h> 158c2ecf20Sopenharmony_ci#include <linux/of_device.h> 168c2ecf20Sopenharmony_ci#include <linux/regmap.h> 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci#include "mtu3.h" 198c2ecf20Sopenharmony_ci#include "mtu3_dr.h" 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci/* mt8173 etc */ 228c2ecf20Sopenharmony_ci#define PERI_WK_CTRL1 0x4 238c2ecf20Sopenharmony_ci#define WC1_IS_C(x) (((x) & 0xf) << 26) /* cycle debounce */ 248c2ecf20Sopenharmony_ci#define WC1_IS_EN BIT(25) 258c2ecf20Sopenharmony_ci#define WC1_IS_P BIT(6) /* polarity for ip sleep */ 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci/* mt2712 etc */ 288c2ecf20Sopenharmony_ci#define PERI_SSUSB_SPM_CTRL 0x0 298c2ecf20Sopenharmony_ci#define SSC_IP_SLEEP_EN BIT(4) 308c2ecf20Sopenharmony_ci#define SSC_SPM_INT_EN BIT(1) 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_cienum ssusb_uwk_vers { 338c2ecf20Sopenharmony_ci SSUSB_UWK_V1 = 1, 348c2ecf20Sopenharmony_ci SSUSB_UWK_V2, 358c2ecf20Sopenharmony_ci}; 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci/* 388c2ecf20Sopenharmony_ci * ip-sleep wakeup mode: 398c2ecf20Sopenharmony_ci * all clocks can be turn off, but power domain should be kept on 408c2ecf20Sopenharmony_ci */ 418c2ecf20Sopenharmony_cistatic void ssusb_wakeup_ip_sleep_set(struct ssusb_mtk *ssusb, bool enable) 428c2ecf20Sopenharmony_ci{ 438c2ecf20Sopenharmony_ci u32 reg, msk, val; 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci switch (ssusb->uwk_vers) { 468c2ecf20Sopenharmony_ci case SSUSB_UWK_V1: 478c2ecf20Sopenharmony_ci reg = ssusb->uwk_reg_base + PERI_WK_CTRL1; 488c2ecf20Sopenharmony_ci msk = WC1_IS_EN | WC1_IS_C(0xf) | WC1_IS_P; 498c2ecf20Sopenharmony_ci val = enable ? (WC1_IS_EN | WC1_IS_C(0x8)) : 0; 508c2ecf20Sopenharmony_ci break; 518c2ecf20Sopenharmony_ci case SSUSB_UWK_V2: 528c2ecf20Sopenharmony_ci reg = ssusb->uwk_reg_base + PERI_SSUSB_SPM_CTRL; 538c2ecf20Sopenharmony_ci msk = SSC_IP_SLEEP_EN | SSC_SPM_INT_EN; 548c2ecf20Sopenharmony_ci val = enable ? msk : 0; 558c2ecf20Sopenharmony_ci break; 568c2ecf20Sopenharmony_ci default: 578c2ecf20Sopenharmony_ci return; 588c2ecf20Sopenharmony_ci } 598c2ecf20Sopenharmony_ci regmap_update_bits(ssusb->uwk, reg, msk, val); 608c2ecf20Sopenharmony_ci} 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ciint ssusb_wakeup_of_property_parse(struct ssusb_mtk *ssusb, 638c2ecf20Sopenharmony_ci struct device_node *dn) 648c2ecf20Sopenharmony_ci{ 658c2ecf20Sopenharmony_ci struct of_phandle_args args; 668c2ecf20Sopenharmony_ci int ret; 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci /* wakeup function is optional */ 698c2ecf20Sopenharmony_ci ssusb->uwk_en = of_property_read_bool(dn, "wakeup-source"); 708c2ecf20Sopenharmony_ci if (!ssusb->uwk_en) 718c2ecf20Sopenharmony_ci return 0; 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci ret = of_parse_phandle_with_fixed_args(dn, 748c2ecf20Sopenharmony_ci "mediatek,syscon-wakeup", 2, 0, &args); 758c2ecf20Sopenharmony_ci if (ret) 768c2ecf20Sopenharmony_ci return ret; 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci ssusb->uwk_reg_base = args.args[0]; 798c2ecf20Sopenharmony_ci ssusb->uwk_vers = args.args[1]; 808c2ecf20Sopenharmony_ci ssusb->uwk = syscon_node_to_regmap(args.np); 818c2ecf20Sopenharmony_ci of_node_put(args.np); 828c2ecf20Sopenharmony_ci dev_info(ssusb->dev, "uwk - reg:0x%x, version:%d\n", 838c2ecf20Sopenharmony_ci ssusb->uwk_reg_base, ssusb->uwk_vers); 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci return PTR_ERR_OR_ZERO(ssusb->uwk); 868c2ecf20Sopenharmony_ci} 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_civoid ssusb_wakeup_set(struct ssusb_mtk *ssusb, bool enable) 898c2ecf20Sopenharmony_ci{ 908c2ecf20Sopenharmony_ci if (ssusb->uwk_en) 918c2ecf20Sopenharmony_ci ssusb_wakeup_ip_sleep_set(ssusb, enable); 928c2ecf20Sopenharmony_ci} 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_cistatic void host_ports_num_get(struct ssusb_mtk *ssusb) 958c2ecf20Sopenharmony_ci{ 968c2ecf20Sopenharmony_ci u32 xhci_cap; 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_ci xhci_cap = mtu3_readl(ssusb->ippc_base, U3D_SSUSB_IP_XHCI_CAP); 998c2ecf20Sopenharmony_ci ssusb->u2_ports = SSUSB_IP_XHCI_U2_PORT_NUM(xhci_cap); 1008c2ecf20Sopenharmony_ci ssusb->u3_ports = SSUSB_IP_XHCI_U3_PORT_NUM(xhci_cap); 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci dev_dbg(ssusb->dev, "host - u2_ports:%d, u3_ports:%d\n", 1038c2ecf20Sopenharmony_ci ssusb->u2_ports, ssusb->u3_ports); 1048c2ecf20Sopenharmony_ci} 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci/* only configure ports will be used later */ 1078c2ecf20Sopenharmony_ciint ssusb_host_enable(struct ssusb_mtk *ssusb) 1088c2ecf20Sopenharmony_ci{ 1098c2ecf20Sopenharmony_ci void __iomem *ibase = ssusb->ippc_base; 1108c2ecf20Sopenharmony_ci int num_u3p = ssusb->u3_ports; 1118c2ecf20Sopenharmony_ci int num_u2p = ssusb->u2_ports; 1128c2ecf20Sopenharmony_ci int u3_ports_disabed; 1138c2ecf20Sopenharmony_ci u32 check_clk; 1148c2ecf20Sopenharmony_ci u32 value; 1158c2ecf20Sopenharmony_ci int i; 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci /* power on host ip */ 1188c2ecf20Sopenharmony_ci mtu3_clrbits(ibase, U3D_SSUSB_IP_PW_CTRL1, SSUSB_IP_HOST_PDN); 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci /* power on and enable u3 ports except skipped ones */ 1218c2ecf20Sopenharmony_ci u3_ports_disabed = 0; 1228c2ecf20Sopenharmony_ci for (i = 0; i < num_u3p; i++) { 1238c2ecf20Sopenharmony_ci if ((0x1 << i) & ssusb->u3p_dis_msk) { 1248c2ecf20Sopenharmony_ci u3_ports_disabed++; 1258c2ecf20Sopenharmony_ci continue; 1268c2ecf20Sopenharmony_ci } 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci value = mtu3_readl(ibase, SSUSB_U3_CTRL(i)); 1298c2ecf20Sopenharmony_ci value &= ~(SSUSB_U3_PORT_PDN | SSUSB_U3_PORT_DIS); 1308c2ecf20Sopenharmony_ci value |= SSUSB_U3_PORT_HOST_SEL; 1318c2ecf20Sopenharmony_ci mtu3_writel(ibase, SSUSB_U3_CTRL(i), value); 1328c2ecf20Sopenharmony_ci } 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci /* power on and enable all u2 ports */ 1358c2ecf20Sopenharmony_ci for (i = 0; i < num_u2p; i++) { 1368c2ecf20Sopenharmony_ci value = mtu3_readl(ibase, SSUSB_U2_CTRL(i)); 1378c2ecf20Sopenharmony_ci value &= ~(SSUSB_U2_PORT_PDN | SSUSB_U2_PORT_DIS); 1388c2ecf20Sopenharmony_ci value |= SSUSB_U2_PORT_HOST_SEL; 1398c2ecf20Sopenharmony_ci mtu3_writel(ibase, SSUSB_U2_CTRL(i), value); 1408c2ecf20Sopenharmony_ci } 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci check_clk = SSUSB_XHCI_RST_B_STS; 1438c2ecf20Sopenharmony_ci if (num_u3p > u3_ports_disabed) 1448c2ecf20Sopenharmony_ci check_clk = SSUSB_U3_MAC_RST_B_STS; 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci return ssusb_check_clocks(ssusb, check_clk); 1478c2ecf20Sopenharmony_ci} 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ciint ssusb_host_disable(struct ssusb_mtk *ssusb, bool suspend) 1508c2ecf20Sopenharmony_ci{ 1518c2ecf20Sopenharmony_ci void __iomem *ibase = ssusb->ippc_base; 1528c2ecf20Sopenharmony_ci int num_u3p = ssusb->u3_ports; 1538c2ecf20Sopenharmony_ci int num_u2p = ssusb->u2_ports; 1548c2ecf20Sopenharmony_ci u32 value; 1558c2ecf20Sopenharmony_ci int ret; 1568c2ecf20Sopenharmony_ci int i; 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci /* power down and disable u3 ports except skipped ones */ 1598c2ecf20Sopenharmony_ci for (i = 0; i < num_u3p; i++) { 1608c2ecf20Sopenharmony_ci if ((0x1 << i) & ssusb->u3p_dis_msk) 1618c2ecf20Sopenharmony_ci continue; 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci value = mtu3_readl(ibase, SSUSB_U3_CTRL(i)); 1648c2ecf20Sopenharmony_ci value |= SSUSB_U3_PORT_PDN; 1658c2ecf20Sopenharmony_ci value |= suspend ? 0 : SSUSB_U3_PORT_DIS; 1668c2ecf20Sopenharmony_ci mtu3_writel(ibase, SSUSB_U3_CTRL(i), value); 1678c2ecf20Sopenharmony_ci } 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ci /* power down and disable all u2 ports */ 1708c2ecf20Sopenharmony_ci for (i = 0; i < num_u2p; i++) { 1718c2ecf20Sopenharmony_ci value = mtu3_readl(ibase, SSUSB_U2_CTRL(i)); 1728c2ecf20Sopenharmony_ci value |= SSUSB_U2_PORT_PDN; 1738c2ecf20Sopenharmony_ci value |= suspend ? 0 : SSUSB_U2_PORT_DIS; 1748c2ecf20Sopenharmony_ci mtu3_writel(ibase, SSUSB_U2_CTRL(i), value); 1758c2ecf20Sopenharmony_ci } 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci /* power down host ip */ 1788c2ecf20Sopenharmony_ci mtu3_setbits(ibase, U3D_SSUSB_IP_PW_CTRL1, SSUSB_IP_HOST_PDN); 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci if (!suspend) 1818c2ecf20Sopenharmony_ci return 0; 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci /* wait for host ip to sleep */ 1848c2ecf20Sopenharmony_ci ret = readl_poll_timeout(ibase + U3D_SSUSB_IP_PW_STS1, value, 1858c2ecf20Sopenharmony_ci (value & SSUSB_IP_SLEEP_STS), 100, 100000); 1868c2ecf20Sopenharmony_ci if (ret) 1878c2ecf20Sopenharmony_ci dev_err(ssusb->dev, "ip sleep failed!!!\n"); 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci return ret; 1908c2ecf20Sopenharmony_ci} 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_cistatic void ssusb_host_setup(struct ssusb_mtk *ssusb) 1938c2ecf20Sopenharmony_ci{ 1948c2ecf20Sopenharmony_ci struct otg_switch_mtk *otg_sx = &ssusb->otg_switch; 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci host_ports_num_get(ssusb); 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci /* 1998c2ecf20Sopenharmony_ci * power on host and power on/enable all ports 2008c2ecf20Sopenharmony_ci * if support OTG, gadget driver will switch port0 to device mode 2018c2ecf20Sopenharmony_ci */ 2028c2ecf20Sopenharmony_ci ssusb_host_enable(ssusb); 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci if (otg_sx->manual_drd_enabled) 2058c2ecf20Sopenharmony_ci ssusb_set_force_mode(ssusb, MTU3_DR_FORCE_HOST); 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci /* if port0 supports dual-role, works as host mode by default */ 2088c2ecf20Sopenharmony_ci ssusb_set_vbus(&ssusb->otg_switch, 1); 2098c2ecf20Sopenharmony_ci} 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_cistatic void ssusb_host_cleanup(struct ssusb_mtk *ssusb) 2128c2ecf20Sopenharmony_ci{ 2138c2ecf20Sopenharmony_ci if (ssusb->is_host) 2148c2ecf20Sopenharmony_ci ssusb_set_vbus(&ssusb->otg_switch, 0); 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci ssusb_host_disable(ssusb, false); 2178c2ecf20Sopenharmony_ci} 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci/* 2208c2ecf20Sopenharmony_ci * If host supports multiple ports, the VBUSes(5V) of ports except port0 2218c2ecf20Sopenharmony_ci * which supports OTG are better to be enabled by default in DTS. 2228c2ecf20Sopenharmony_ci * Because the host driver will keep link with devices attached when system 2238c2ecf20Sopenharmony_ci * enters suspend mode, so no need to control VBUSes after initialization. 2248c2ecf20Sopenharmony_ci */ 2258c2ecf20Sopenharmony_ciint ssusb_host_init(struct ssusb_mtk *ssusb, struct device_node *parent_dn) 2268c2ecf20Sopenharmony_ci{ 2278c2ecf20Sopenharmony_ci struct device *parent_dev = ssusb->dev; 2288c2ecf20Sopenharmony_ci int ret; 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci ssusb_host_setup(ssusb); 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci ret = of_platform_populate(parent_dn, NULL, NULL, parent_dev); 2338c2ecf20Sopenharmony_ci if (ret) { 2348c2ecf20Sopenharmony_ci dev_dbg(parent_dev, "failed to create child devices at %pOF\n", 2358c2ecf20Sopenharmony_ci parent_dn); 2368c2ecf20Sopenharmony_ci return ret; 2378c2ecf20Sopenharmony_ci } 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_ci dev_info(parent_dev, "xHCI platform device register success...\n"); 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci return 0; 2428c2ecf20Sopenharmony_ci} 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_civoid ssusb_host_exit(struct ssusb_mtk *ssusb) 2458c2ecf20Sopenharmony_ci{ 2468c2ecf20Sopenharmony_ci of_platform_depopulate(ssusb->dev); 2478c2ecf20Sopenharmony_ci ssusb_host_cleanup(ssusb); 2488c2ecf20Sopenharmony_ci} 249