aboutsummaryrefslogtreecommitdiffstats
path: root/net/dsa/mv88e6123_61_65.c
diff options
context:
space:
mode:
authorLennert Buytenhek <buytenh@wantstofly.org>2008-10-07 09:44:02 -0400
committerDavid S. Miller <davem@davemloft.net>2008-10-08 20:15:19 -0400
commit91da11f870f00a3322b81c73042291d7f0be5a17 (patch)
tree670fedb54ee3c8fa403e9095f6d7e95ee560f346 /net/dsa/mv88e6123_61_65.c
parent176eaa589b3d242f25f24e472883fcce5f196777 (diff)
net: Distributed Switch Architecture protocol support
Distributed Switch Architecture is a protocol for managing hardware switch chips. It consists of a set of MII management registers and commands to configure the switch, and an ethernet header format to signal which of the ports of the switch a packet was received from or is intended to be sent to. The switches that this driver supports are typically embedded in access points and routers, and a typical setup with a DSA switch looks something like this: +-----------+ +-----------+ | | RGMII | | | +-------+ +------ 1000baseT MDI ("WAN") | | | 6-port +------ 1000baseT MDI ("LAN1") | CPU | | ethernet +------ 1000baseT MDI ("LAN2") | |MIImgmt| switch +------ 1000baseT MDI ("LAN3") | +-------+ w/5 PHYs +------ 1000baseT MDI ("LAN4") | | | | +-----------+ +-----------+ The switch driver presents each port on the switch as a separate network interface to Linux, polls the switch to maintain software link state of those ports, forwards MII management interface accesses to those network interfaces (e.g. as done by ethtool) to the switch, and exposes the switch's hardware statistics counters via the appropriate Linux kernel interfaces. This initial patch supports the MII management interface register layout of the Marvell 88E6123, 88E6161 and 88E6165 switch chips, and supports the "Ethertype DSA" packet tagging format. (There is no officially registered ethertype for the Ethertype DSA packet format, so we just grab a random one. The ethertype to use is programmed into the switch, and the switch driver uses the value of ETH_P_EDSA for this, so this define can be changed at any time in the future if the one we chose is allocated to another protocol or if Ethertype DSA gets its own officially registered ethertype, and everything will continue to work.) Signed-off-by: Lennert Buytenhek <buytenh@marvell.com> Tested-by: Nicolas Pitre <nico@marvell.com> Tested-by: Byron Bradley <byron.bbradley@gmail.com> Tested-by: Tim Ellis <tim.ellis@mac.com> Tested-by: Peter van Valderen <linux@ddcrew.com> Tested-by: Dirk Teurlings <dirk@upexia.nl> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/dsa/mv88e6123_61_65.c')
-rw-r--r--net/dsa/mv88e6123_61_65.c417
1 files changed, 417 insertions, 0 deletions
diff --git a/net/dsa/mv88e6123_61_65.c b/net/dsa/mv88e6123_61_65.c
new file mode 100644
index 000000000000..147818cc706e
--- /dev/null
+++ b/net/dsa/mv88e6123_61_65.c
@@ -0,0 +1,417 @@
1/*
2 * net/dsa/mv88e6123_61_65.c - Marvell 88e6123/6161/6165 switch chip support
3 * Copyright (c) 2008 Marvell Semiconductor
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 */
10
11#include <linux/list.h>
12#include <linux/netdevice.h>
13#include <linux/phy.h>
14#include "dsa_priv.h"
15#include "mv88e6xxx.h"
16
17static char *mv88e6123_61_65_probe(struct mii_bus *bus, int sw_addr)
18{
19 int ret;
20
21 ret = __mv88e6xxx_reg_read(bus, sw_addr, REG_PORT(0), 0x03);
22 if (ret >= 0) {
23 ret &= 0xfff0;
24 if (ret == 0x1210)
25 return "Marvell 88E6123";
26 if (ret == 0x1610)
27 return "Marvell 88E6161";
28 if (ret == 0x1650)
29 return "Marvell 88E6165";
30 }
31
32 return NULL;
33}
34
35static int mv88e6123_61_65_switch_reset(struct dsa_switch *ds)
36{
37 int i;
38 int ret;
39
40 /*
41 * Set all ports to the disabled state.
42 */
43 for (i = 0; i < 8; i++) {
44 ret = REG_READ(REG_PORT(i), 0x04);
45 REG_WRITE(REG_PORT(i), 0x04, ret & 0xfffc);
46 }
47
48 /*
49 * Wait for transmit queues to drain.
50 */
51 msleep(2);
52
53 /*
54 * Reset the switch.
55 */
56 REG_WRITE(REG_GLOBAL, 0x04, 0xc400);
57
58 /*
59 * Wait up to one second for reset to complete.
60 */
61 for (i = 0; i < 1000; i++) {
62 ret = REG_READ(REG_GLOBAL, 0x00);
63 if ((ret & 0xc800) == 0xc800)
64 break;
65
66 msleep(1);
67 }
68 if (i == 1000)
69 return -ETIMEDOUT;
70
71 return 0;
72}
73
74static int mv88e6123_61_65_setup_global(struct dsa_switch *ds)
75{
76 int ret;
77 int i;
78
79 /*
80 * Disable the PHY polling unit (since there won't be any
81 * external PHYs to poll), don't discard packets with
82 * excessive collisions, and mask all interrupt sources.
83 */
84 REG_WRITE(REG_GLOBAL, 0x04, 0x0000);
85
86 /*
87 * Set the default address aging time to 5 minutes, and
88 * enable address learn messages to be sent to all message
89 * ports.
90 */
91 REG_WRITE(REG_GLOBAL, 0x0a, 0x0148);
92
93 /*
94 * Configure the priority mapping registers.
95 */
96 ret = mv88e6xxx_config_prio(ds);
97 if (ret < 0)
98 return ret;
99
100 /*
101 * Configure the cpu port, and configure the cpu port as the
102 * port to which ingress and egress monitor frames are to be
103 * sent.
104 */
105 REG_WRITE(REG_GLOBAL, 0x1a, (ds->cpu_port * 0x1110));
106
107 /*
108 * Disable remote management for now, and set the switch's
109 * DSA device number to zero.
110 */
111 REG_WRITE(REG_GLOBAL, 0x1c, 0x0000);
112
113 /*
114 * Send all frames with destination addresses matching
115 * 01:80:c2:00:00:2x to the CPU port.
116 */
117 REG_WRITE(REG_GLOBAL2, 0x02, 0xffff);
118
119 /*
120 * Send all frames with destination addresses matching
121 * 01:80:c2:00:00:0x to the CPU port.
122 */
123 REG_WRITE(REG_GLOBAL2, 0x03, 0xffff);
124
125 /*
126 * Disable the loopback filter, disable flow control
127 * messages, disable flood broadcast override, disable
128 * removing of provider tags, disable ATU age violation
129 * interrupts, disable tag flow control, force flow
130 * control priority to the highest, and send all special
131 * multicast frames to the CPU at the highest priority.
132 */
133 REG_WRITE(REG_GLOBAL2, 0x05, 0x00ff);
134
135 /*
136 * Map all DSA device IDs to the CPU port.
137 */
138 for (i = 0; i < 32; i++)
139 REG_WRITE(REG_GLOBAL2, 0x06, 0x8000 | (i << 8) | ds->cpu_port);
140
141 /*
142 * Clear all trunk masks.
143 */
144 for (i = 0; i < 8; i++)
145 REG_WRITE(REG_GLOBAL2, 0x07, 0x8000 | (i << 12) | 0xff);
146
147 /*
148 * Clear all trunk mappings.
149 */
150 for (i = 0; i < 16; i++)
151 REG_WRITE(REG_GLOBAL2, 0x08, 0x8000 | (i << 11));
152
153 /*
154 * Disable ingress rate limiting by resetting all ingress
155 * rate limit registers to their initial state.
156 */
157 for (i = 0; i < 6; i++)
158 REG_WRITE(REG_GLOBAL2, 0x09, 0x9000 | (i << 8));
159
160 /*
161 * Initialise cross-chip port VLAN table to reset defaults.
162 */
163 REG_WRITE(REG_GLOBAL2, 0x0b, 0x9000);
164
165 /*
166 * Clear the priority override table.
167 */
168 for (i = 0; i < 16; i++)
169 REG_WRITE(REG_GLOBAL2, 0x0f, 0x8000 | (i << 8));
170
171 /* @@@ initialise AVB (22/23) watchdog (27) sdet (29) registers */
172
173 return 0;
174}
175
176static int mv88e6123_61_65_setup_port(struct dsa_switch *ds, int p)
177{
178 int addr = REG_PORT(p);
179
180 /*
181 * MAC Forcing register: don't force link, speed, duplex
182 * or flow control state to any particular values.
183 */
184 REG_WRITE(addr, 0x01, 0x0003);
185
186 /*
187 * Do not limit the period of time that this port can be
188 * paused for by the remote end or the period of time that
189 * this port can pause the remote end.
190 */
191 REG_WRITE(addr, 0x02, 0x0000);
192
193 /*
194 * Port Control: disable Drop-on-Unlock, disable Drop-on-Lock,
195 * configure the EDSA tagging mode if this is the CPU port,
196 * disable Header mode, enable IGMP/MLD snooping, disable VLAN
197 * tunneling, determine priority by looking at 802.1p and IP
198 * priority fields (IP prio has precedence), and set STP state
199 * to Forwarding. Finally, if this is the CPU port, additionally
200 * enable forwarding of unknown unicast and multicast addresses.
201 */
202 REG_WRITE(addr, 0x04,
203 (p == ds->cpu_port) ? 0x373f : 0x0433);
204
205 /*
206 * Port Control 1: disable trunking. Also, if this is the
207 * CPU port, enable learn messages to be sent to this port.
208 */
209 REG_WRITE(addr, 0x05, (p == ds->cpu_port) ? 0x8000 : 0x0000);
210
211 /*
212 * Port based VLAN map: give each port its own address
213 * database, allow the CPU port to talk to each of the 'real'
214 * ports, and allow each of the 'real' ports to only talk to
215 * the CPU port.
216 */
217 REG_WRITE(addr, 0x06,
218 ((p & 0xf) << 12) |
219 ((p == ds->cpu_port) ?
220 ds->valid_port_mask :
221 (1 << ds->cpu_port)));
222
223 /*
224 * Default VLAN ID and priority: don't set a default VLAN
225 * ID, and set the default packet priority to zero.
226 */
227 REG_WRITE(addr, 0x07, 0x0000);
228
229 /*
230 * Port Control 2: don't force a good FCS, set the maximum
231 * frame size to 10240 bytes, don't let the switch add or
232 * strip 802.1q tags, don't discard tagged or untagged frames
233 * on this port, do a destination address lookup on all
234 * received packets as usual, disable ARP mirroring and don't
235 * send a copy of all transmitted/received frames on this port
236 * to the CPU.
237 */
238 REG_WRITE(addr, 0x08, 0x2080);
239
240 /*
241 * Egress rate control: disable egress rate control.
242 */
243 REG_WRITE(addr, 0x09, 0x0001);
244
245 /*
246 * Egress rate control 2: disable egress rate control.
247 */
248 REG_WRITE(addr, 0x0a, 0x0000);
249
250 /*
251 * Port Association Vector: when learning source addresses
252 * of packets, add the address to the address database using
253 * a port bitmap that has only the bit for this port set and
254 * the other bits clear.
255 */
256 REG_WRITE(addr, 0x0b, 1 << p);
257
258 /*
259 * Port ATU control: disable limiting the number of address
260 * database entries that this port is allowed to use.
261 */
262 REG_WRITE(addr, 0x0c, 0x0000);
263
264 /*
265 * Priorit Override: disable DA, SA and VTU priority override.
266 */
267 REG_WRITE(addr, 0x0d, 0x0000);
268
269 /*
270 * Port Ethertype: use the Ethertype DSA Ethertype value.
271 */
272 REG_WRITE(addr, 0x0f, ETH_P_EDSA);
273
274 /*
275 * Tag Remap: use an identity 802.1p prio -> switch prio
276 * mapping.
277 */
278 REG_WRITE(addr, 0x18, 0x3210);
279
280 /*
281 * Tag Remap 2: use an identity 802.1p prio -> switch prio
282 * mapping.
283 */
284 REG_WRITE(addr, 0x19, 0x7654);
285
286 return 0;
287}
288
289static int mv88e6123_61_65_setup(struct dsa_switch *ds)
290{
291 struct mv88e6xxx_priv_state *ps = (void *)(ds + 1);
292 int i;
293 int ret;
294
295 mutex_init(&ps->smi_mutex);
296 mutex_init(&ps->stats_mutex);
297
298 ret = mv88e6123_61_65_switch_reset(ds);
299 if (ret < 0)
300 return ret;
301
302 /* @@@ initialise vtu and atu */
303
304 ret = mv88e6123_61_65_setup_global(ds);
305 if (ret < 0)
306 return ret;
307
308 for (i = 0; i < 6; i++) {
309 ret = mv88e6123_61_65_setup_port(ds, i);
310 if (ret < 0)
311 return ret;
312 }
313
314 return 0;
315}
316
317static int mv88e6123_61_65_port_to_phy_addr(int port)
318{
319 if (port >= 0 && port <= 4)
320 return port;
321 return -1;
322}
323
324static int
325mv88e6123_61_65_phy_read(struct dsa_switch *ds, int port, int regnum)
326{
327 int addr = mv88e6123_61_65_port_to_phy_addr(port);
328 return mv88e6xxx_phy_read(ds, addr, regnum);
329}
330
331static int
332mv88e6123_61_65_phy_write(struct dsa_switch *ds,
333 int port, int regnum, u16 val)
334{
335 int addr = mv88e6123_61_65_port_to_phy_addr(port);
336 return mv88e6xxx_phy_write(ds, addr, regnum, val);
337}
338
339static struct mv88e6xxx_hw_stat mv88e6123_61_65_hw_stats[] = {
340 { "in_good_octets", 8, 0x00, },
341 { "in_bad_octets", 4, 0x02, },
342 { "in_unicast", 4, 0x04, },
343 { "in_broadcasts", 4, 0x06, },
344 { "in_multicasts", 4, 0x07, },
345 { "in_pause", 4, 0x16, },
346 { "in_undersize", 4, 0x18, },
347 { "in_fragments", 4, 0x19, },
348 { "in_oversize", 4, 0x1a, },
349 { "in_jabber", 4, 0x1b, },
350 { "in_rx_error", 4, 0x1c, },
351 { "in_fcs_error", 4, 0x1d, },
352 { "out_octets", 8, 0x0e, },
353 { "out_unicast", 4, 0x10, },
354 { "out_broadcasts", 4, 0x13, },
355 { "out_multicasts", 4, 0x12, },
356 { "out_pause", 4, 0x15, },
357 { "excessive", 4, 0x11, },
358 { "collisions", 4, 0x1e, },
359 { "deferred", 4, 0x05, },
360 { "single", 4, 0x14, },
361 { "multiple", 4, 0x17, },
362 { "out_fcs_error", 4, 0x03, },
363 { "late", 4, 0x1f, },
364 { "hist_64bytes", 4, 0x08, },
365 { "hist_65_127bytes", 4, 0x09, },
366 { "hist_128_255bytes", 4, 0x0a, },
367 { "hist_256_511bytes", 4, 0x0b, },
368 { "hist_512_1023bytes", 4, 0x0c, },
369 { "hist_1024_max_bytes", 4, 0x0d, },
370};
371
372static void
373mv88e6123_61_65_get_strings(struct dsa_switch *ds, int port, uint8_t *data)
374{
375 mv88e6xxx_get_strings(ds, ARRAY_SIZE(mv88e6123_61_65_hw_stats),
376 mv88e6123_61_65_hw_stats, port, data);
377}
378
379static void
380mv88e6123_61_65_get_ethtool_stats(struct dsa_switch *ds,
381 int port, uint64_t *data)
382{
383 mv88e6xxx_get_ethtool_stats(ds, ARRAY_SIZE(mv88e6123_61_65_hw_stats),
384 mv88e6123_61_65_hw_stats, port, data);
385}
386
387static int mv88e6123_61_65_get_sset_count(struct dsa_switch *ds)
388{
389 return ARRAY_SIZE(mv88e6123_61_65_hw_stats);
390}
391
392static struct dsa_switch_driver mv88e6123_61_65_switch_driver = {
393 .tag_protocol = __constant_htons(ETH_P_EDSA),
394 .priv_size = sizeof(struct mv88e6xxx_priv_state),
395 .probe = mv88e6123_61_65_probe,
396 .setup = mv88e6123_61_65_setup,
397 .set_addr = mv88e6xxx_set_addr_indirect,
398 .phy_read = mv88e6123_61_65_phy_read,
399 .phy_write = mv88e6123_61_65_phy_write,
400 .poll_link = mv88e6xxx_poll_link,
401 .get_strings = mv88e6123_61_65_get_strings,
402 .get_ethtool_stats = mv88e6123_61_65_get_ethtool_stats,
403 .get_sset_count = mv88e6123_61_65_get_sset_count,
404};
405
406int __init mv88e6123_61_65_init(void)
407{
408 register_switch_driver(&mv88e6123_61_65_switch_driver);
409 return 0;
410}
411module_init(mv88e6123_61_65_init);
412
413void __exit mv88e6123_61_65_cleanup(void)
414{
415 unregister_switch_driver(&mv88e6123_61_65_switch_driver);
416}
417module_exit(mv88e6123_61_65_cleanup);