1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Samsung SoC USB 1.1/2.0 PHY driver - S5PV210 support |
4 | * |
5 | * Copyright (C) 2013 Samsung Electronics Co., Ltd. |
6 | * Authors: Kamil Debski <k.debski@samsung.com> |
7 | */ |
8 | |
9 | #include <linux/delay.h> |
10 | #include <linux/io.h> |
11 | #include <linux/phy/phy.h> |
12 | #include "phy-samsung-usb2.h" |
13 | |
14 | /* Exynos USB PHY registers */ |
15 | |
16 | /* PHY power control */ |
17 | #define S5PV210_UPHYPWR 0x0 |
18 | |
19 | #define S5PV210_UPHYPWR_PHY0_SUSPEND BIT(0) |
20 | #define S5PV210_UPHYPWR_PHY0_PWR BIT(3) |
21 | #define S5PV210_UPHYPWR_PHY0_OTG_PWR BIT(4) |
22 | #define S5PV210_UPHYPWR_PHY0 ( \ |
23 | S5PV210_UPHYPWR_PHY0_SUSPEND | \ |
24 | S5PV210_UPHYPWR_PHY0_PWR | \ |
25 | S5PV210_UPHYPWR_PHY0_OTG_PWR) |
26 | |
27 | #define S5PV210_UPHYPWR_PHY1_SUSPEND BIT(6) |
28 | #define S5PV210_UPHYPWR_PHY1_PWR BIT(7) |
29 | #define S5PV210_UPHYPWR_PHY1 ( \ |
30 | S5PV210_UPHYPWR_PHY1_SUSPEND | \ |
31 | S5PV210_UPHYPWR_PHY1_PWR) |
32 | |
33 | /* PHY clock control */ |
34 | #define S5PV210_UPHYCLK 0x4 |
35 | |
36 | #define S5PV210_UPHYCLK_PHYFSEL_MASK (0x3 << 0) |
37 | #define S5PV210_UPHYCLK_PHYFSEL_48MHZ (0x0 << 0) |
38 | #define S5PV210_UPHYCLK_PHYFSEL_24MHZ (0x3 << 0) |
39 | #define S5PV210_UPHYCLK_PHYFSEL_12MHZ (0x2 << 0) |
40 | |
41 | #define S5PV210_UPHYCLK_PHY0_ID_PULLUP BIT(2) |
42 | #define S5PV210_UPHYCLK_PHY0_COMMON_ON BIT(4) |
43 | #define S5PV210_UPHYCLK_PHY1_COMMON_ON BIT(7) |
44 | |
45 | /* PHY reset control */ |
46 | #define S5PV210_UPHYRST 0x8 |
47 | |
48 | #define S5PV210_URSTCON_PHY0 BIT(0) |
49 | #define S5PV210_URSTCON_OTG_HLINK BIT(1) |
50 | #define S5PV210_URSTCON_OTG_PHYLINK BIT(2) |
51 | #define S5PV210_URSTCON_PHY1_ALL BIT(3) |
52 | #define S5PV210_URSTCON_HOST_LINK_ALL BIT(4) |
53 | |
54 | /* Isolation, configured in the power management unit */ |
55 | #define S5PV210_USB_ISOL_OFFSET 0x680c |
56 | #define S5PV210_USB_ISOL_DEVICE BIT(0) |
57 | #define S5PV210_USB_ISOL_HOST BIT(1) |
58 | |
59 | |
60 | enum s5pv210_phy_id { |
61 | S5PV210_DEVICE, |
62 | S5PV210_HOST, |
63 | S5PV210_NUM_PHYS, |
64 | }; |
65 | |
66 | /* |
67 | * s5pv210_rate_to_clk() converts the supplied clock rate to the value that |
68 | * can be written to the phy register. |
69 | */ |
70 | static int s5pv210_rate_to_clk(unsigned long rate, u32 *reg) |
71 | { |
72 | switch (rate) { |
73 | case 12 * MHZ: |
74 | *reg = S5PV210_UPHYCLK_PHYFSEL_12MHZ; |
75 | break; |
76 | case 24 * MHZ: |
77 | *reg = S5PV210_UPHYCLK_PHYFSEL_24MHZ; |
78 | break; |
79 | case 48 * MHZ: |
80 | *reg = S5PV210_UPHYCLK_PHYFSEL_48MHZ; |
81 | break; |
82 | default: |
83 | return -EINVAL; |
84 | } |
85 | |
86 | return 0; |
87 | } |
88 | |
89 | static void s5pv210_isol(struct samsung_usb2_phy_instance *inst, bool on) |
90 | { |
91 | struct samsung_usb2_phy_driver *drv = inst->drv; |
92 | u32 mask; |
93 | |
94 | switch (inst->cfg->id) { |
95 | case S5PV210_DEVICE: |
96 | mask = S5PV210_USB_ISOL_DEVICE; |
97 | break; |
98 | case S5PV210_HOST: |
99 | mask = S5PV210_USB_ISOL_HOST; |
100 | break; |
101 | default: |
102 | return; |
103 | } |
104 | |
105 | regmap_update_bits(map: drv->reg_pmu, S5PV210_USB_ISOL_OFFSET, |
106 | mask, val: on ? 0 : mask); |
107 | } |
108 | |
109 | static void s5pv210_phy_pwr(struct samsung_usb2_phy_instance *inst, bool on) |
110 | { |
111 | struct samsung_usb2_phy_driver *drv = inst->drv; |
112 | u32 rstbits = 0; |
113 | u32 phypwr = 0; |
114 | u32 rst; |
115 | u32 pwr; |
116 | |
117 | switch (inst->cfg->id) { |
118 | case S5PV210_DEVICE: |
119 | phypwr = S5PV210_UPHYPWR_PHY0; |
120 | rstbits = S5PV210_URSTCON_PHY0; |
121 | break; |
122 | case S5PV210_HOST: |
123 | phypwr = S5PV210_UPHYPWR_PHY1; |
124 | rstbits = S5PV210_URSTCON_PHY1_ALL | |
125 | S5PV210_URSTCON_HOST_LINK_ALL; |
126 | break; |
127 | } |
128 | |
129 | if (on) { |
130 | writel(val: drv->ref_reg_val, addr: drv->reg_phy + S5PV210_UPHYCLK); |
131 | |
132 | pwr = readl(addr: drv->reg_phy + S5PV210_UPHYPWR); |
133 | pwr &= ~phypwr; |
134 | writel(val: pwr, addr: drv->reg_phy + S5PV210_UPHYPWR); |
135 | |
136 | rst = readl(addr: drv->reg_phy + S5PV210_UPHYRST); |
137 | rst |= rstbits; |
138 | writel(val: rst, addr: drv->reg_phy + S5PV210_UPHYRST); |
139 | udelay(10); |
140 | rst &= ~rstbits; |
141 | writel(val: rst, addr: drv->reg_phy + S5PV210_UPHYRST); |
142 | /* The following delay is necessary for the reset sequence to be |
143 | * completed |
144 | */ |
145 | udelay(80); |
146 | } else { |
147 | pwr = readl(addr: drv->reg_phy + S5PV210_UPHYPWR); |
148 | pwr |= phypwr; |
149 | writel(val: pwr, addr: drv->reg_phy + S5PV210_UPHYPWR); |
150 | } |
151 | } |
152 | |
153 | static int s5pv210_power_on(struct samsung_usb2_phy_instance *inst) |
154 | { |
155 | s5pv210_isol(inst, on: 0); |
156 | s5pv210_phy_pwr(inst, on: 1); |
157 | |
158 | return 0; |
159 | } |
160 | |
161 | static int s5pv210_power_off(struct samsung_usb2_phy_instance *inst) |
162 | { |
163 | s5pv210_phy_pwr(inst, on: 0); |
164 | s5pv210_isol(inst, on: 1); |
165 | |
166 | return 0; |
167 | } |
168 | |
169 | static const struct samsung_usb2_common_phy s5pv210_phys[S5PV210_NUM_PHYS] = { |
170 | [S5PV210_DEVICE] = { |
171 | .label = "device" , |
172 | .id = S5PV210_DEVICE, |
173 | .power_on = s5pv210_power_on, |
174 | .power_off = s5pv210_power_off, |
175 | }, |
176 | [S5PV210_HOST] = { |
177 | .label = "host" , |
178 | .id = S5PV210_HOST, |
179 | .power_on = s5pv210_power_on, |
180 | .power_off = s5pv210_power_off, |
181 | }, |
182 | }; |
183 | |
184 | const struct samsung_usb2_phy_config s5pv210_usb2_phy_config = { |
185 | .num_phys = ARRAY_SIZE(s5pv210_phys), |
186 | .phys = s5pv210_phys, |
187 | .rate_to_clk = s5pv210_rate_to_clk, |
188 | }; |
189 | |