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
88static int thunder_mdio_detach(device_t);
89
90static int thunder_mdio_read(device_t, int, int);
91static int thunder_mdio_write(device_t, int, int, int);
92
93static int thunder_ifmedia_change_stub(if_t);
94static void thunder_ifmedia_status_stub(if_t, struct ifmediareq *);
95
96static int thunder_mdio_media_status(device_t, int, int *, int *, int *);
97static int thunder_mdio_media_change(device_t, int, int, int, int);
98static int thunder_mdio_phy_connect(device_t, int, int);
99static int thunder_mdio_phy_disconnect(device_t, int, int);
100
101static 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
117DEFINE_CLASS_0(thunder_mdio, thunder_mdio_driver, thunder_mdio_methods,
118    sizeof(struct thunder_mdio_softc));
119
120DRIVER_MODULE(miibus, thunder_mdio, miibus_driver, 0, 0);
121MODULE_VERSION(thunder_mdio, 1);
122MODULE_DEPEND(thunder_mdio, ether, 1, 1, 1);
123MODULE_DEPEND(thunder_mdio, miibus, 1, 1, 1);
124MODULE_DEPEND(thunder_mdio, mrmlbus, 1, 1, 1);
125
126MALLOC_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
147int
148thunder_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
174static int
175thunder_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
189static __inline void
190thunder_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
211static int
212thunder_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
251static int
252thunder_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
303static int
304thunder_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
350static int
351thunder_ifmedia_change_stub(if_t ifp __unused)
352{
353	/* Will never be called by if_media */
354	return (0);
355}
356
357static void
358thunder_ifmedia_status_stub(if_t ifp __unused, struct ifmediareq
359    *ifmr __unused)
360{
361	/* Will never be called by if_media */
362}
363
364static __inline struct phy_desc *
365get_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}
377static int
378thunder_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
431static int
432thunder_mdio_media_change(device_t dev, int lmacid, int link, int duplex,
433    int speed)
434{
435
436	return (EIO);
437}
438
439static int
440thunder_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		if (pd->ifp == NULL) {
457			free(pd, M_THUNDER_MDIO);
458			return (ENOMEM);
459		}
460		pd->lmacid = lmacid;
461	}
462
463	err = mii_attach(dev, &pd->miibus, pd->ifp,
464	    thunder_ifmedia_change_stub, thunder_ifmedia_status_stub,
465	    BMSR_DEFCAPMASK, phy, MII_OFFSET_ANY, 0);
466
467	if (err != 0) {
468		device_printf(dev, "Could not attach PHY%d\n", phy);
469		if_free(pd->ifp);
470		free(pd, M_THUNDER_MDIO);
471		return (ENXIO);
472	}
473
474	MDIO_LOCK(sc);
475	TAILQ_INSERT_TAIL(&sc->phy_desc_head, pd, phy_desc_list);
476	MDIO_UNLOCK(sc);
477
478	return (0);
479}
480
481static int
482thunder_mdio_phy_disconnect(device_t dev, int lmacid, int phy)
483{
484	struct thunder_mdio_softc *sc;
485	struct phy_desc *pd;
486
487	sc = device_get_softc(dev);
488	MDIO_LOCK(sc);
489
490	pd = get_phy_desc(sc, lmacid);
491	if (pd == NULL) {
492		MDIO_UNLOCK(sc);
493		return (EINVAL);
494	}
495
496	/* Remove this PHY descriptor from the list */
497	TAILQ_REMOVE(&sc->phy_desc_head, pd, phy_desc_list);
498
499	/* Detach miibus */
500	bus_generic_detach(dev);
501	device_delete_child(dev, pd->miibus);
502	/* Free fake ifnet */
503	if_free(pd->ifp);
504	/* Free memory under phy descriptor */
505	free(pd, M_THUNDER_MDIO);
506	MDIO_UNLOCK(sc);
507
508	return (0);
509}
510