1 /*-
2 * Copyright (c) 2015 The FreeBSD Foundation
3 *
4 * This software was developed by Semihalf under
5 * the sponsorship of the FreeBSD Foundation.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29 #include <sys/cdefs.h>
30 #include <sys/param.h>
31 #include <sys/systm.h>
32 #include <sys/bus.h>
33 #include <sys/kernel.h>
34 #include <sys/module.h>
35 #include <sys/resource.h>
36 #include <sys/rman.h>
37 #include <sys/socket.h>
38 #include <sys/queue.h>
39
40 #include <machine/bus.h>
41 #include <machine/resource.h>
42
43 #include <net/if.h>
44 #include <net/if_media.h>
45 #include <net/if_types.h>
46 #include <net/if_var.h>
47
48 #include <dev/mii/mii.h>
49 #include <dev/mii/miivar.h>
50
51 #include "thunder_mdio_var.h"
52
53 #include "lmac_if.h"
54 #include "miibus_if.h"
55
56 #define REG_BASE_RID 0
57
58 #define SMI_CMD 0x00
59 #define SMI_CMD_PHY_REG_ADR_SHIFT (0)
60 #define SMI_CMD_PHY_REG_ADR_MASK (0x1FUL << SMI_CMD_PHY_REG_ADR_SHIFT)
61 #define SMI_CMD_PHY_ADR_SHIFT (8)
62 #define SMI_CMD_PHY_ADR_MASK (0x1FUL << SMI_CMD_PHY_ADR_SHIFT)
63 #define SMI_CMD_PHY_OP_MASK (0x3UL << 16)
64 #define SMI_CMD_PHY_OP_C22_READ (0x1UL << 16)
65 #define SMI_CMD_PHY_OP_C22_WRITE (0x0UL << 16)
66 #define SMI_CMD_PHY_OP_C45_READ (0x3UL << 16)
67 #define SMI_CMD_PHY_OP_C45_WRITE (0x1UL << 16)
68 #define SMI_CMD_PHY_OP_C45_ADDR (0x0UL << 16)
69
70 #define SMI_WR_DAT 0x08
71 #define SMI_WR_DAT_PENDING (1UL << 17)
72 #define SMI_WR_DAT_VAL (1UL << 16)
73 #define SMI_WR_DAT_DAT_MASK (0xFFFFUL << 0)
74
75 #define SMI_RD_DAT 0x10
76 #define SMI_RD_DAT_PENDING (1UL << 17)
77 #define SMI_RD_DAT_VAL (1UL << 16)
78 #define SMI_RD_DAT_DAT_MASK (0xFFFFUL << 0)
79
80 #define SMI_CLK 0x18
81 #define SMI_CLK_PREAMBLE (1UL << 12)
82 #define SMI_CLK_MODE (1UL << 24)
83
84 #define SMI_EN 0x20
85 #define SMI_EN_EN (1UL << 0) /* Enable interface */
86
87 #define SMI_DRV_CTL 0x28
88
89 static int thunder_mdio_detach(device_t);
90
91 static int thunder_mdio_read(device_t, int, int);
92 static int thunder_mdio_write(device_t, int, int, int);
93
94 static int thunder_ifmedia_change_stub(struct ifnet *);
95 static void thunder_ifmedia_status_stub(struct ifnet *, struct ifmediareq *);
96
97 static int thunder_mdio_media_status(device_t, int, int *, int *, int *);
98 static int thunder_mdio_media_change(device_t, int, int, int, int);
99 static int thunder_mdio_phy_connect(device_t, int, int);
100 static int thunder_mdio_phy_disconnect(device_t, int, int);
101
102 static device_method_t thunder_mdio_methods[] = {
103 /* Device interface */
104 DEVMETHOD(device_detach, thunder_mdio_detach),
105 /* LMAC interface */
106 DEVMETHOD(lmac_media_status, thunder_mdio_media_status),
107 DEVMETHOD(lmac_media_change, thunder_mdio_media_change),
108 DEVMETHOD(lmac_phy_connect, thunder_mdio_phy_connect),
109 DEVMETHOD(lmac_phy_disconnect, thunder_mdio_phy_disconnect),
110 /* MII interface */
111 DEVMETHOD(miibus_readreg, thunder_mdio_read),
112 DEVMETHOD(miibus_writereg, thunder_mdio_write),
113
114 /* End */
115 DEVMETHOD_END
116 };
117
118 DEFINE_CLASS_0(thunder_mdio, thunder_mdio_driver, thunder_mdio_methods,
119 sizeof(struct thunder_mdio_softc));
120
121 DRIVER_MODULE(miibus, thunder_mdio, miibus_driver, miibus_devclass, 0, 0);
122 MODULE_VERSION(thunder_mdio, 1);
123 MODULE_DEPEND(thunder_mdio, ether, 1, 1, 1);
124 MODULE_DEPEND(thunder_mdio, miibus, 1, 1, 1);
125 MODULE_DEPEND(thunder_mdio, mrmlbus, 1, 1, 1);
126
127 MALLOC_DEFINE(M_THUNDER_MDIO, "ThunderX MDIO",
128 "Cavium ThunderX MDIO dynamic memory");
129
130 #define MDIO_LOCK_INIT(sc, name) \
131 mtx_init(&(sc)->mtx, name, NULL, MTX_DEF)
132
133 #define MDIO_LOCK_DESTROY(sc) \
134 mtx_destroy(&(sc)->mtx)
135
136 #define MDIO_LOCK(sc) mtx_lock(&(sc)->mtx)
137 #define MDIO_UNLOCK(sc) mtx_unlock(&(sc)->mtx)
138
139 #define MDIO_LOCK_ASSERT(sc) \
140 mtx_assert(&(sc)->mtx, MA_OWNED)
141
142 #define mdio_reg_read(sc, reg) \
143 bus_read_8((sc)->reg_base, (reg))
144
145 #define mdio_reg_write(sc, reg, val) \
146 bus_write_8((sc)->reg_base, (reg), (val))
147
148 int
thunder_mdio_attach(device_t dev)149 thunder_mdio_attach(device_t dev)
150 {
151 struct thunder_mdio_softc *sc;
152 int rid;
153
154 sc = device_get_softc(dev);
155 sc->dev = dev;
156
157 /* Allocate memory resources */
158 rid = REG_BASE_RID;
159 sc->reg_base = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
160 RF_ACTIVE);
161 if (sc->reg_base == NULL) {
162 device_printf(dev, "Could not allocate memory\n");
163 return (ENXIO);
164 }
165
166 TAILQ_INIT(&sc->phy_desc_head);
167 MDIO_LOCK_INIT(sc, "ThunderX MDIO lock");
168
169 /* Enable SMI/MDIO interface */
170 mdio_reg_write(sc, SMI_EN, SMI_EN_EN);
171
172 return (0);
173 }
174
175 static int
thunder_mdio_detach(device_t dev)176 thunder_mdio_detach(device_t dev)
177 {
178 struct thunder_mdio_softc *sc;
179
180 sc = device_get_softc(dev);
181
182 if (sc->reg_base != NULL) {
183 bus_release_resource(dev, SYS_RES_MEMORY, REG_BASE_RID,
184 sc->reg_base);
185 }
186
187 return (0);
188 }
189
190 static __inline void
thunder_mdio_set_mode(struct thunder_mdio_softc * sc,enum thunder_mdio_mode mode)191 thunder_mdio_set_mode(struct thunder_mdio_softc *sc,
192 enum thunder_mdio_mode mode)
193 {
194 uint64_t smi_clk;
195
196 if (sc->mode == mode)
197 return;
198
199 /* Set mode, IEEE CLAUSE 22 or IEEE CAUSE 45 */
200 smi_clk = mdio_reg_read(sc, SMI_CLK);
201 if (mode == MODE_IEEE_C22)
202 smi_clk &= ~SMI_CLK_MODE;
203 else
204 smi_clk |= SMI_CLK_MODE;
205 /* Enable sending 32 bit preable on SMI transactions */
206 smi_clk |= SMI_CLK_PREAMBLE;
207 /* Saved settings */
208 mdio_reg_write(sc, SMI_CLK, smi_clk);
209 sc->mode = mode;
210 }
211
212 static int
thunder_mdio_c45_addr(struct thunder_mdio_softc * sc,int phy,int reg)213 thunder_mdio_c45_addr(struct thunder_mdio_softc *sc, int phy, int reg)
214 {
215 uint64_t smi_cmd, smi_wr_dat;
216 ssize_t timeout;
217
218 thunder_mdio_set_mode(sc, MODE_IEEE_C45);
219
220 /* Prepare data for transmission */
221 mdio_reg_write(sc, SMI_WR_DAT, reg & SMI_WR_DAT_DAT_MASK);
222 /*
223 * Assemble command
224 */
225 smi_cmd = 0;
226 /* Set opcode */
227 smi_cmd |= SMI_CMD_PHY_OP_C45_WRITE;
228
229 /* Set PHY address */
230 smi_cmd |= ((phy << SMI_CMD_PHY_ADR_SHIFT) & SMI_CMD_PHY_ADR_MASK);
231 /* Set PHY register offset */
232 smi_cmd |= ((reg << SMI_CMD_PHY_REG_ADR_SHIFT) &
233 SMI_CMD_PHY_REG_ADR_MASK);
234
235 mdio_reg_write(sc, SMI_CMD, smi_cmd);
236 for (timeout = 1000; timeout > 0; timeout--) {
237 smi_wr_dat = mdio_reg_read(sc, SMI_WR_DAT);
238 if (smi_wr_dat & SMI_WR_DAT_PENDING)
239 DELAY(1000);
240 else
241 break;
242 }
243
244 if (timeout <= 0)
245 return (EIO);
246 else {
247 /* Return 0 on success */
248 return (0);
249 }
250 }
251
252 static int
thunder_mdio_read(device_t dev,int phy,int reg)253 thunder_mdio_read(device_t dev, int phy, int reg)
254 {
255 struct thunder_mdio_softc *sc;
256 uint64_t smi_cmd, smi_rd_dat;
257 ssize_t timeout;
258 int err;
259
260 sc = device_get_softc(dev);
261
262 /* XXX Always C22 - for <= 1Gbps only */
263 thunder_mdio_set_mode(sc, MODE_IEEE_C22);
264
265 /*
266 * Assemble command
267 */
268 smi_cmd = 0;
269 /* Set opcode */
270 if (sc->mode == MODE_IEEE_C22)
271 smi_cmd |= SMI_CMD_PHY_OP_C22_READ;
272 else {
273 smi_cmd |= SMI_CMD_PHY_OP_C45_READ;
274 err = thunder_mdio_c45_addr(sc, phy, reg);
275 if (err != 0)
276 return (err);
277
278 reg = (reg >> 16) & 0x1F;
279 }
280
281 /* Set PHY address */
282 smi_cmd |= ((phy << SMI_CMD_PHY_ADR_SHIFT) & SMI_CMD_PHY_ADR_MASK);
283 /* Set PHY register offset */
284 smi_cmd |= ((reg << SMI_CMD_PHY_REG_ADR_SHIFT) &
285 SMI_CMD_PHY_REG_ADR_MASK);
286
287 mdio_reg_write(sc, SMI_CMD, smi_cmd);
288 for (timeout = 1000; timeout > 0; timeout--) {
289 smi_rd_dat = mdio_reg_read(sc, SMI_RD_DAT);
290 if (smi_rd_dat & SMI_RD_DAT_PENDING)
291 DELAY(1000);
292 else
293 break;
294 }
295
296 if (smi_rd_dat & SMI_RD_DAT_VAL)
297 return (smi_rd_dat & SMI_RD_DAT_DAT_MASK);
298 else {
299 /* Return 0 on error */
300 return (0);
301 }
302 }
303
304 static int
thunder_mdio_write(device_t dev,int phy,int reg,int data)305 thunder_mdio_write(device_t dev, int phy, int reg, int data)
306 {
307 struct thunder_mdio_softc *sc;
308 uint64_t smi_cmd, smi_wr_dat;
309 ssize_t timeout;
310
311 sc = device_get_softc(dev);
312
313 /* XXX Always C22 - for <= 1Gbps only */
314 thunder_mdio_set_mode(sc, MODE_IEEE_C22);
315
316 /* Prepare data for transmission */
317 mdio_reg_write(sc, SMI_WR_DAT, data & SMI_WR_DAT_DAT_MASK);
318 /*
319 * Assemble command
320 */
321 smi_cmd = 0;
322 /* Set opcode */
323 if (sc->mode == MODE_IEEE_C22)
324 smi_cmd |= SMI_CMD_PHY_OP_C22_WRITE;
325 else
326 smi_cmd |= SMI_CMD_PHY_OP_C45_WRITE;
327
328 /* Set PHY address */
329 smi_cmd |= ((phy << SMI_CMD_PHY_ADR_SHIFT) & SMI_CMD_PHY_ADR_MASK);
330 /* Set PHY register offset */
331 smi_cmd |= ((reg << SMI_CMD_PHY_REG_ADR_SHIFT) &
332 SMI_CMD_PHY_REG_ADR_MASK);
333
334 mdio_reg_write(sc, SMI_CMD, smi_cmd);
335 for (timeout = 1000; timeout > 0; timeout--) {
336 smi_wr_dat = mdio_reg_read(sc, SMI_WR_DAT);
337 if (smi_wr_dat & SMI_WR_DAT_PENDING)
338 DELAY(1000);
339 else
340 break;
341 }
342
343 if (timeout <= 0)
344 return (EIO);
345 else {
346 /* Return 0 on success */
347 return (0);
348 }
349 }
350
351 static int
thunder_ifmedia_change_stub(struct ifnet * ifp __unused)352 thunder_ifmedia_change_stub(struct ifnet *ifp __unused)
353 {
354 /* Will never be called by if_media */
355 return (0);
356 }
357
358 static void
thunder_ifmedia_status_stub(struct ifnet * ifp __unused,struct ifmediareq * ifmr __unused)359 thunder_ifmedia_status_stub(struct ifnet *ifp __unused, struct ifmediareq
360 *ifmr __unused)
361 {
362 /* Will never be called by if_media */
363 }
364
365 static __inline struct phy_desc *
get_phy_desc(struct thunder_mdio_softc * sc,int lmacid)366 get_phy_desc(struct thunder_mdio_softc *sc, int lmacid)
367 {
368 struct phy_desc *pd = NULL;
369
370 MDIO_LOCK_ASSERT(sc);
371 TAILQ_FOREACH(pd, &sc->phy_desc_head, phy_desc_list) {
372 if (pd->lmacid == lmacid)
373 break;
374 }
375
376 return (pd);
377 }
378 static int
thunder_mdio_media_status(device_t dev,int lmacid,int * link,int * duplex,int * speed)379 thunder_mdio_media_status(device_t dev, int lmacid, int *link, int *duplex,
380 int *speed)
381 {
382 struct thunder_mdio_softc *sc;
383 struct mii_data *mii_sc;
384 struct phy_desc *pd;
385
386 sc = device_get_softc(dev);
387
388 MDIO_LOCK(sc);
389 pd = get_phy_desc(sc, lmacid);
390 if (pd == NULL) {
391 /* Panic when invariants are enabled, fail otherwise. */
392 KASSERT(0, ("%s: no PHY descriptor for LMAC%d",
393 __func__, lmacid));
394 MDIO_UNLOCK(sc);
395 return (ENXIO);
396 }
397 mii_sc = device_get_softc(pd->miibus);
398
399 mii_tick(mii_sc);
400 if ((mii_sc->mii_media_status & (IFM_ACTIVE | IFM_AVALID)) ==
401 (IFM_ACTIVE | IFM_AVALID)) {
402 /* Link is up */
403 *link = 1;
404 } else
405 *link = 0;
406
407 switch (IFM_SUBTYPE(mii_sc->mii_media_active)) {
408 case IFM_10_T:
409 *speed = 10;
410 break;
411 case IFM_100_TX:
412 *speed = 100;
413 break;
414 case IFM_1000_T:
415 *speed = 1000;
416 break;
417 default:
418 /* IFM_NONE */
419 *speed = 0;
420 }
421
422 if ((IFM_OPTIONS(mii_sc->mii_media_active) & IFM_FDX) != 0)
423 *duplex = 1;
424 else
425 *duplex = 0;
426
427 MDIO_UNLOCK(sc);
428
429 return (0);
430 }
431
432 static int
thunder_mdio_media_change(device_t dev,int lmacid,int link,int duplex,int speed)433 thunder_mdio_media_change(device_t dev, int lmacid, int link, int duplex,
434 int speed)
435 {
436
437 return (EIO);
438 }
439
440 static int
thunder_mdio_phy_connect(device_t dev,int lmacid,int phy)441 thunder_mdio_phy_connect(device_t dev, int lmacid, int phy)
442 {
443 struct thunder_mdio_softc *sc;
444 struct phy_desc *pd;
445 int err;
446
447 sc = device_get_softc(dev);
448
449 MDIO_LOCK(sc);
450 pd = get_phy_desc(sc, lmacid);
451 MDIO_UNLOCK(sc);
452 if (pd == NULL) {
453 pd = malloc(sizeof(*pd), M_THUNDER_MDIO, (M_NOWAIT | M_ZERO));
454 if (pd == NULL)
455 return (ENOMEM);
456 pd->ifp = if_alloc(IFT_ETHER);
457 pd->lmacid = lmacid;
458 }
459
460 err = mii_attach(dev, &pd->miibus, pd->ifp,
461 thunder_ifmedia_change_stub, thunder_ifmedia_status_stub,
462 BMSR_DEFCAPMASK, phy, MII_OFFSET_ANY, 0);
463
464 if (err != 0) {
465 device_printf(dev, "Could not attach PHY%d\n", phy);
466 if_free(pd->ifp);
467 free(pd, M_THUNDER_MDIO);
468 return (ENXIO);
469 }
470
471 MDIO_LOCK(sc);
472 TAILQ_INSERT_TAIL(&sc->phy_desc_head, pd, phy_desc_list);
473 MDIO_UNLOCK(sc);
474
475 return (0);
476 }
477
478 static int
thunder_mdio_phy_disconnect(device_t dev,int lmacid,int phy)479 thunder_mdio_phy_disconnect(device_t dev, int lmacid, int phy)
480 {
481 struct thunder_mdio_softc *sc;
482 struct phy_desc *pd;
483
484 sc = device_get_softc(dev);
485 MDIO_LOCK(sc);
486
487 pd = get_phy_desc(sc, lmacid);
488 if (pd == NULL) {
489 MDIO_UNLOCK(sc);
490 return (EINVAL);
491 }
492
493 /* Remove this PHY descriptor from the list */
494 TAILQ_REMOVE(&sc->phy_desc_head, pd, phy_desc_list);
495
496 /* Detach miibus */
497 bus_generic_detach(dev);
498 device_delete_child(dev, pd->miibus);
499 /* Free fake ifnet */
500 if_free(pd->ifp);
501 /* Free memory under phy descriptor */
502 free(pd, M_THUNDER_MDIO);
503 MDIO_UNLOCK(sc);
504
505 return (0);
506 }
507