1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /***************************************************************************** |
3 | * * |
4 | * File: mv88x201x.c * |
5 | * $Revision: 1.12 $ * |
6 | * $Date: 2005/04/15 19:27:14 $ * |
7 | * Description: * |
8 | * Marvell PHY (mv88x201x) functionality. * |
9 | * part of the Chelsio 10Gb Ethernet Driver. * |
10 | * * |
11 | * * |
12 | * http://www.chelsio.com * |
13 | * * |
14 | * Copyright (c) 2003 - 2005 Chelsio Communications, Inc. * |
15 | * All rights reserved. * |
16 | * * |
17 | * Maintainers: maintainers@chelsio.com * |
18 | * * |
19 | * Authors: Dimitrios Michailidis <dm@chelsio.com> * |
20 | * Tina Yang <tainay@chelsio.com> * |
21 | * Felix Marti <felix@chelsio.com> * |
22 | * Scott Bardone <sbardone@chelsio.com> * |
23 | * Kurt Ottaway <kottaway@chelsio.com> * |
24 | * Frank DiMambro <frank@chelsio.com> * |
25 | * * |
26 | * History: * |
27 | * * |
28 | ****************************************************************************/ |
29 | |
30 | #include "cphy.h" |
31 | #include "elmer0.h" |
32 | |
33 | /* |
34 | * The 88x2010 Rev C. requires some link status registers * to be read |
35 | * twice in order to get the right values. Future * revisions will fix |
36 | * this problem and then this macro * can disappear. |
37 | */ |
38 | #define MV88x2010_LINK_STATUS_BUGS 1 |
39 | |
40 | static int led_init(struct cphy *cphy) |
41 | { |
42 | /* Setup the LED registers so we can turn on/off. |
43 | * Writing these bits maps control to another |
44 | * register. mmd(0x1) addr(0x7) |
45 | */ |
46 | cphy_mdio_write(cphy, MDIO_MMD_PCS, reg: 0x8304, val: 0xdddd); |
47 | return 0; |
48 | } |
49 | |
50 | static int led_link(struct cphy *cphy, u32 do_enable) |
51 | { |
52 | u32 led = 0; |
53 | #define LINK_ENABLE_BIT 0x1 |
54 | |
55 | cphy_mdio_read(cphy, MDIO_MMD_PMAPMD, MDIO_CTRL2, valp: &led); |
56 | |
57 | if (do_enable & LINK_ENABLE_BIT) { |
58 | led |= LINK_ENABLE_BIT; |
59 | cphy_mdio_write(cphy, MDIO_MMD_PMAPMD, MDIO_CTRL2, val: led); |
60 | } else { |
61 | led &= ~LINK_ENABLE_BIT; |
62 | cphy_mdio_write(cphy, MDIO_MMD_PMAPMD, MDIO_CTRL2, val: led); |
63 | } |
64 | return 0; |
65 | } |
66 | |
67 | /* Port Reset */ |
68 | static int mv88x201x_reset(struct cphy *cphy, int wait) |
69 | { |
70 | /* This can be done through registers. It is not required since |
71 | * a full chip reset is used. |
72 | */ |
73 | return 0; |
74 | } |
75 | |
76 | static int mv88x201x_interrupt_enable(struct cphy *cphy) |
77 | { |
78 | /* Enable PHY LASI interrupts. */ |
79 | cphy_mdio_write(cphy, MDIO_MMD_PMAPMD, MDIO_PMA_LASI_CTRL, |
80 | MDIO_PMA_LASI_LSALARM); |
81 | |
82 | /* Enable Marvell interrupts through Elmer0. */ |
83 | if (t1_is_asic(adapter: cphy->adapter)) { |
84 | u32 elmer; |
85 | |
86 | t1_tpi_read(adapter: cphy->adapter, A_ELMER0_INT_ENABLE, value: &elmer); |
87 | elmer |= ELMER0_GP_BIT6; |
88 | t1_tpi_write(adapter: cphy->adapter, A_ELMER0_INT_ENABLE, value: elmer); |
89 | } |
90 | return 0; |
91 | } |
92 | |
93 | static int mv88x201x_interrupt_disable(struct cphy *cphy) |
94 | { |
95 | /* Disable PHY LASI interrupts. */ |
96 | cphy_mdio_write(cphy, MDIO_MMD_PMAPMD, MDIO_PMA_LASI_CTRL, val: 0x0); |
97 | |
98 | /* Disable Marvell interrupts through Elmer0. */ |
99 | if (t1_is_asic(adapter: cphy->adapter)) { |
100 | u32 elmer; |
101 | |
102 | t1_tpi_read(adapter: cphy->adapter, A_ELMER0_INT_ENABLE, value: &elmer); |
103 | elmer &= ~ELMER0_GP_BIT6; |
104 | t1_tpi_write(adapter: cphy->adapter, A_ELMER0_INT_ENABLE, value: elmer); |
105 | } |
106 | return 0; |
107 | } |
108 | |
109 | static int mv88x201x_interrupt_clear(struct cphy *cphy) |
110 | { |
111 | u32 elmer; |
112 | u32 val; |
113 | |
114 | #ifdef MV88x2010_LINK_STATUS_BUGS |
115 | /* Required to read twice before clear takes affect. */ |
116 | cphy_mdio_read(cphy, MDIO_MMD_PMAPMD, MDIO_PMA_LASI_RXSTAT, valp: &val); |
117 | cphy_mdio_read(cphy, MDIO_MMD_PMAPMD, MDIO_PMA_LASI_TXSTAT, valp: &val); |
118 | cphy_mdio_read(cphy, MDIO_MMD_PMAPMD, MDIO_PMA_LASI_STAT, valp: &val); |
119 | |
120 | /* Read this register after the others above it else |
121 | * the register doesn't clear correctly. |
122 | */ |
123 | cphy_mdio_read(cphy, MDIO_MMD_PMAPMD, MDIO_STAT1, valp: &val); |
124 | #endif |
125 | |
126 | /* Clear link status. */ |
127 | cphy_mdio_read(cphy, MDIO_MMD_PMAPMD, MDIO_STAT1, valp: &val); |
128 | /* Clear PHY LASI interrupts. */ |
129 | cphy_mdio_read(cphy, MDIO_MMD_PMAPMD, MDIO_PMA_LASI_STAT, valp: &val); |
130 | |
131 | #ifdef MV88x2010_LINK_STATUS_BUGS |
132 | /* Do it again. */ |
133 | cphy_mdio_read(cphy, MDIO_MMD_PMAPMD, MDIO_PMA_LASI_RXSTAT, valp: &val); |
134 | cphy_mdio_read(cphy, MDIO_MMD_PMAPMD, MDIO_PMA_LASI_TXSTAT, valp: &val); |
135 | #endif |
136 | |
137 | /* Clear Marvell interrupts through Elmer0. */ |
138 | if (t1_is_asic(adapter: cphy->adapter)) { |
139 | t1_tpi_read(adapter: cphy->adapter, A_ELMER0_INT_CAUSE, value: &elmer); |
140 | elmer |= ELMER0_GP_BIT6; |
141 | t1_tpi_write(adapter: cphy->adapter, A_ELMER0_INT_CAUSE, value: elmer); |
142 | } |
143 | return 0; |
144 | } |
145 | |
146 | static int mv88x201x_interrupt_handler(struct cphy *cphy) |
147 | { |
148 | /* Clear interrupts */ |
149 | mv88x201x_interrupt_clear(cphy); |
150 | |
151 | /* We have only enabled link change interrupts and so |
152 | * cphy_cause must be a link change interrupt. |
153 | */ |
154 | return cphy_cause_link_change; |
155 | } |
156 | |
157 | static int mv88x201x_set_loopback(struct cphy *cphy, int on) |
158 | { |
159 | return 0; |
160 | } |
161 | |
162 | static int mv88x201x_get_link_status(struct cphy *cphy, int *link_ok, |
163 | int *speed, int *duplex, int *fc) |
164 | { |
165 | u32 val = 0; |
166 | |
167 | if (link_ok) { |
168 | /* Read link status. */ |
169 | cphy_mdio_read(cphy, MDIO_MMD_PMAPMD, MDIO_STAT1, valp: &val); |
170 | val &= MDIO_STAT1_LSTATUS; |
171 | *link_ok = (val == MDIO_STAT1_LSTATUS); |
172 | /* Turn on/off Link LED */ |
173 | led_link(cphy, do_enable: *link_ok); |
174 | } |
175 | if (speed) |
176 | *speed = SPEED_10000; |
177 | if (duplex) |
178 | *duplex = DUPLEX_FULL; |
179 | if (fc) |
180 | *fc = PAUSE_RX | PAUSE_TX; |
181 | return 0; |
182 | } |
183 | |
184 | static void mv88x201x_destroy(struct cphy *cphy) |
185 | { |
186 | kfree(objp: cphy); |
187 | } |
188 | |
189 | static const struct cphy_ops mv88x201x_ops = { |
190 | .destroy = mv88x201x_destroy, |
191 | .reset = mv88x201x_reset, |
192 | .interrupt_enable = mv88x201x_interrupt_enable, |
193 | .interrupt_disable = mv88x201x_interrupt_disable, |
194 | .interrupt_clear = mv88x201x_interrupt_clear, |
195 | .interrupt_handler = mv88x201x_interrupt_handler, |
196 | .get_link_status = mv88x201x_get_link_status, |
197 | .set_loopback = mv88x201x_set_loopback, |
198 | .mmds = (MDIO_DEVS_PMAPMD | MDIO_DEVS_PCS | |
199 | MDIO_DEVS_PHYXS | MDIO_DEVS_WIS), |
200 | }; |
201 | |
202 | static struct cphy *mv88x201x_phy_create(struct net_device *dev, int phy_addr, |
203 | const struct mdio_ops *mdio_ops) |
204 | { |
205 | u32 val; |
206 | struct cphy *cphy = kzalloc(size: sizeof(*cphy), GFP_KERNEL); |
207 | |
208 | if (!cphy) |
209 | return NULL; |
210 | |
211 | cphy_init(phy: cphy, dev, phy_addr, phy_ops: &mv88x201x_ops, mdio_ops); |
212 | |
213 | /* Commands the PHY to enable XFP's clock. */ |
214 | cphy_mdio_read(cphy, MDIO_MMD_PCS, reg: 0x8300, valp: &val); |
215 | cphy_mdio_write(cphy, MDIO_MMD_PCS, reg: 0x8300, val: val | 1); |
216 | |
217 | /* Clear link status. Required because of a bug in the PHY. */ |
218 | cphy_mdio_read(cphy, MDIO_MMD_PMAPMD, MDIO_STAT2, valp: &val); |
219 | cphy_mdio_read(cphy, MDIO_MMD_PCS, MDIO_STAT2, valp: &val); |
220 | |
221 | /* Allows for Link,Ack LED turn on/off */ |
222 | led_init(cphy); |
223 | return cphy; |
224 | } |
225 | |
226 | /* Chip Reset */ |
227 | static int mv88x201x_phy_reset(adapter_t *adapter) |
228 | { |
229 | u32 val; |
230 | |
231 | t1_tpi_read(adapter, A_ELMER0_GPO, value: &val); |
232 | val &= ~4; |
233 | t1_tpi_write(adapter, A_ELMER0_GPO, value: val); |
234 | msleep(msecs: 100); |
235 | |
236 | t1_tpi_write(adapter, A_ELMER0_GPO, value: val | 4); |
237 | msleep(msecs: 1000); |
238 | |
239 | /* Now lets enable the Laser. Delay 100us */ |
240 | t1_tpi_read(adapter, A_ELMER0_GPO, value: &val); |
241 | val |= 0x8000; |
242 | t1_tpi_write(adapter, A_ELMER0_GPO, value: val); |
243 | udelay(100); |
244 | return 0; |
245 | } |
246 | |
247 | const struct gphy t1_mv88x201x_ops = { |
248 | .create = mv88x201x_phy_create, |
249 | .reset = mv88x201x_phy_reset |
250 | }; |
251 | |