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