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