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