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