1213762Sjmallett/*-
2213762Sjmallett * Copyright (c) 2010 Juli Mallett <jmallett@FreeBSD.org>
3213762Sjmallett * All rights reserved.
4213762Sjmallett *
5213762Sjmallett * Redistribution and use in source and binary forms, with or without
6213762Sjmallett * modification, are permitted provided that the following conditions
7213762Sjmallett * are met:
8213762Sjmallett * 1. Redistributions of source code must retain the above copyright
9213762Sjmallett *    notice, this list of conditions and the following disclaimer.
10213762Sjmallett * 2. Redistributions in binary form must reproduce the above copyright
11213762Sjmallett *    notice, this list of conditions and the following disclaimer in the
12213762Sjmallett *    documentation and/or other materials provided with the distribution.
13213762Sjmallett *
14213762Sjmallett * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15213762Sjmallett * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16213762Sjmallett * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17213762Sjmallett * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18213762Sjmallett * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19213762Sjmallett * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20213762Sjmallett * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21213762Sjmallett * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22213762Sjmallett * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23213762Sjmallett * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24213762Sjmallett * SUCH DAMAGE.
25213762Sjmallett *
26213762Sjmallett * $FreeBSD$
27213762Sjmallett */
28213762Sjmallett
29213762Sjmallett#include <sys/cdefs.h>
30213762Sjmallett__FBSDID("$FreeBSD$");
31213762Sjmallett
32213762Sjmallett/*
33213762Sjmallett * Driver for the Marvell 88E61xx family of switch PHYs
34213762Sjmallett */
35213762Sjmallett
36213762Sjmallett#include <sys/param.h>
37213762Sjmallett#include <sys/systm.h>
38213762Sjmallett#include <sys/kernel.h>
39213762Sjmallett#include <sys/socket.h>
40213762Sjmallett#include <sys/errno.h>
41213762Sjmallett#include <sys/module.h>
42213762Sjmallett#include <sys/bus.h>
43213762Sjmallett#include <sys/sysctl.h>
44213762Sjmallett
45213762Sjmallett#include <net/ethernet.h>
46213762Sjmallett#include <net/if.h>
47213762Sjmallett#include <net/if_media.h>
48213762Sjmallett
49213762Sjmallett#include "miibus_if.h"
50213762Sjmallett
51213762Sjmallett#include "mv88e61xxphyreg.h"
52213762Sjmallett
53213762Sjmallettstruct mv88e61xxphy_softc;
54213762Sjmallett
55213762Sjmallettstruct mv88e61xxphy_port_softc {
56213762Sjmallett	struct mv88e61xxphy_softc *sc_switch;
57213762Sjmallett	unsigned sc_port;
58213762Sjmallett	unsigned sc_domain;
59213762Sjmallett	unsigned sc_vlan;
60213762Sjmallett	unsigned sc_priority;
61213762Sjmallett	unsigned sc_flags;
62213762Sjmallett};
63213762Sjmallett
64213762Sjmallett#define	MV88E61XXPHY_PORT_FLAG_VTU_UPDATE	(0x0001)
65213762Sjmallett
66213762Sjmallettstruct mv88e61xxphy_softc {
67213762Sjmallett	device_t sc_dev;
68213762Sjmallett	struct mv88e61xxphy_port_softc sc_ports[MV88E61XX_PORTS];
69213762Sjmallett};
70213762Sjmallett
71213762Sjmallettenum mv88e61xxphy_vtu_membership_type {
72213762Sjmallett	MV88E61XXPHY_VTU_UNMODIFIED,
73213762Sjmallett	MV88E61XXPHY_VTU_UNTAGGED,
74213762Sjmallett	MV88E61XXPHY_VTU_TAGGED,
75213762Sjmallett	MV88E61XXPHY_VTU_DISCARDED,
76213762Sjmallett};
77213762Sjmallett
78213762Sjmallettenum mv88e61xxphy_sysctl_link_type {
79213762Sjmallett	MV88E61XXPHY_LINK_SYSCTL_DUPLEX,
80213762Sjmallett	MV88E61XXPHY_LINK_SYSCTL_LINK,
81213762Sjmallett	MV88E61XXPHY_LINK_SYSCTL_MEDIA,
82213762Sjmallett};
83213762Sjmallett
84213762Sjmallettenum mv88e61xxphy_sysctl_port_type {
85213762Sjmallett	MV88E61XXPHY_PORT_SYSCTL_DOMAIN,
86213762Sjmallett	MV88E61XXPHY_PORT_SYSCTL_VLAN,
87213762Sjmallett	MV88E61XXPHY_PORT_SYSCTL_PRIORITY,
88213762Sjmallett};
89213762Sjmallett
90213762Sjmallett/*
91213762Sjmallett * Register access macros.
92213762Sjmallett */
93213762Sjmallett#define	MV88E61XX_READ(sc, phy, reg)					\
94213762Sjmallett	MIIBUS_READREG(device_get_parent((sc)->sc_dev), (phy), (reg))
95213762Sjmallett
96213762Sjmallett#define	MV88E61XX_WRITE(sc, phy, reg, val)				\
97213762Sjmallett	MIIBUS_WRITEREG(device_get_parent((sc)->sc_dev), (phy), (reg), (val))
98213762Sjmallett
99213762Sjmallett#define	MV88E61XX_READ_PORT(psc, reg)					\
100213762Sjmallett	MV88E61XX_READ((psc)->sc_switch, MV88E61XX_PORT((psc)->sc_port), (reg))
101213762Sjmallett
102213762Sjmallett#define	MV88E61XX_WRITE_PORT(psc, reg, val)				\
103213762Sjmallett	MV88E61XX_WRITE((psc)->sc_switch, MV88E61XX_PORT((psc)->sc_port), (reg), (val))
104213762Sjmallett
105213762Sjmallettstatic int mv88e61xxphy_probe(device_t);
106213762Sjmallettstatic int mv88e61xxphy_attach(device_t);
107213762Sjmallett
108213762Sjmallettstatic void mv88e61xxphy_init(struct mv88e61xxphy_softc *);
109213762Sjmallettstatic void mv88e61xxphy_init_port(struct mv88e61xxphy_port_softc *);
110213762Sjmallettstatic void mv88e61xxphy_init_vtu(struct mv88e61xxphy_softc *);
111213762Sjmallettstatic int mv88e61xxphy_sysctl_link_proc(SYSCTL_HANDLER_ARGS);
112213762Sjmallettstatic int mv88e61xxphy_sysctl_port_proc(SYSCTL_HANDLER_ARGS);
113213762Sjmallettstatic void mv88e61xxphy_vtu_load(struct mv88e61xxphy_softc *, uint16_t);
114213762Sjmallettstatic void mv88e61xxphy_vtu_set_membership(struct mv88e61xxphy_softc *, unsigned, enum mv88e61xxphy_vtu_membership_type);
115213762Sjmallettstatic void mv88e61xxphy_vtu_wait(struct mv88e61xxphy_softc *);
116213762Sjmallett
117213762Sjmallettstatic int
118213762Sjmallettmv88e61xxphy_probe(device_t dev)
119213762Sjmallett{
120213762Sjmallett	uint16_t val;
121213762Sjmallett
122213762Sjmallett	val = MIIBUS_READREG(device_get_parent(dev), MV88E61XX_PORT(0),
123213762Sjmallett	    MV88E61XX_PORT_REVISION);
124213762Sjmallett	switch (val >> 4) {
125213762Sjmallett	case 0x121:
126213762Sjmallett		device_set_desc(dev, "Marvell Link Street 88E6123 3-Port Gigabit Switch");
127213762Sjmallett		return (0);
128213762Sjmallett	case 0x161:
129213762Sjmallett		device_set_desc(dev, "Marvell Link Street 88E6161 6-Port Gigabit Switch");
130213762Sjmallett		return (0);
131213762Sjmallett	case 0x165:
132213762Sjmallett		device_set_desc(dev, "Marvell Link Street 88E6161 6-Port Advanced Gigabit Switch");
133213762Sjmallett		return (0);
134213762Sjmallett	default:
135213762Sjmallett		return (ENXIO);
136213762Sjmallett	}
137213762Sjmallett}
138213762Sjmallett
139213762Sjmallettstatic int
140213762Sjmallettmv88e61xxphy_attach(device_t dev)
141213762Sjmallett{
142213762Sjmallett	char portbuf[] = "N";
143213762Sjmallett	struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(dev);
144213762Sjmallett	struct sysctl_oid *tree = device_get_sysctl_tree(dev);
145213762Sjmallett	struct sysctl_oid_list *child = SYSCTL_CHILDREN(tree);
146213762Sjmallett	struct sysctl_oid *port_node, *portN_node;
147213762Sjmallett	struct sysctl_oid_list *port_tree, *portN_tree;
148213762Sjmallett	struct mv88e61xxphy_softc *sc;
149213762Sjmallett	unsigned port;
150213762Sjmallett
151213762Sjmallett	sc = device_get_softc(dev);
152213762Sjmallett	sc->sc_dev = dev;
153213762Sjmallett
154213762Sjmallett	/*
155213762Sjmallett	 * Initialize port softcs.
156213762Sjmallett	 */
157213762Sjmallett	for (port = 0; port < MV88E61XX_PORTS; port++) {
158213762Sjmallett		struct mv88e61xxphy_port_softc *psc;
159213762Sjmallett
160213762Sjmallett		psc = &sc->sc_ports[port];
161213762Sjmallett		psc->sc_switch = sc;
162213762Sjmallett		psc->sc_port = port;
163213762Sjmallett		psc->sc_domain = 0; /* One broadcast domain by default.  */
164213762Sjmallett		psc->sc_vlan = port + 1; /* Tag VLANs by default.  */
165213762Sjmallett		psc->sc_priority = 0; /* No default special priority.  */
166213762Sjmallett		psc->sc_flags = 0;
167213762Sjmallett	}
168213762Sjmallett
169213762Sjmallett	/*
170213762Sjmallett	 * Add per-port sysctl tree/handlers.
171213762Sjmallett	 */
172213762Sjmallett	port_node = SYSCTL_ADD_NODE(ctx, child, OID_AUTO, "port",
173213762Sjmallett	    CTLFLAG_RD, NULL, "Switch Ports");
174213762Sjmallett	port_tree = SYSCTL_CHILDREN(port_node);
175213762Sjmallett	for (port = 0; port < MV88E61XX_PORTS; port++) {
176213762Sjmallett		struct mv88e61xxphy_port_softc *psc;
177213762Sjmallett
178213762Sjmallett		psc = &sc->sc_ports[port];
179213762Sjmallett
180213762Sjmallett		portbuf[0] = '0' + port;
181213762Sjmallett		portN_node = SYSCTL_ADD_NODE(ctx, port_tree, OID_AUTO, portbuf,
182213762Sjmallett		    CTLFLAG_RD, NULL, "Switch Port");
183213762Sjmallett		portN_tree = SYSCTL_CHILDREN(portN_node);
184213762Sjmallett
185213762Sjmallett		SYSCTL_ADD_PROC(ctx, portN_tree, OID_AUTO, "duplex",
186213762Sjmallett		    CTLFLAG_RD | CTLTYPE_INT, psc,
187213762Sjmallett		    MV88E61XXPHY_LINK_SYSCTL_DUPLEX,
188213762Sjmallett		    mv88e61xxphy_sysctl_link_proc, "IU",
189213762Sjmallett		    "Media duplex status (0 = half duplex; 1 = full duplex)");
190213762Sjmallett
191213762Sjmallett		SYSCTL_ADD_PROC(ctx, portN_tree, OID_AUTO, "link",
192213762Sjmallett		    CTLFLAG_RD | CTLTYPE_INT, psc,
193213762Sjmallett		    MV88E61XXPHY_LINK_SYSCTL_LINK,
194213762Sjmallett		    mv88e61xxphy_sysctl_link_proc, "IU",
195213762Sjmallett		    "Link status (0 = down; 1 = up)");
196213762Sjmallett
197213762Sjmallett		SYSCTL_ADD_PROC(ctx, portN_tree, OID_AUTO, "media",
198213762Sjmallett		    CTLFLAG_RD | CTLTYPE_INT, psc,
199213762Sjmallett		    MV88E61XXPHY_LINK_SYSCTL_MEDIA,
200213762Sjmallett		    mv88e61xxphy_sysctl_link_proc, "IU",
201213762Sjmallett		    "Media speed (0 = unknown; 10 = 10Mbps; 100 = 100Mbps; 1000 = 1Gbps)");
202213762Sjmallett
203213762Sjmallett		SYSCTL_ADD_PROC(ctx, portN_tree, OID_AUTO, "domain",
204213762Sjmallett		    CTLFLAG_RW | CTLTYPE_INT, psc,
205213762Sjmallett		    MV88E61XXPHY_PORT_SYSCTL_DOMAIN,
206213762Sjmallett		    mv88e61xxphy_sysctl_port_proc, "IU",
207213762Sjmallett		    "Broadcast domain (ports can only talk to other ports in the same domain)");
208213762Sjmallett
209213762Sjmallett		SYSCTL_ADD_PROC(ctx, portN_tree, OID_AUTO, "vlan",
210213762Sjmallett		    CTLFLAG_RW | CTLTYPE_INT, psc,
211213762Sjmallett		    MV88E61XXPHY_PORT_SYSCTL_VLAN,
212213762Sjmallett		    mv88e61xxphy_sysctl_port_proc, "IU",
213213762Sjmallett		    "Tag packets from/for this port with a given VLAN.");
214213762Sjmallett
215213762Sjmallett		SYSCTL_ADD_PROC(ctx, portN_tree, OID_AUTO, "priority",
216213762Sjmallett		    CTLFLAG_RW | CTLTYPE_INT, psc,
217213762Sjmallett		    MV88E61XXPHY_PORT_SYSCTL_PRIORITY,
218213762Sjmallett		    mv88e61xxphy_sysctl_port_proc, "IU",
219213762Sjmallett		    "Default packet priority for this port.");
220213762Sjmallett	}
221213762Sjmallett
222213762Sjmallett	mv88e61xxphy_init(sc);
223213762Sjmallett
224213762Sjmallett	return (0);
225213762Sjmallett}
226213762Sjmallett
227213762Sjmallettstatic void
228213762Sjmallettmv88e61xxphy_init(struct mv88e61xxphy_softc *sc)
229213762Sjmallett{
230213762Sjmallett	unsigned port;
231213762Sjmallett	uint16_t val;
232213762Sjmallett	unsigned i;
233213762Sjmallett
234213762Sjmallett	/* Disable all ports.  */
235213762Sjmallett	for (port = 0; port < MV88E61XX_PORTS; port++) {
236213762Sjmallett		struct mv88e61xxphy_port_softc *psc;
237213762Sjmallett
238213762Sjmallett		psc = &sc->sc_ports[port];
239213762Sjmallett
240213762Sjmallett		val = MV88E61XX_READ_PORT(psc, MV88E61XX_PORT_CONTROL);
241213762Sjmallett		val &= ~0x3;
242213762Sjmallett		MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_CONTROL, val);
243213762Sjmallett	}
244213762Sjmallett
245213762Sjmallett	DELAY(2000);
246213762Sjmallett
247213762Sjmallett	/* Reset the switch.  */
248213762Sjmallett	MV88E61XX_WRITE(sc, MV88E61XX_GLOBAL, MV88E61XX_GLOBAL_CONTROL, 0xc400);
249213762Sjmallett	for (i = 0; i < 100; i++) {
250213762Sjmallett		val = MV88E61XX_READ(sc, MV88E61XX_GLOBAL, MV88E61XX_GLOBAL_STATUS);
251213762Sjmallett		if ((val & 0xc800) == 0xc800)
252213762Sjmallett			break;
253213762Sjmallett		DELAY(10);
254213762Sjmallett	}
255213762Sjmallett	if (i == 100) {
256213762Sjmallett		device_printf(sc->sc_dev, "%s: switch reset timed out.\n", __func__);
257213762Sjmallett		return;
258213762Sjmallett	}
259213762Sjmallett
260213762Sjmallett	/* Disable PPU.  */
261213762Sjmallett	MV88E61XX_WRITE(sc, MV88E61XX_GLOBAL, MV88E61XX_GLOBAL_CONTROL, 0x0000);
262213762Sjmallett
263213762Sjmallett	/* Configure host port and send monitor frames to it.  */
264213762Sjmallett	MV88E61XX_WRITE(sc, MV88E61XX_GLOBAL, MV88E61XX_GLOBAL_MONITOR,
265213762Sjmallett	    (MV88E61XX_HOST_PORT << 12) | (MV88E61XX_HOST_PORT << 8) |
266213762Sjmallett	    (MV88E61XX_HOST_PORT << 4));
267213762Sjmallett
268213762Sjmallett	/* Disable remote management.  */
269213762Sjmallett	MV88E61XX_WRITE(sc, MV88E61XX_GLOBAL, MV88E61XX_GLOBAL_REMOTE_MGMT, 0x0000);
270213762Sjmallett
271213762Sjmallett	/* Send all specifically-addressed frames to the host port.  */
272213762Sjmallett	MV88E61XX_WRITE(sc, MV88E61XX_GLOBAL2, MV88E61XX_GLOBAL2_MANAGE_2X, 0xffff);
273213762Sjmallett	MV88E61XX_WRITE(sc, MV88E61XX_GLOBAL2, MV88E61XX_GLOBAL2_MANAGE_0X, 0xffff);
274213762Sjmallett
275213762Sjmallett	/* Remove provider-supplied tag and use it for switching.  */
276213762Sjmallett	MV88E61XX_WRITE(sc, MV88E61XX_GLOBAL2, MV88E61XX_GLOBAL2_CONTROL2,
277213762Sjmallett	    MV88E61XX_GLOBAL2_CONTROL2_REMOVE_PTAG);
278213762Sjmallett
279213762Sjmallett	/* Configure all ports.  */
280213762Sjmallett	for (port = 0; port < MV88E61XX_PORTS; port++) {
281213762Sjmallett		struct mv88e61xxphy_port_softc *psc;
282213762Sjmallett
283213762Sjmallett		psc = &sc->sc_ports[port];
284213762Sjmallett		mv88e61xxphy_init_port(psc);
285213762Sjmallett	}
286213762Sjmallett
287213762Sjmallett	/* Reprogram VLAN table (VTU.)  */
288213762Sjmallett	mv88e61xxphy_init_vtu(sc);
289213762Sjmallett
290213762Sjmallett	/* Enable all ports.  */
291213762Sjmallett	for (port = 0; port < MV88E61XX_PORTS; port++) {
292213762Sjmallett		struct mv88e61xxphy_port_softc *psc;
293213762Sjmallett
294213762Sjmallett		psc = &sc->sc_ports[port];
295213762Sjmallett
296213762Sjmallett		val = MV88E61XX_READ_PORT(psc, MV88E61XX_PORT_CONTROL);
297213762Sjmallett		val |= 0x3;
298213762Sjmallett		MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_CONTROL, val);
299213762Sjmallett	}
300213762Sjmallett}
301213762Sjmallett
302213762Sjmallettstatic void
303213762Sjmallettmv88e61xxphy_init_port(struct mv88e61xxphy_port_softc *psc)
304213762Sjmallett{
305213762Sjmallett	struct mv88e61xxphy_softc *sc;
306213762Sjmallett	unsigned allow_mask;
307213762Sjmallett
308213762Sjmallett	sc = psc->sc_switch;
309213762Sjmallett
310213762Sjmallett	/* Set media type and flow control.  */
311213762Sjmallett	if (psc->sc_port != MV88E61XX_HOST_PORT) {
312213762Sjmallett		/* Don't force any media type or flow control.  */
313213762Sjmallett		MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_FORCE_MAC, 0x0003);
314213762Sjmallett	} else {
315213762Sjmallett		/* Make CPU port 1G FDX.  */
316213762Sjmallett		MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_FORCE_MAC, 0x003e);
317213762Sjmallett	}
318213762Sjmallett
319213762Sjmallett	/* Don't limit flow control pauses.  */
320213762Sjmallett	MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_PAUSE_CONTROL, 0x0000);
321213762Sjmallett
322213762Sjmallett	/* Set various port functions per Linux.  */
323213762Sjmallett	if (psc->sc_port != MV88E61XX_HOST_PORT) {
324213762Sjmallett		MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_CONTROL, 0x04bc);
325213762Sjmallett	} else {
326213762Sjmallett		/*
327213762Sjmallett		 * Send frames for unknown unicast and multicast groups to
328213762Sjmallett		 * host, too.
329213762Sjmallett		 */
330213762Sjmallett		MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_CONTROL, 0x063f);
331213762Sjmallett	}
332213762Sjmallett
333213762Sjmallett	if (psc->sc_port != MV88E61XX_HOST_PORT) {
334213762Sjmallett		/* Disable trunking.  */
335213762Sjmallett		MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_CONTROL2, 0x0000);
336213762Sjmallett	} else {
337213762Sjmallett		/* Disable trunking and send learn messages to host.  */
338213762Sjmallett		MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_CONTROL2, 0x8000);
339213762Sjmallett	}
340213762Sjmallett
341213762Sjmallett	/*
342213762Sjmallett	 * Port-based VLAN map; isolates MAC tables and forces ports to talk
343213762Sjmallett	 * only to the host.
344213762Sjmallett	 *
345213762Sjmallett	 * Always allow the host to send to all ports and allow all ports to
346213762Sjmallett	 * send to the host.
347213762Sjmallett	 */
348213762Sjmallett	if (psc->sc_port != MV88E61XX_HOST_PORT) {
349213762Sjmallett		allow_mask = 1 << MV88E61XX_HOST_PORT;
350213762Sjmallett	} else {
351213762Sjmallett		allow_mask = (1 << MV88E61XX_PORTS) - 1;
352213762Sjmallett		allow_mask &= ~(1 << MV88E61XX_HOST_PORT);
353213762Sjmallett	}
354213762Sjmallett	MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_VLAN_MAP,
355213762Sjmallett	    (psc->sc_domain << 12) | allow_mask);
356213762Sjmallett
357213762Sjmallett	/* VLAN tagging.  Set default priority and VLAN tag (or none.)  */
358213762Sjmallett	MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_VLAN,
359213762Sjmallett	    (psc->sc_priority << 14) | psc->sc_vlan);
360213762Sjmallett
361213762Sjmallett	if (psc->sc_port == MV88E61XX_HOST_PORT) {
362213762Sjmallett		/* Set provider ingress tag.  */
363213762Sjmallett		MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_PROVIDER_PROTO,
364213762Sjmallett		    ETHERTYPE_VLAN);
365213762Sjmallett
366213762Sjmallett		/* Set provider egress tag.  */
367213762Sjmallett		MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_ETHER_PROTO,
368213762Sjmallett		    ETHERTYPE_VLAN);
369213762Sjmallett
370213762Sjmallett		/* Use secure 802.1q mode and accept only tagged frames.  */
371213762Sjmallett		MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_FILTER,
372213762Sjmallett		    MV88E61XX_PORT_FILTER_MAP_DEST |
373213762Sjmallett		    MV88E61XX_PORT_FILTER_8021Q_SECURE |
374213762Sjmallett		    MV88E61XX_PORT_FILTER_DISCARD_UNTAGGED);
375213762Sjmallett	} else {
376213762Sjmallett		/* Don't allow tagged frames.  */
377213762Sjmallett		MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_FILTER,
378213762Sjmallett		    MV88E61XX_PORT_FILTER_MAP_DEST |
379213762Sjmallett		    MV88E61XX_PORT_FILTER_DISCARD_TAGGED);
380213762Sjmallett	}
381213762Sjmallett}
382213762Sjmallett
383213762Sjmallettstatic void
384213762Sjmallettmv88e61xxphy_init_vtu(struct mv88e61xxphy_softc *sc)
385213762Sjmallett{
386213762Sjmallett	unsigned port;
387213762Sjmallett
388213762Sjmallett	/*
389213762Sjmallett	 * Start flush of the VTU.
390213762Sjmallett	 */
391213762Sjmallett	mv88e61xxphy_vtu_wait(sc);
392213762Sjmallett	MV88E61XX_WRITE(sc, MV88E61XX_GLOBAL, MV88E61XX_GLOBAL_VTU_OP,
393213762Sjmallett	    MV88E61XX_GLOBAL_VTU_OP_BUSY | MV88E61XX_GLOBAL_VTU_OP_OP_FLUSH);
394213762Sjmallett
395213762Sjmallett	/*
396213762Sjmallett	 * Queue each port's VLAN to be programmed.
397213762Sjmallett	 */
398213762Sjmallett	for (port = 0; port < MV88E61XX_PORTS; port++) {
399213762Sjmallett		struct mv88e61xxphy_port_softc *psc;
400213762Sjmallett
401213762Sjmallett		psc = &sc->sc_ports[port];
402213762Sjmallett		psc->sc_flags &= ~MV88E61XXPHY_PORT_FLAG_VTU_UPDATE;
403213762Sjmallett		if (psc->sc_vlan == 0)
404213762Sjmallett			continue;
405213762Sjmallett		psc->sc_flags |= MV88E61XXPHY_PORT_FLAG_VTU_UPDATE;
406213762Sjmallett	}
407213762Sjmallett
408213762Sjmallett	/*
409213762Sjmallett	 * Program each VLAN that is in use.
410213762Sjmallett	 */
411213762Sjmallett	for (port = 0; port < MV88E61XX_PORTS; port++) {
412213762Sjmallett		struct mv88e61xxphy_port_softc *psc;
413213762Sjmallett
414213762Sjmallett		psc = &sc->sc_ports[port];
415213762Sjmallett		if ((psc->sc_flags & MV88E61XXPHY_PORT_FLAG_VTU_UPDATE) == 0)
416213762Sjmallett			continue;
417213762Sjmallett		mv88e61xxphy_vtu_load(sc, psc->sc_vlan);
418213762Sjmallett	}
419213762Sjmallett
420213762Sjmallett	/*
421213762Sjmallett	 * Wait for last pending VTU operation to complete.
422213762Sjmallett	 */
423213762Sjmallett	mv88e61xxphy_vtu_wait(sc);
424213762Sjmallett}
425213762Sjmallett
426213762Sjmallettstatic int
427213762Sjmallettmv88e61xxphy_sysctl_link_proc(SYSCTL_HANDLER_ARGS)
428213762Sjmallett{
429213762Sjmallett	struct mv88e61xxphy_port_softc *psc = arg1;
430213762Sjmallett	enum mv88e61xxphy_sysctl_link_type type = arg2;
431213762Sjmallett	uint16_t val;
432213762Sjmallett	unsigned out;
433213762Sjmallett
434213762Sjmallett	val = MV88E61XX_READ_PORT(psc, MV88E61XX_PORT_STATUS);
435213762Sjmallett	switch (type) {
436213762Sjmallett	case MV88E61XXPHY_LINK_SYSCTL_DUPLEX:
437213762Sjmallett		if ((val & MV88E61XX_PORT_STATUS_DUPLEX) != 0)
438213762Sjmallett			out = 1;
439213762Sjmallett		else
440213762Sjmallett			out = 0;
441213762Sjmallett		break;
442213762Sjmallett	case MV88E61XXPHY_LINK_SYSCTL_LINK:
443213762Sjmallett		if ((val & MV88E61XX_PORT_STATUS_LINK) != 0)
444213762Sjmallett			out = 1;
445213762Sjmallett		else
446213762Sjmallett			out = 0;
447213762Sjmallett		break;
448213762Sjmallett	case MV88E61XXPHY_LINK_SYSCTL_MEDIA:
449213762Sjmallett		switch (val & MV88E61XX_PORT_STATUS_MEDIA) {
450213762Sjmallett		case MV88E61XX_PORT_STATUS_MEDIA_10M:
451213762Sjmallett			out = 10;
452213762Sjmallett			break;
453213762Sjmallett		case MV88E61XX_PORT_STATUS_MEDIA_100M:
454213762Sjmallett			out = 100;
455213762Sjmallett			break;
456213762Sjmallett		case MV88E61XX_PORT_STATUS_MEDIA_1G:
457213762Sjmallett			out = 1000;
458213762Sjmallett			break;
459213762Sjmallett		default:
460213762Sjmallett			out = 0;
461213762Sjmallett			break;
462213762Sjmallett		}
463213762Sjmallett		break;
464213762Sjmallett	default:
465213762Sjmallett		return (EINVAL);
466213762Sjmallett	}
467213762Sjmallett	return (sysctl_handle_int(oidp, NULL, out, req));
468213762Sjmallett}
469213762Sjmallett
470213762Sjmallettstatic int
471213762Sjmallettmv88e61xxphy_sysctl_port_proc(SYSCTL_HANDLER_ARGS)
472213762Sjmallett{
473213762Sjmallett	struct mv88e61xxphy_port_softc *psc = arg1;
474213762Sjmallett	enum mv88e61xxphy_sysctl_port_type type = arg2;
475213762Sjmallett	struct mv88e61xxphy_softc *sc = psc->sc_switch;
476213762Sjmallett	unsigned max, val, *valp;
477213762Sjmallett	int error;
478213762Sjmallett
479213762Sjmallett	switch (type) {
480213762Sjmallett	case MV88E61XXPHY_PORT_SYSCTL_DOMAIN:
481213762Sjmallett		valp = &psc->sc_domain;
482213762Sjmallett		max = 0xf;
483213762Sjmallett		break;
484213762Sjmallett	case MV88E61XXPHY_PORT_SYSCTL_VLAN:
485213762Sjmallett		valp = &psc->sc_vlan;
486213762Sjmallett		max = 0x1000;
487213762Sjmallett		break;
488213762Sjmallett	case MV88E61XXPHY_PORT_SYSCTL_PRIORITY:
489213762Sjmallett		valp = &psc->sc_priority;
490213762Sjmallett		max = 3;
491213762Sjmallett		break;
492213762Sjmallett	default:
493213762Sjmallett		return (EINVAL);
494213762Sjmallett	}
495213762Sjmallett
496213762Sjmallett	val = *valp;
497213762Sjmallett	error = sysctl_handle_int(oidp, &val, 0, req);
498213762Sjmallett	if (error != 0 || req->newptr == NULL)
499213762Sjmallett		return (error);
500213762Sjmallett
501213762Sjmallett	/* Bounds check value.  */
502213762Sjmallett	if (val >= max)
503213762Sjmallett		return (EINVAL);
504213762Sjmallett
505213762Sjmallett	/* Reinitialize switch with new value.  */
506213762Sjmallett	*valp = val;
507213762Sjmallett	mv88e61xxphy_init(sc);
508213762Sjmallett
509213762Sjmallett	return (0);
510213762Sjmallett}
511213762Sjmallett
512213762Sjmallettstatic void
513213762Sjmallettmv88e61xxphy_vtu_load(struct mv88e61xxphy_softc *sc, uint16_t vid)
514213762Sjmallett{
515213762Sjmallett	unsigned port;
516213762Sjmallett
517213762Sjmallett	/*
518213762Sjmallett	 * Wait for previous operation to complete.
519213762Sjmallett	 */
520213762Sjmallett	mv88e61xxphy_vtu_wait(sc);
521213762Sjmallett
522213762Sjmallett	/*
523213762Sjmallett	 * Set VID.
524213762Sjmallett	 */
525213762Sjmallett	MV88E61XX_WRITE(sc, MV88E61XX_GLOBAL, MV88E61XX_GLOBAL_VTU_VID,
526213762Sjmallett	    MV88E61XX_GLOBAL_VTU_VID_VALID | vid);
527213762Sjmallett
528213762Sjmallett	/*
529213762Sjmallett	 * Add ports to this VTU.
530213762Sjmallett	 */
531213762Sjmallett	for (port = 0; port < MV88E61XX_PORTS; port++) {
532213762Sjmallett		struct mv88e61xxphy_port_softc *psc;
533213762Sjmallett
534213762Sjmallett		psc = &sc->sc_ports[port];
535213762Sjmallett		if (psc->sc_vlan == vid) {
536213762Sjmallett			/*
537213762Sjmallett			 * Send this port its VLAN traffic untagged.
538213762Sjmallett			 */
539213762Sjmallett			psc->sc_flags &= ~MV88E61XXPHY_PORT_FLAG_VTU_UPDATE;
540213762Sjmallett			mv88e61xxphy_vtu_set_membership(sc, port, MV88E61XXPHY_VTU_UNTAGGED);
541213762Sjmallett		} else if (psc->sc_port == MV88E61XX_HOST_PORT) {
542213762Sjmallett			/*
543213762Sjmallett			 * The host sees all VLANs tagged.
544213762Sjmallett			 */
545213762Sjmallett			mv88e61xxphy_vtu_set_membership(sc, port, MV88E61XXPHY_VTU_TAGGED);
546213762Sjmallett		} else {
547213762Sjmallett			/*
548213762Sjmallett			 * This port isn't on this VLAN.
549213762Sjmallett			 */
550213762Sjmallett			mv88e61xxphy_vtu_set_membership(sc, port, MV88E61XXPHY_VTU_DISCARDED);
551213762Sjmallett		}
552213762Sjmallett	}
553213762Sjmallett
554213762Sjmallett	/*
555213762Sjmallett	 * Start adding this entry.
556213762Sjmallett	 */
557213762Sjmallett	MV88E61XX_WRITE(sc, MV88E61XX_GLOBAL, MV88E61XX_GLOBAL_VTU_OP,
558213762Sjmallett	    MV88E61XX_GLOBAL_VTU_OP_BUSY |
559213762Sjmallett	    MV88E61XX_GLOBAL_VTU_OP_OP_VTU_LOAD);
560213762Sjmallett}
561213762Sjmallett
562213762Sjmallettstatic void
563213762Sjmallettmv88e61xxphy_vtu_set_membership(struct mv88e61xxphy_softc *sc, unsigned port,
564213762Sjmallett    enum mv88e61xxphy_vtu_membership_type type)
565213762Sjmallett{
566213762Sjmallett	unsigned shift, reg;
567213762Sjmallett	uint16_t bits;
568213762Sjmallett	uint16_t val;
569213762Sjmallett
570213762Sjmallett	switch (type) {
571213762Sjmallett	case MV88E61XXPHY_VTU_UNMODIFIED:
572213762Sjmallett		bits = 0;
573213762Sjmallett		break;
574213762Sjmallett	case MV88E61XXPHY_VTU_UNTAGGED:
575213762Sjmallett		bits = 1;
576213762Sjmallett		break;
577213762Sjmallett	case MV88E61XXPHY_VTU_TAGGED:
578213762Sjmallett		bits = 2;
579213762Sjmallett		break;
580213762Sjmallett	case MV88E61XXPHY_VTU_DISCARDED:
581213762Sjmallett		bits = 3;
582213762Sjmallett		break;
583213762Sjmallett	default:
584213762Sjmallett		return;
585213762Sjmallett	}
586213762Sjmallett
587213762Sjmallett	if (port < 4) {
588213762Sjmallett		reg = MV88E61XX_GLOBAL_VTU_DATA_P0P3;
589213762Sjmallett		shift = port * 4;
590213762Sjmallett	} else {
591213762Sjmallett		reg = MV88E61XX_GLOBAL_VTU_DATA_P4P5;
592213762Sjmallett		shift = (port - 4) * 4;
593213762Sjmallett	}
594213762Sjmallett
595213762Sjmallett	val = MV88E61XX_READ(sc, MV88E61XX_GLOBAL, reg);
596213762Sjmallett	val |= bits << shift;
597213762Sjmallett	MV88E61XX_WRITE(sc, MV88E61XX_GLOBAL, reg, val);
598213762Sjmallett}
599213762Sjmallett
600213762Sjmallettstatic void
601213762Sjmallettmv88e61xxphy_vtu_wait(struct mv88e61xxphy_softc *sc)
602213762Sjmallett{
603213762Sjmallett	uint16_t val;
604213762Sjmallett
605213762Sjmallett	for (;;) {
606213762Sjmallett		val = MV88E61XX_READ(sc, MV88E61XX_GLOBAL, MV88E61XX_GLOBAL_VTU_OP);
607213762Sjmallett		if ((val & MV88E61XX_GLOBAL_VTU_OP_BUSY) == 0)
608213762Sjmallett			return;
609213762Sjmallett	}
610213762Sjmallett}
611213762Sjmallett
612213762Sjmallettstatic device_method_t mv88e61xxphy_methods[] = {
613213762Sjmallett	/* device interface */
614213762Sjmallett	DEVMETHOD(device_probe,		mv88e61xxphy_probe),
615213762Sjmallett	DEVMETHOD(device_attach,	mv88e61xxphy_attach),
616213762Sjmallett	DEVMETHOD(device_detach,	bus_generic_detach),
617213762Sjmallett	DEVMETHOD(device_shutdown,	bus_generic_shutdown),
618213762Sjmallett
619213762Sjmallett	{ 0, 0 }
620213762Sjmallett};
621213762Sjmallett
622213762Sjmallettstatic devclass_t mv88e61xxphy_devclass;
623213762Sjmallett
624213762Sjmallettstatic driver_t mv88e61xxphy_driver = {
625213762Sjmallett	"mv88e61xxphy",
626213762Sjmallett	mv88e61xxphy_methods,
627213762Sjmallett	sizeof(struct mv88e61xxphy_softc)
628213762Sjmallett};
629213762Sjmallett
630213762SjmallettDRIVER_MODULE(mv88e61xxphy, octe, mv88e61xxphy_driver, mv88e61xxphy_devclass, 0, 0);
631