1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* $Date: 2005/10/24 23:18:13 $ $RCSfile: mv88e1xxx.c,v $ $Revision: 1.49 $ */ |
3 | #include "common.h" |
4 | #include "mv88e1xxx.h" |
5 | #include "cphy.h" |
6 | #include "elmer0.h" |
7 | |
8 | /* MV88E1XXX MDI crossover register values */ |
9 | #define CROSSOVER_MDI 0 |
10 | #define CROSSOVER_MDIX 1 |
11 | #define CROSSOVER_AUTO 3 |
12 | |
13 | #define INTR_ENABLE_MASK 0x6CA0 |
14 | |
15 | /* |
16 | * Set the bits given by 'bitval' in PHY register 'reg'. |
17 | */ |
18 | static void mdio_set_bit(struct cphy *cphy, int reg, u32 bitval) |
19 | { |
20 | u32 val; |
21 | |
22 | (void) simple_mdio_read(cphy, reg, valp: &val); |
23 | (void) simple_mdio_write(cphy, reg, val: val | bitval); |
24 | } |
25 | |
26 | /* |
27 | * Clear the bits given by 'bitval' in PHY register 'reg'. |
28 | */ |
29 | static void mdio_clear_bit(struct cphy *cphy, int reg, u32 bitval) |
30 | { |
31 | u32 val; |
32 | |
33 | (void) simple_mdio_read(cphy, reg, valp: &val); |
34 | (void) simple_mdio_write(cphy, reg, val: val & ~bitval); |
35 | } |
36 | |
37 | /* |
38 | * NAME: phy_reset |
39 | * |
40 | * DESC: Reset the given PHY's port. NOTE: This is not a global |
41 | * chip reset. |
42 | * |
43 | * PARAMS: cphy - Pointer to PHY instance data. |
44 | * |
45 | * RETURN: 0 - Successful reset. |
46 | * -1 - Timeout. |
47 | */ |
48 | static int mv88e1xxx_reset(struct cphy *cphy, int wait) |
49 | { |
50 | u32 ctl; |
51 | int time_out = 1000; |
52 | |
53 | mdio_set_bit(cphy, MII_BMCR, BMCR_RESET); |
54 | |
55 | do { |
56 | (void) simple_mdio_read(cphy, MII_BMCR, valp: &ctl); |
57 | ctl &= BMCR_RESET; |
58 | if (ctl) |
59 | udelay(1); |
60 | } while (ctl && --time_out); |
61 | |
62 | return ctl ? -1 : 0; |
63 | } |
64 | |
65 | static int mv88e1xxx_interrupt_enable(struct cphy *cphy) |
66 | { |
67 | /* Enable PHY interrupts. */ |
68 | (void) simple_mdio_write(cphy, MV88E1XXX_INTERRUPT_ENABLE_REGISTER, |
69 | INTR_ENABLE_MASK); |
70 | |
71 | /* Enable Marvell interrupts through Elmer0. */ |
72 | if (t1_is_asic(adapter: cphy->adapter)) { |
73 | u32 elmer; |
74 | |
75 | t1_tpi_read(adapter: cphy->adapter, A_ELMER0_INT_ENABLE, value: &elmer); |
76 | elmer |= ELMER0_GP_BIT1; |
77 | if (is_T2(cphy->adapter)) |
78 | elmer |= ELMER0_GP_BIT2 | ELMER0_GP_BIT3 | ELMER0_GP_BIT4; |
79 | t1_tpi_write(adapter: cphy->adapter, A_ELMER0_INT_ENABLE, value: elmer); |
80 | } |
81 | return 0; |
82 | } |
83 | |
84 | static int mv88e1xxx_interrupt_disable(struct cphy *cphy) |
85 | { |
86 | /* Disable all phy interrupts. */ |
87 | (void) simple_mdio_write(cphy, MV88E1XXX_INTERRUPT_ENABLE_REGISTER, val: 0); |
88 | |
89 | /* Disable Marvell interrupts through Elmer0. */ |
90 | if (t1_is_asic(adapter: cphy->adapter)) { |
91 | u32 elmer; |
92 | |
93 | t1_tpi_read(adapter: cphy->adapter, A_ELMER0_INT_ENABLE, value: &elmer); |
94 | elmer &= ~ELMER0_GP_BIT1; |
95 | if (is_T2(cphy->adapter)) |
96 | elmer &= ~(ELMER0_GP_BIT2|ELMER0_GP_BIT3|ELMER0_GP_BIT4); |
97 | t1_tpi_write(adapter: cphy->adapter, A_ELMER0_INT_ENABLE, value: elmer); |
98 | } |
99 | return 0; |
100 | } |
101 | |
102 | static int mv88e1xxx_interrupt_clear(struct cphy *cphy) |
103 | { |
104 | u32 elmer; |
105 | |
106 | /* Clear PHY interrupts by reading the register. */ |
107 | (void) simple_mdio_read(cphy, |
108 | MV88E1XXX_INTERRUPT_STATUS_REGISTER, valp: &elmer); |
109 | |
110 | /* Clear Marvell interrupts through Elmer0. */ |
111 | if (t1_is_asic(adapter: cphy->adapter)) { |
112 | t1_tpi_read(adapter: cphy->adapter, A_ELMER0_INT_CAUSE, value: &elmer); |
113 | elmer |= ELMER0_GP_BIT1; |
114 | if (is_T2(cphy->adapter)) |
115 | elmer |= ELMER0_GP_BIT2|ELMER0_GP_BIT3|ELMER0_GP_BIT4; |
116 | t1_tpi_write(adapter: cphy->adapter, A_ELMER0_INT_CAUSE, value: elmer); |
117 | } |
118 | return 0; |
119 | } |
120 | |
121 | /* |
122 | * Set the PHY speed and duplex. This also disables auto-negotiation, except |
123 | * for 1Gb/s, where auto-negotiation is mandatory. |
124 | */ |
125 | static int mv88e1xxx_set_speed_duplex(struct cphy *phy, int speed, int duplex) |
126 | { |
127 | u32 ctl; |
128 | |
129 | (void) simple_mdio_read(cphy: phy, MII_BMCR, valp: &ctl); |
130 | if (speed >= 0) { |
131 | ctl &= ~(BMCR_SPEED100 | BMCR_SPEED1000 | BMCR_ANENABLE); |
132 | if (speed == SPEED_100) |
133 | ctl |= BMCR_SPEED100; |
134 | else if (speed == SPEED_1000) |
135 | ctl |= BMCR_SPEED1000; |
136 | } |
137 | if (duplex >= 0) { |
138 | ctl &= ~(BMCR_FULLDPLX | BMCR_ANENABLE); |
139 | if (duplex == DUPLEX_FULL) |
140 | ctl |= BMCR_FULLDPLX; |
141 | } |
142 | if (ctl & BMCR_SPEED1000) /* auto-negotiation required for 1Gb/s */ |
143 | ctl |= BMCR_ANENABLE; |
144 | (void) simple_mdio_write(cphy: phy, MII_BMCR, val: ctl); |
145 | return 0; |
146 | } |
147 | |
148 | static int mv88e1xxx_crossover_set(struct cphy *cphy, int crossover) |
149 | { |
150 | u32 data32; |
151 | |
152 | (void) simple_mdio_read(cphy, |
153 | MV88E1XXX_SPECIFIC_CNTRL_REGISTER, valp: &data32); |
154 | data32 &= ~V_PSCR_MDI_XOVER_MODE(M_PSCR_MDI_XOVER_MODE); |
155 | data32 |= V_PSCR_MDI_XOVER_MODE(crossover); |
156 | (void) simple_mdio_write(cphy, |
157 | MV88E1XXX_SPECIFIC_CNTRL_REGISTER, val: data32); |
158 | return 0; |
159 | } |
160 | |
161 | static int mv88e1xxx_autoneg_enable(struct cphy *cphy) |
162 | { |
163 | u32 ctl; |
164 | |
165 | (void) mv88e1xxx_crossover_set(cphy, CROSSOVER_AUTO); |
166 | |
167 | (void) simple_mdio_read(cphy, MII_BMCR, valp: &ctl); |
168 | /* restart autoneg for change to take effect */ |
169 | ctl |= BMCR_ANENABLE | BMCR_ANRESTART; |
170 | (void) simple_mdio_write(cphy, MII_BMCR, val: ctl); |
171 | return 0; |
172 | } |
173 | |
174 | static int mv88e1xxx_autoneg_disable(struct cphy *cphy) |
175 | { |
176 | u32 ctl; |
177 | |
178 | /* |
179 | * Crossover *must* be set to manual in order to disable auto-neg. |
180 | * The Alaska FAQs document highlights this point. |
181 | */ |
182 | (void) mv88e1xxx_crossover_set(cphy, CROSSOVER_MDI); |
183 | |
184 | /* |
185 | * Must include autoneg reset when disabling auto-neg. This |
186 | * is described in the Alaska FAQ document. |
187 | */ |
188 | (void) simple_mdio_read(cphy, MII_BMCR, valp: &ctl); |
189 | ctl &= ~BMCR_ANENABLE; |
190 | (void) simple_mdio_write(cphy, MII_BMCR, val: ctl | BMCR_ANRESTART); |
191 | return 0; |
192 | } |
193 | |
194 | static int mv88e1xxx_autoneg_restart(struct cphy *cphy) |
195 | { |
196 | mdio_set_bit(cphy, MII_BMCR, BMCR_ANRESTART); |
197 | return 0; |
198 | } |
199 | |
200 | static int mv88e1xxx_advertise(struct cphy *phy, unsigned int advertise_map) |
201 | { |
202 | u32 val = 0; |
203 | |
204 | if (advertise_map & |
205 | (ADVERTISED_1000baseT_Half | ADVERTISED_1000baseT_Full)) { |
206 | (void) simple_mdio_read(cphy: phy, MII_GBCR, valp: &val); |
207 | val &= ~(GBCR_ADV_1000HALF | GBCR_ADV_1000FULL); |
208 | if (advertise_map & ADVERTISED_1000baseT_Half) |
209 | val |= GBCR_ADV_1000HALF; |
210 | if (advertise_map & ADVERTISED_1000baseT_Full) |
211 | val |= GBCR_ADV_1000FULL; |
212 | } |
213 | (void) simple_mdio_write(cphy: phy, MII_GBCR, val); |
214 | |
215 | val = 1; |
216 | if (advertise_map & ADVERTISED_10baseT_Half) |
217 | val |= ADVERTISE_10HALF; |
218 | if (advertise_map & ADVERTISED_10baseT_Full) |
219 | val |= ADVERTISE_10FULL; |
220 | if (advertise_map & ADVERTISED_100baseT_Half) |
221 | val |= ADVERTISE_100HALF; |
222 | if (advertise_map & ADVERTISED_100baseT_Full) |
223 | val |= ADVERTISE_100FULL; |
224 | if (advertise_map & ADVERTISED_PAUSE) |
225 | val |= ADVERTISE_PAUSE; |
226 | if (advertise_map & ADVERTISED_ASYM_PAUSE) |
227 | val |= ADVERTISE_PAUSE_ASYM; |
228 | (void) simple_mdio_write(cphy: phy, MII_ADVERTISE, val); |
229 | return 0; |
230 | } |
231 | |
232 | static int mv88e1xxx_set_loopback(struct cphy *cphy, int on) |
233 | { |
234 | if (on) |
235 | mdio_set_bit(cphy, MII_BMCR, BMCR_LOOPBACK); |
236 | else |
237 | mdio_clear_bit(cphy, MII_BMCR, BMCR_LOOPBACK); |
238 | return 0; |
239 | } |
240 | |
241 | static int mv88e1xxx_get_link_status(struct cphy *cphy, int *link_ok, |
242 | int *speed, int *duplex, int *fc) |
243 | { |
244 | u32 status; |
245 | int sp = -1, dplx = -1, pause = 0; |
246 | |
247 | (void) simple_mdio_read(cphy, |
248 | MV88E1XXX_SPECIFIC_STATUS_REGISTER, valp: &status); |
249 | if ((status & V_PSSR_STATUS_RESOLVED) != 0) { |
250 | if (status & V_PSSR_RX_PAUSE) |
251 | pause |= PAUSE_RX; |
252 | if (status & V_PSSR_TX_PAUSE) |
253 | pause |= PAUSE_TX; |
254 | dplx = (status & V_PSSR_DUPLEX) ? DUPLEX_FULL : DUPLEX_HALF; |
255 | sp = G_PSSR_SPEED(status); |
256 | if (sp == 0) |
257 | sp = SPEED_10; |
258 | else if (sp == 1) |
259 | sp = SPEED_100; |
260 | else |
261 | sp = SPEED_1000; |
262 | } |
263 | if (link_ok) |
264 | *link_ok = (status & V_PSSR_LINK) != 0; |
265 | if (speed) |
266 | *speed = sp; |
267 | if (duplex) |
268 | *duplex = dplx; |
269 | if (fc) |
270 | *fc = pause; |
271 | return 0; |
272 | } |
273 | |
274 | static int mv88e1xxx_downshift_set(struct cphy *cphy, int downshift_enable) |
275 | { |
276 | u32 val; |
277 | |
278 | (void) simple_mdio_read(cphy, |
279 | MV88E1XXX_EXT_PHY_SPECIFIC_CNTRL_REGISTER, valp: &val); |
280 | |
281 | /* |
282 | * Set the downshift counter to 2 so we try to establish Gb link |
283 | * twice before downshifting. |
284 | */ |
285 | val &= ~(V_DOWNSHIFT_ENABLE | V_DOWNSHIFT_CNT(M_DOWNSHIFT_CNT)); |
286 | |
287 | if (downshift_enable) |
288 | val |= V_DOWNSHIFT_ENABLE | V_DOWNSHIFT_CNT(2); |
289 | (void) simple_mdio_write(cphy, |
290 | MV88E1XXX_EXT_PHY_SPECIFIC_CNTRL_REGISTER, val); |
291 | return 0; |
292 | } |
293 | |
294 | static int mv88e1xxx_interrupt_handler(struct cphy *cphy) |
295 | { |
296 | int cphy_cause = 0; |
297 | u32 status; |
298 | |
299 | /* |
300 | * Loop until cause reads zero. Need to handle bouncing interrupts. |
301 | */ |
302 | while (1) { |
303 | u32 cause; |
304 | |
305 | (void) simple_mdio_read(cphy, |
306 | MV88E1XXX_INTERRUPT_STATUS_REGISTER, |
307 | valp: &cause); |
308 | cause &= INTR_ENABLE_MASK; |
309 | if (!cause) |
310 | break; |
311 | |
312 | if (cause & MV88E1XXX_INTR_LINK_CHNG) { |
313 | (void) simple_mdio_read(cphy, |
314 | MV88E1XXX_SPECIFIC_STATUS_REGISTER, valp: &status); |
315 | |
316 | if (status & MV88E1XXX_INTR_LINK_CHNG) |
317 | cphy->state |= PHY_LINK_UP; |
318 | else { |
319 | cphy->state &= ~PHY_LINK_UP; |
320 | if (cphy->state & PHY_AUTONEG_EN) |
321 | cphy->state &= ~PHY_AUTONEG_RDY; |
322 | cphy_cause |= cphy_cause_link_change; |
323 | } |
324 | } |
325 | |
326 | if (cause & MV88E1XXX_INTR_AUTONEG_DONE) |
327 | cphy->state |= PHY_AUTONEG_RDY; |
328 | |
329 | if ((cphy->state & (PHY_LINK_UP | PHY_AUTONEG_RDY)) == |
330 | (PHY_LINK_UP | PHY_AUTONEG_RDY)) |
331 | cphy_cause |= cphy_cause_link_change; |
332 | } |
333 | return cphy_cause; |
334 | } |
335 | |
336 | static void mv88e1xxx_destroy(struct cphy *cphy) |
337 | { |
338 | kfree(objp: cphy); |
339 | } |
340 | |
341 | static const struct cphy_ops mv88e1xxx_ops = { |
342 | .destroy = mv88e1xxx_destroy, |
343 | .reset = mv88e1xxx_reset, |
344 | .interrupt_enable = mv88e1xxx_interrupt_enable, |
345 | .interrupt_disable = mv88e1xxx_interrupt_disable, |
346 | .interrupt_clear = mv88e1xxx_interrupt_clear, |
347 | .interrupt_handler = mv88e1xxx_interrupt_handler, |
348 | .autoneg_enable = mv88e1xxx_autoneg_enable, |
349 | .autoneg_disable = mv88e1xxx_autoneg_disable, |
350 | .autoneg_restart = mv88e1xxx_autoneg_restart, |
351 | .advertise = mv88e1xxx_advertise, |
352 | .set_loopback = mv88e1xxx_set_loopback, |
353 | .set_speed_duplex = mv88e1xxx_set_speed_duplex, |
354 | .get_link_status = mv88e1xxx_get_link_status, |
355 | }; |
356 | |
357 | static struct cphy *mv88e1xxx_phy_create(struct net_device *dev, int phy_addr, |
358 | const struct mdio_ops *mdio_ops) |
359 | { |
360 | struct adapter *adapter = netdev_priv(dev); |
361 | struct cphy *cphy = kzalloc(size: sizeof(*cphy), GFP_KERNEL); |
362 | |
363 | if (!cphy) |
364 | return NULL; |
365 | |
366 | cphy_init(phy: cphy, dev, phy_addr, phy_ops: &mv88e1xxx_ops, mdio_ops); |
367 | |
368 | /* Configure particular PHY's to run in a different mode. */ |
369 | if ((board_info(adapter)->caps & SUPPORTED_TP) && |
370 | board_info(adapter)->chip_phy == CHBT_PHY_88E1111) { |
371 | /* |
372 | * Configure the PHY transmitter as class A to reduce EMI. |
373 | */ |
374 | (void) simple_mdio_write(cphy, |
375 | MV88E1XXX_EXTENDED_ADDR_REGISTER, val: 0xB); |
376 | (void) simple_mdio_write(cphy, |
377 | MV88E1XXX_EXTENDED_REGISTER, val: 0x8004); |
378 | } |
379 | (void) mv88e1xxx_downshift_set(cphy, downshift_enable: 1); /* Enable downshift */ |
380 | |
381 | /* LED */ |
382 | if (is_T2(adapter)) { |
383 | (void) simple_mdio_write(cphy, |
384 | MV88E1XXX_LED_CONTROL_REGISTER, val: 0x1); |
385 | } |
386 | |
387 | return cphy; |
388 | } |
389 | |
390 | static int mv88e1xxx_phy_reset(adapter_t* adapter) |
391 | { |
392 | return 0; |
393 | } |
394 | |
395 | const struct gphy t1_mv88e1xxx_ops = { |
396 | .create = mv88e1xxx_phy_create, |
397 | .reset = mv88e1xxx_phy_reset |
398 | }; |
399 | |