1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // Copyright (c) 2018-2019 MediaTek Inc. |
3 | /* A library for MediaTek SGMII circuit |
4 | * |
5 | * Author: Sean Wang <sean.wang@mediatek.com> |
6 | * Author: Alexander Couzens <lynxis@fe80.eu> |
7 | * Author: Daniel Golle <daniel@makrotopia.org> |
8 | * |
9 | */ |
10 | |
11 | #include <linux/mdio.h> |
12 | #include <linux/of.h> |
13 | #include <linux/pcs/pcs-mtk-lynxi.h> |
14 | #include <linux/phylink.h> |
15 | #include <linux/regmap.h> |
16 | |
17 | /* SGMII subsystem config registers */ |
18 | /* BMCR (low 16) BMSR (high 16) */ |
19 | #define SGMSYS_PCS_CONTROL_1 0x0 |
20 | #define SGMII_BMCR GENMASK(15, 0) |
21 | #define SGMII_BMSR GENMASK(31, 16) |
22 | |
23 | #define SGMSYS_PCS_DEVICE_ID 0x4 |
24 | #define SGMII_LYNXI_DEV_ID 0x4d544950 |
25 | |
26 | #define SGMSYS_PCS_ADVERTISE 0x8 |
27 | #define SGMII_ADVERTISE GENMASK(15, 0) |
28 | #define SGMII_LPA GENMASK(31, 16) |
29 | |
30 | #define SGMSYS_PCS_SCRATCH 0x14 |
31 | #define SGMII_DEV_VERSION GENMASK(31, 16) |
32 | |
33 | /* Register to programmable link timer, the unit in 2 * 8ns */ |
34 | #define SGMSYS_PCS_LINK_TIMER 0x18 |
35 | #define SGMII_LINK_TIMER_MASK GENMASK(19, 0) |
36 | #define SGMII_LINK_TIMER_VAL(ns) FIELD_PREP(SGMII_LINK_TIMER_MASK, \ |
37 | ((ns) / 2 / 8)) |
38 | |
39 | /* Register to control remote fault */ |
40 | #define SGMSYS_SGMII_MODE 0x20 |
41 | #define SGMII_IF_MODE_SGMII BIT(0) |
42 | #define SGMII_SPEED_DUPLEX_AN BIT(1) |
43 | #define SGMII_SPEED_MASK GENMASK(3, 2) |
44 | #define SGMII_SPEED_10 FIELD_PREP(SGMII_SPEED_MASK, 0) |
45 | #define SGMII_SPEED_100 FIELD_PREP(SGMII_SPEED_MASK, 1) |
46 | #define SGMII_SPEED_1000 FIELD_PREP(SGMII_SPEED_MASK, 2) |
47 | #define SGMII_DUPLEX_HALF BIT(4) |
48 | #define SGMII_REMOTE_FAULT_DIS BIT(8) |
49 | |
50 | /* Register to reset SGMII design */ |
51 | #define SGMSYS_RESERVED_0 0x34 |
52 | #define SGMII_SW_RESET BIT(0) |
53 | |
54 | /* Register to set SGMII speed, ANA RG_ Control Signals III */ |
55 | #define SGMII_PHY_SPEED_MASK GENMASK(3, 2) |
56 | #define SGMII_PHY_SPEED_1_25G FIELD_PREP(SGMII_PHY_SPEED_MASK, 0) |
57 | #define SGMII_PHY_SPEED_3_125G FIELD_PREP(SGMII_PHY_SPEED_MASK, 1) |
58 | |
59 | /* Register to power up QPHY */ |
60 | #define SGMSYS_QPHY_PWR_STATE_CTRL 0xe8 |
61 | #define SGMII_PHYA_PWD BIT(4) |
62 | |
63 | /* Register to QPHY wrapper control */ |
64 | #define SGMSYS_QPHY_WRAP_CTRL 0xec |
65 | #define SGMII_PN_SWAP_MASK GENMASK(1, 0) |
66 | #define SGMII_PN_SWAP_TX_RX (BIT(0) | BIT(1)) |
67 | |
68 | /* struct mtk_pcs_lynxi - This structure holds each sgmii regmap andassociated |
69 | * data |
70 | * @regmap: The register map pointing at the range used to setup |
71 | * SGMII modes |
72 | * @dev: Pointer to device owning the PCS |
73 | * @ana_rgc3: The offset of register ANA_RGC3 relative to regmap |
74 | * @interface: Currently configured interface mode |
75 | * @pcs: Phylink PCS structure |
76 | * @flags: Flags indicating hardware properties |
77 | */ |
78 | struct mtk_pcs_lynxi { |
79 | struct regmap *regmap; |
80 | u32 ana_rgc3; |
81 | phy_interface_t interface; |
82 | struct phylink_pcs pcs; |
83 | u32 flags; |
84 | }; |
85 | |
86 | static struct mtk_pcs_lynxi *pcs_to_mtk_pcs_lynxi(struct phylink_pcs *pcs) |
87 | { |
88 | return container_of(pcs, struct mtk_pcs_lynxi, pcs); |
89 | } |
90 | |
91 | static void mtk_pcs_lynxi_get_state(struct phylink_pcs *pcs, |
92 | struct phylink_link_state *state) |
93 | { |
94 | struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs); |
95 | unsigned int bm, adv; |
96 | |
97 | /* Read the BMSR and LPA */ |
98 | regmap_read(map: mpcs->regmap, SGMSYS_PCS_CONTROL_1, val: &bm); |
99 | regmap_read(map: mpcs->regmap, SGMSYS_PCS_ADVERTISE, val: &adv); |
100 | |
101 | phylink_mii_c22_pcs_decode_state(state, FIELD_GET(SGMII_BMSR, bm), |
102 | FIELD_GET(SGMII_LPA, adv)); |
103 | } |
104 | |
105 | static int mtk_pcs_lynxi_config(struct phylink_pcs *pcs, unsigned int neg_mode, |
106 | phy_interface_t interface, |
107 | const unsigned long *advertising, |
108 | bool permit_pause_to_mac) |
109 | { |
110 | struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs); |
111 | bool mode_changed = false, changed; |
112 | unsigned int rgc3, sgm_mode, bmcr; |
113 | int advertise, link_timer; |
114 | |
115 | advertise = phylink_mii_c22_pcs_encode_advertisement(interface, |
116 | advertising); |
117 | if (advertise < 0) |
118 | return advertise; |
119 | |
120 | /* Clearing IF_MODE_BIT0 switches the PCS to BASE-X mode, and |
121 | * we assume that fixes it's speed at bitrate = line rate (in |
122 | * other words, 1000Mbps or 2500Mbps). |
123 | */ |
124 | if (interface == PHY_INTERFACE_MODE_SGMII) |
125 | sgm_mode = SGMII_IF_MODE_SGMII; |
126 | else |
127 | sgm_mode = 0; |
128 | |
129 | if (neg_mode & PHYLINK_PCS_NEG_INBAND) |
130 | sgm_mode |= SGMII_REMOTE_FAULT_DIS; |
131 | |
132 | if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) { |
133 | if (interface == PHY_INTERFACE_MODE_SGMII) |
134 | sgm_mode |= SGMII_SPEED_DUPLEX_AN; |
135 | bmcr = BMCR_ANENABLE; |
136 | } else { |
137 | bmcr = 0; |
138 | } |
139 | |
140 | if (mpcs->interface != interface) { |
141 | link_timer = phylink_get_link_timer_ns(interface); |
142 | if (link_timer < 0) |
143 | return link_timer; |
144 | |
145 | /* PHYA power down */ |
146 | regmap_set_bits(map: mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL, |
147 | SGMII_PHYA_PWD); |
148 | |
149 | /* Reset SGMII PCS state */ |
150 | regmap_set_bits(map: mpcs->regmap, SGMSYS_RESERVED_0, |
151 | SGMII_SW_RESET); |
152 | |
153 | if (mpcs->flags & MTK_SGMII_FLAG_PN_SWAP) |
154 | regmap_update_bits(map: mpcs->regmap, SGMSYS_QPHY_WRAP_CTRL, |
155 | SGMII_PN_SWAP_MASK, |
156 | SGMII_PN_SWAP_TX_RX); |
157 | |
158 | if (interface == PHY_INTERFACE_MODE_2500BASEX) |
159 | rgc3 = SGMII_PHY_SPEED_3_125G; |
160 | else |
161 | rgc3 = SGMII_PHY_SPEED_1_25G; |
162 | |
163 | /* Configure the underlying interface speed */ |
164 | regmap_update_bits(map: mpcs->regmap, reg: mpcs->ana_rgc3, |
165 | SGMII_PHY_SPEED_MASK, val: rgc3); |
166 | |
167 | /* Setup the link timer */ |
168 | regmap_write(map: mpcs->regmap, SGMSYS_PCS_LINK_TIMER, |
169 | SGMII_LINK_TIMER_VAL(link_timer)); |
170 | |
171 | mpcs->interface = interface; |
172 | mode_changed = true; |
173 | } |
174 | |
175 | /* Update the advertisement, noting whether it has changed */ |
176 | regmap_update_bits_check(map: mpcs->regmap, SGMSYS_PCS_ADVERTISE, |
177 | SGMII_ADVERTISE, val: advertise, change: &changed); |
178 | |
179 | /* Update the sgmsys mode register */ |
180 | regmap_update_bits(map: mpcs->regmap, SGMSYS_SGMII_MODE, |
181 | SGMII_REMOTE_FAULT_DIS | SGMII_SPEED_DUPLEX_AN | |
182 | SGMII_IF_MODE_SGMII, val: sgm_mode); |
183 | |
184 | /* Update the BMCR */ |
185 | regmap_update_bits(map: mpcs->regmap, SGMSYS_PCS_CONTROL_1, |
186 | BMCR_ANENABLE, val: bmcr); |
187 | |
188 | /* Release PHYA power down state |
189 | * Only removing bit SGMII_PHYA_PWD isn't enough. |
190 | * There are cases when the SGMII_PHYA_PWD register contains 0x9 which |
191 | * prevents SGMII from working. The SGMII still shows link but no traffic |
192 | * can flow. Writing 0x0 to the PHYA_PWD register fix the issue. 0x0 was |
193 | * taken from a good working state of the SGMII interface. |
194 | * Unknown how much the QPHY needs but it is racy without a sleep. |
195 | * Tested on mt7622 & mt7986. |
196 | */ |
197 | usleep_range(min: 50, max: 100); |
198 | regmap_write(map: mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL, val: 0); |
199 | |
200 | return changed || mode_changed; |
201 | } |
202 | |
203 | static void mtk_pcs_lynxi_restart_an(struct phylink_pcs *pcs) |
204 | { |
205 | struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs); |
206 | |
207 | regmap_set_bits(map: mpcs->regmap, SGMSYS_PCS_CONTROL_1, BMCR_ANRESTART); |
208 | } |
209 | |
210 | static void mtk_pcs_lynxi_link_up(struct phylink_pcs *pcs, |
211 | unsigned int neg_mode, |
212 | phy_interface_t interface, int speed, |
213 | int duplex) |
214 | { |
215 | struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs); |
216 | unsigned int sgm_mode; |
217 | |
218 | if (neg_mode != PHYLINK_PCS_NEG_INBAND_ENABLED) { |
219 | /* Force the speed and duplex setting */ |
220 | if (speed == SPEED_10) |
221 | sgm_mode = SGMII_SPEED_10; |
222 | else if (speed == SPEED_100) |
223 | sgm_mode = SGMII_SPEED_100; |
224 | else |
225 | sgm_mode = SGMII_SPEED_1000; |
226 | |
227 | if (duplex != DUPLEX_FULL) |
228 | sgm_mode |= SGMII_DUPLEX_HALF; |
229 | |
230 | regmap_update_bits(map: mpcs->regmap, SGMSYS_SGMII_MODE, |
231 | SGMII_DUPLEX_HALF | SGMII_SPEED_MASK, |
232 | val: sgm_mode); |
233 | } |
234 | } |
235 | |
236 | static void mtk_pcs_lynxi_disable(struct phylink_pcs *pcs) |
237 | { |
238 | struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs); |
239 | |
240 | mpcs->interface = PHY_INTERFACE_MODE_NA; |
241 | } |
242 | |
243 | static const struct phylink_pcs_ops mtk_pcs_lynxi_ops = { |
244 | .pcs_get_state = mtk_pcs_lynxi_get_state, |
245 | .pcs_config = mtk_pcs_lynxi_config, |
246 | .pcs_an_restart = mtk_pcs_lynxi_restart_an, |
247 | .pcs_link_up = mtk_pcs_lynxi_link_up, |
248 | .pcs_disable = mtk_pcs_lynxi_disable, |
249 | }; |
250 | |
251 | struct phylink_pcs *mtk_pcs_lynxi_create(struct device *dev, |
252 | struct regmap *regmap, u32 ana_rgc3, |
253 | u32 flags) |
254 | { |
255 | struct mtk_pcs_lynxi *mpcs; |
256 | u32 id, ver; |
257 | int ret; |
258 | |
259 | ret = regmap_read(map: regmap, SGMSYS_PCS_DEVICE_ID, val: &id); |
260 | if (ret < 0) |
261 | return NULL; |
262 | |
263 | if (id != SGMII_LYNXI_DEV_ID) { |
264 | dev_err(dev, "unknown PCS device id %08x\n" , id); |
265 | return NULL; |
266 | } |
267 | |
268 | ret = regmap_read(map: regmap, SGMSYS_PCS_SCRATCH, val: &ver); |
269 | if (ret < 0) |
270 | return NULL; |
271 | |
272 | ver = FIELD_GET(SGMII_DEV_VERSION, ver); |
273 | if (ver != 0x1) { |
274 | dev_err(dev, "unknown PCS device version %04x\n" , ver); |
275 | return NULL; |
276 | } |
277 | |
278 | dev_dbg(dev, "MediaTek LynxI SGMII PCS (id 0x%08x, ver 0x%04x)\n" , id, |
279 | ver); |
280 | |
281 | mpcs = kzalloc(size: sizeof(*mpcs), GFP_KERNEL); |
282 | if (!mpcs) |
283 | return NULL; |
284 | |
285 | mpcs->ana_rgc3 = ana_rgc3; |
286 | mpcs->regmap = regmap; |
287 | mpcs->flags = flags; |
288 | mpcs->pcs.ops = &mtk_pcs_lynxi_ops; |
289 | mpcs->pcs.neg_mode = true; |
290 | mpcs->pcs.poll = true; |
291 | mpcs->interface = PHY_INTERFACE_MODE_NA; |
292 | |
293 | return &mpcs->pcs; |
294 | } |
295 | EXPORT_SYMBOL(mtk_pcs_lynxi_create); |
296 | |
297 | void mtk_pcs_lynxi_destroy(struct phylink_pcs *pcs) |
298 | { |
299 | if (!pcs) |
300 | return; |
301 | |
302 | kfree(objp: pcs_to_mtk_pcs_lynxi(pcs)); |
303 | } |
304 | EXPORT_SYMBOL(mtk_pcs_lynxi_destroy); |
305 | |
306 | MODULE_DESCRIPTION("MediaTek SGMII library for LynxI" ); |
307 | MODULE_LICENSE("GPL" ); |
308 | |