]>
Commit | Line | Data |
---|---|---|
db82173f | 1 | /* |
ca47130a | 2 | * Texas Instruments N-Port Ethernet Switch Address Lookup Engine |
db82173f M |
3 | * |
4 | * Copyright (C) 2012 Texas Instruments | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or | |
7 | * modify it under the terms of the GNU General Public License as | |
8 | * published by the Free Software Foundation version 2. | |
9 | * | |
10 | * This program is distributed "as is" WITHOUT ANY WARRANTY of any | |
11 | * kind, whether express or implied; without even the implied warranty | |
12 | * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | * GNU General Public License for more details. | |
14 | */ | |
15 | #include <linux/kernel.h> | |
58c11b5f | 16 | #include <linux/module.h> |
db82173f M |
17 | #include <linux/platform_device.h> |
18 | #include <linux/seq_file.h> | |
19 | #include <linux/slab.h> | |
20 | #include <linux/err.h> | |
21 | #include <linux/io.h> | |
22 | #include <linux/stat.h> | |
23 | #include <linux/sysfs.h> | |
5c50a856 | 24 | #include <linux/etherdevice.h> |
db82173f M |
25 | |
26 | #include "cpsw_ale.h" | |
27 | ||
28 | #define BITMASK(bits) (BIT(bits) - 1) | |
db82173f | 29 | |
ca47130a | 30 | #define ALE_VERSION_MAJOR(rev, mask) (((rev) >> 8) & (mask)) |
db82173f | 31 | #define ALE_VERSION_MINOR(rev) (rev & 0xff) |
b361da83 | 32 | #define ALE_VERSION_1R3 0x0103 |
ca47130a | 33 | #define ALE_VERSION_1R4 0x0104 |
db82173f M |
34 | |
35 | /* ALE Registers */ | |
36 | #define ALE_IDVER 0x00 | |
7938a0d7 | 37 | #define ALE_STATUS 0x04 |
db82173f M |
38 | #define ALE_CONTROL 0x08 |
39 | #define ALE_PRESCALE 0x10 | |
40 | #define ALE_UNKNOWNVLAN 0x18 | |
41 | #define ALE_TABLE_CONTROL 0x20 | |
42 | #define ALE_TABLE 0x34 | |
43 | #define ALE_PORTCTL 0x40 | |
44 | ||
ca47130a KM |
45 | /* ALE NetCP NU switch specific Registers */ |
46 | #define ALE_UNKNOWNVLAN_MEMBER 0x90 | |
47 | #define ALE_UNKNOWNVLAN_UNREG_MCAST_FLOOD 0x94 | |
48 | #define ALE_UNKNOWNVLAN_REG_MCAST_FLOOD 0x98 | |
49 | #define ALE_UNKNOWNVLAN_FORCE_UNTAG_EGRESS 0x9C | |
b361da83 | 50 | #define ALE_VLAN_MASK_MUX(reg) (0xc0 + (0x4 * (reg))) |
ca47130a | 51 | |
db82173f M |
52 | #define ALE_TABLE_WRITE BIT(31) |
53 | ||
54 | #define ALE_TYPE_FREE 0 | |
55 | #define ALE_TYPE_ADDR 1 | |
56 | #define ALE_TYPE_VLAN 2 | |
57 | #define ALE_TYPE_VLAN_ADDR 3 | |
58 | ||
59 | #define ALE_UCAST_PERSISTANT 0 | |
60 | #define ALE_UCAST_UNTOUCHED 1 | |
61 | #define ALE_UCAST_OUI 2 | |
62 | #define ALE_UCAST_TOUCHED 3 | |
63 | ||
7938a0d7 KM |
64 | #define ALE_TABLE_SIZE_MULTIPLIER 1024 |
65 | #define ALE_STATUS_SIZE_MASK 0x1f | |
66 | #define ALE_TABLE_SIZE_DEFAULT 64 | |
67 | ||
db82173f M |
68 | static inline int cpsw_ale_get_field(u32 *ale_entry, u32 start, u32 bits) |
69 | { | |
70 | int idx; | |
71 | ||
72 | idx = start / 32; | |
73 | start -= idx * 32; | |
74 | idx = 2 - idx; /* flip */ | |
75 | return (ale_entry[idx] >> start) & BITMASK(bits); | |
76 | } | |
77 | ||
78 | static inline void cpsw_ale_set_field(u32 *ale_entry, u32 start, u32 bits, | |
79 | u32 value) | |
80 | { | |
81 | int idx; | |
82 | ||
83 | value &= BITMASK(bits); | |
84 | idx = start / 32; | |
85 | start -= idx * 32; | |
86 | idx = 2 - idx; /* flip */ | |
87 | ale_entry[idx] &= ~(BITMASK(bits) << start); | |
88 | ale_entry[idx] |= (value << start); | |
89 | } | |
90 | ||
91 | #define DEFINE_ALE_FIELD(name, start, bits) \ | |
92 | static inline int cpsw_ale_get_##name(u32 *ale_entry) \ | |
93 | { \ | |
94 | return cpsw_ale_get_field(ale_entry, start, bits); \ | |
95 | } \ | |
96 | static inline void cpsw_ale_set_##name(u32 *ale_entry, u32 value) \ | |
97 | { \ | |
98 | cpsw_ale_set_field(ale_entry, start, bits, value); \ | |
99 | } | |
100 | ||
b361da83 KM |
101 | #define DEFINE_ALE_FIELD1(name, start) \ |
102 | static inline int cpsw_ale_get_##name(u32 *ale_entry, u32 bits) \ | |
103 | { \ | |
104 | return cpsw_ale_get_field(ale_entry, start, bits); \ | |
105 | } \ | |
106 | static inline void cpsw_ale_set_##name(u32 *ale_entry, u32 value, \ | |
107 | u32 bits) \ | |
108 | { \ | |
109 | cpsw_ale_set_field(ale_entry, start, bits, value); \ | |
110 | } | |
111 | ||
db82173f M |
112 | DEFINE_ALE_FIELD(entry_type, 60, 2) |
113 | DEFINE_ALE_FIELD(vlan_id, 48, 12) | |
114 | DEFINE_ALE_FIELD(mcast_state, 62, 2) | |
b361da83 | 115 | DEFINE_ALE_FIELD1(port_mask, 66) |
db82173f M |
116 | DEFINE_ALE_FIELD(super, 65, 1) |
117 | DEFINE_ALE_FIELD(ucast_type, 62, 2) | |
b361da83 | 118 | DEFINE_ALE_FIELD1(port_num, 66) |
db82173f M |
119 | DEFINE_ALE_FIELD(blocked, 65, 1) |
120 | DEFINE_ALE_FIELD(secure, 64, 1) | |
b361da83 KM |
121 | DEFINE_ALE_FIELD1(vlan_untag_force, 24) |
122 | DEFINE_ALE_FIELD1(vlan_reg_mcast, 16) | |
123 | DEFINE_ALE_FIELD1(vlan_unreg_mcast, 8) | |
124 | DEFINE_ALE_FIELD1(vlan_member_list, 0) | |
db82173f | 125 | DEFINE_ALE_FIELD(mcast, 40, 1) |
b361da83 KM |
126 | /* ALE NetCP nu switch specific */ |
127 | DEFINE_ALE_FIELD(vlan_unreg_mcast_idx, 20, 3) | |
128 | DEFINE_ALE_FIELD(vlan_reg_mcast_idx, 44, 3) | |
db82173f M |
129 | |
130 | /* The MAC address field in the ALE entry cannot be macroized as above */ | |
131 | static inline void cpsw_ale_get_addr(u32 *ale_entry, u8 *addr) | |
132 | { | |
133 | int i; | |
134 | ||
135 | for (i = 0; i < 6; i++) | |
136 | addr[i] = cpsw_ale_get_field(ale_entry, 40 - 8*i, 8); | |
137 | } | |
138 | ||
139 | static inline void cpsw_ale_set_addr(u32 *ale_entry, u8 *addr) | |
140 | { | |
141 | int i; | |
142 | ||
143 | for (i = 0; i < 6; i++) | |
144 | cpsw_ale_set_field(ale_entry, 40 - 8*i, 8, addr[i]); | |
145 | } | |
146 | ||
147 | static int cpsw_ale_read(struct cpsw_ale *ale, int idx, u32 *ale_entry) | |
148 | { | |
149 | int i; | |
150 | ||
151 | WARN_ON(idx > ale->params.ale_entries); | |
152 | ||
153 | __raw_writel(idx, ale->params.ale_regs + ALE_TABLE_CONTROL); | |
154 | ||
155 | for (i = 0; i < ALE_ENTRY_WORDS; i++) | |
156 | ale_entry[i] = __raw_readl(ale->params.ale_regs + | |
157 | ALE_TABLE + 4 * i); | |
158 | ||
159 | return idx; | |
160 | } | |
161 | ||
162 | static int cpsw_ale_write(struct cpsw_ale *ale, int idx, u32 *ale_entry) | |
163 | { | |
164 | int i; | |
165 | ||
166 | WARN_ON(idx > ale->params.ale_entries); | |
167 | ||
168 | for (i = 0; i < ALE_ENTRY_WORDS; i++) | |
169 | __raw_writel(ale_entry[i], ale->params.ale_regs + | |
170 | ALE_TABLE + 4 * i); | |
171 | ||
172 | __raw_writel(idx | ALE_TABLE_WRITE, ale->params.ale_regs + | |
173 | ALE_TABLE_CONTROL); | |
174 | ||
175 | return idx; | |
176 | } | |
177 | ||
58c11b5f | 178 | static int cpsw_ale_match_addr(struct cpsw_ale *ale, u8 *addr, u16 vid) |
db82173f M |
179 | { |
180 | u32 ale_entry[ALE_ENTRY_WORDS]; | |
181 | int type, idx; | |
182 | ||
183 | for (idx = 0; idx < ale->params.ale_entries; idx++) { | |
184 | u8 entry_addr[6]; | |
185 | ||
186 | cpsw_ale_read(ale, idx, ale_entry); | |
187 | type = cpsw_ale_get_entry_type(ale_entry); | |
188 | if (type != ALE_TYPE_ADDR && type != ALE_TYPE_VLAN_ADDR) | |
189 | continue; | |
e11b220f M |
190 | if (cpsw_ale_get_vlan_id(ale_entry) != vid) |
191 | continue; | |
db82173f | 192 | cpsw_ale_get_addr(ale_entry, entry_addr); |
d9f394fe | 193 | if (ether_addr_equal(entry_addr, addr)) |
db82173f M |
194 | return idx; |
195 | } | |
196 | return -ENOENT; | |
197 | } | |
198 | ||
58c11b5f | 199 | static int cpsw_ale_match_vlan(struct cpsw_ale *ale, u16 vid) |
e11b220f M |
200 | { |
201 | u32 ale_entry[ALE_ENTRY_WORDS]; | |
202 | int type, idx; | |
203 | ||
204 | for (idx = 0; idx < ale->params.ale_entries; idx++) { | |
205 | cpsw_ale_read(ale, idx, ale_entry); | |
206 | type = cpsw_ale_get_entry_type(ale_entry); | |
207 | if (type != ALE_TYPE_VLAN) | |
208 | continue; | |
209 | if (cpsw_ale_get_vlan_id(ale_entry) == vid) | |
210 | return idx; | |
211 | } | |
212 | return -ENOENT; | |
213 | } | |
214 | ||
db82173f M |
215 | static int cpsw_ale_match_free(struct cpsw_ale *ale) |
216 | { | |
217 | u32 ale_entry[ALE_ENTRY_WORDS]; | |
218 | int type, idx; | |
219 | ||
220 | for (idx = 0; idx < ale->params.ale_entries; idx++) { | |
221 | cpsw_ale_read(ale, idx, ale_entry); | |
222 | type = cpsw_ale_get_entry_type(ale_entry); | |
223 | if (type == ALE_TYPE_FREE) | |
224 | return idx; | |
225 | } | |
226 | return -ENOENT; | |
227 | } | |
228 | ||
229 | static int cpsw_ale_find_ageable(struct cpsw_ale *ale) | |
230 | { | |
231 | u32 ale_entry[ALE_ENTRY_WORDS]; | |
232 | int type, idx; | |
233 | ||
234 | for (idx = 0; idx < ale->params.ale_entries; idx++) { | |
235 | cpsw_ale_read(ale, idx, ale_entry); | |
236 | type = cpsw_ale_get_entry_type(ale_entry); | |
237 | if (type != ALE_TYPE_ADDR && type != ALE_TYPE_VLAN_ADDR) | |
238 | continue; | |
239 | if (cpsw_ale_get_mcast(ale_entry)) | |
240 | continue; | |
241 | type = cpsw_ale_get_ucast_type(ale_entry); | |
242 | if (type != ALE_UCAST_PERSISTANT && | |
243 | type != ALE_UCAST_OUI) | |
244 | return idx; | |
245 | } | |
246 | return -ENOENT; | |
247 | } | |
248 | ||
249 | static void cpsw_ale_flush_mcast(struct cpsw_ale *ale, u32 *ale_entry, | |
250 | int port_mask) | |
251 | { | |
252 | int mask; | |
253 | ||
b361da83 KM |
254 | mask = cpsw_ale_get_port_mask(ale_entry, |
255 | ale->port_mask_bits); | |
db82173f M |
256 | if ((mask & port_mask) == 0) |
257 | return; /* ports dont intersect, not interested */ | |
258 | mask &= ~port_mask; | |
259 | ||
260 | /* free if only remaining port is host port */ | |
5c50a856 | 261 | if (mask) |
b361da83 KM |
262 | cpsw_ale_set_port_mask(ale_entry, mask, |
263 | ale->port_mask_bits); | |
5c50a856 M |
264 | else |
265 | cpsw_ale_set_entry_type(ale_entry, ALE_TYPE_FREE); | |
266 | } | |
267 | ||
25906052 | 268 | int cpsw_ale_flush_multicast(struct cpsw_ale *ale, int port_mask, int vid) |
5c50a856 M |
269 | { |
270 | u32 ale_entry[ALE_ENTRY_WORDS]; | |
271 | int ret, idx; | |
272 | ||
273 | for (idx = 0; idx < ale->params.ale_entries; idx++) { | |
274 | cpsw_ale_read(ale, idx, ale_entry); | |
275 | ret = cpsw_ale_get_entry_type(ale_entry); | |
276 | if (ret != ALE_TYPE_ADDR && ret != ALE_TYPE_VLAN_ADDR) | |
277 | continue; | |
278 | ||
25906052 M |
279 | /* if vid passed is -1 then remove all multicast entry from |
280 | * the table irrespective of vlan id, if a valid vlan id is | |
281 | * passed then remove only multicast added to that vlan id. | |
282 | * if vlan id doesn't match then move on to next entry. | |
283 | */ | |
284 | if (vid != -1 && cpsw_ale_get_vlan_id(ale_entry) != vid) | |
285 | continue; | |
286 | ||
5c50a856 M |
287 | if (cpsw_ale_get_mcast(ale_entry)) { |
288 | u8 addr[6]; | |
289 | ||
290 | cpsw_ale_get_addr(ale_entry, addr); | |
291 | if (!is_broadcast_ether_addr(addr)) | |
292 | cpsw_ale_flush_mcast(ale, ale_entry, port_mask); | |
293 | } | |
294 | ||
295 | cpsw_ale_write(ale, idx, ale_entry); | |
296 | } | |
297 | return 0; | |
db82173f | 298 | } |
58c11b5f | 299 | EXPORT_SYMBOL_GPL(cpsw_ale_flush_multicast); |
db82173f | 300 | |
e11b220f M |
301 | static inline void cpsw_ale_set_vlan_entry_type(u32 *ale_entry, |
302 | int flags, u16 vid) | |
303 | { | |
304 | if (flags & ALE_VLAN) { | |
305 | cpsw_ale_set_entry_type(ale_entry, ALE_TYPE_VLAN_ADDR); | |
306 | cpsw_ale_set_vlan_id(ale_entry, vid); | |
307 | } else { | |
308 | cpsw_ale_set_entry_type(ale_entry, ALE_TYPE_ADDR); | |
309 | } | |
310 | } | |
311 | ||
312 | int cpsw_ale_add_ucast(struct cpsw_ale *ale, u8 *addr, int port, | |
313 | int flags, u16 vid) | |
db82173f M |
314 | { |
315 | u32 ale_entry[ALE_ENTRY_WORDS] = {0, 0, 0}; | |
316 | int idx; | |
317 | ||
e11b220f M |
318 | cpsw_ale_set_vlan_entry_type(ale_entry, flags, vid); |
319 | ||
db82173f M |
320 | cpsw_ale_set_addr(ale_entry, addr); |
321 | cpsw_ale_set_ucast_type(ale_entry, ALE_UCAST_PERSISTANT); | |
322 | cpsw_ale_set_secure(ale_entry, (flags & ALE_SECURE) ? 1 : 0); | |
323 | cpsw_ale_set_blocked(ale_entry, (flags & ALE_BLOCKED) ? 1 : 0); | |
b361da83 | 324 | cpsw_ale_set_port_num(ale_entry, port, ale->port_num_bits); |
db82173f | 325 | |
e11b220f | 326 | idx = cpsw_ale_match_addr(ale, addr, (flags & ALE_VLAN) ? vid : 0); |
db82173f M |
327 | if (idx < 0) |
328 | idx = cpsw_ale_match_free(ale); | |
329 | if (idx < 0) | |
330 | idx = cpsw_ale_find_ageable(ale); | |
331 | if (idx < 0) | |
332 | return -ENOMEM; | |
333 | ||
334 | cpsw_ale_write(ale, idx, ale_entry); | |
335 | return 0; | |
336 | } | |
58c11b5f | 337 | EXPORT_SYMBOL_GPL(cpsw_ale_add_ucast); |
db82173f | 338 | |
e11b220f M |
339 | int cpsw_ale_del_ucast(struct cpsw_ale *ale, u8 *addr, int port, |
340 | int flags, u16 vid) | |
db82173f M |
341 | { |
342 | u32 ale_entry[ALE_ENTRY_WORDS] = {0, 0, 0}; | |
343 | int idx; | |
344 | ||
e11b220f | 345 | idx = cpsw_ale_match_addr(ale, addr, (flags & ALE_VLAN) ? vid : 0); |
db82173f M |
346 | if (idx < 0) |
347 | return -ENOENT; | |
348 | ||
349 | cpsw_ale_set_entry_type(ale_entry, ALE_TYPE_FREE); | |
350 | cpsw_ale_write(ale, idx, ale_entry); | |
351 | return 0; | |
352 | } | |
58c11b5f | 353 | EXPORT_SYMBOL_GPL(cpsw_ale_del_ucast); |
db82173f M |
354 | |
355 | int cpsw_ale_add_mcast(struct cpsw_ale *ale, u8 *addr, int port_mask, | |
e11b220f | 356 | int flags, u16 vid, int mcast_state) |
db82173f M |
357 | { |
358 | u32 ale_entry[ALE_ENTRY_WORDS] = {0, 0, 0}; | |
359 | int idx, mask; | |
360 | ||
e11b220f | 361 | idx = cpsw_ale_match_addr(ale, addr, (flags & ALE_VLAN) ? vid : 0); |
db82173f M |
362 | if (idx >= 0) |
363 | cpsw_ale_read(ale, idx, ale_entry); | |
364 | ||
e11b220f M |
365 | cpsw_ale_set_vlan_entry_type(ale_entry, flags, vid); |
366 | ||
db82173f | 367 | cpsw_ale_set_addr(ale_entry, addr); |
e11b220f | 368 | cpsw_ale_set_super(ale_entry, (flags & ALE_BLOCKED) ? 1 : 0); |
db82173f M |
369 | cpsw_ale_set_mcast_state(ale_entry, mcast_state); |
370 | ||
b361da83 KM |
371 | mask = cpsw_ale_get_port_mask(ale_entry, |
372 | ale->port_mask_bits); | |
db82173f | 373 | port_mask |= mask; |
b361da83 KM |
374 | cpsw_ale_set_port_mask(ale_entry, port_mask, |
375 | ale->port_mask_bits); | |
db82173f M |
376 | |
377 | if (idx < 0) | |
378 | idx = cpsw_ale_match_free(ale); | |
379 | if (idx < 0) | |
380 | idx = cpsw_ale_find_ageable(ale); | |
381 | if (idx < 0) | |
382 | return -ENOMEM; | |
383 | ||
384 | cpsw_ale_write(ale, idx, ale_entry); | |
385 | return 0; | |
386 | } | |
58c11b5f | 387 | EXPORT_SYMBOL_GPL(cpsw_ale_add_mcast); |
db82173f | 388 | |
e11b220f M |
389 | int cpsw_ale_del_mcast(struct cpsw_ale *ale, u8 *addr, int port_mask, |
390 | int flags, u16 vid) | |
db82173f M |
391 | { |
392 | u32 ale_entry[ALE_ENTRY_WORDS] = {0, 0, 0}; | |
393 | int idx; | |
394 | ||
e11b220f | 395 | idx = cpsw_ale_match_addr(ale, addr, (flags & ALE_VLAN) ? vid : 0); |
db82173f M |
396 | if (idx < 0) |
397 | return -EINVAL; | |
398 | ||
399 | cpsw_ale_read(ale, idx, ale_entry); | |
400 | ||
401 | if (port_mask) | |
b361da83 KM |
402 | cpsw_ale_set_port_mask(ale_entry, port_mask, |
403 | ale->port_mask_bits); | |
db82173f M |
404 | else |
405 | cpsw_ale_set_entry_type(ale_entry, ALE_TYPE_FREE); | |
406 | ||
407 | cpsw_ale_write(ale, idx, ale_entry); | |
408 | return 0; | |
409 | } | |
58c11b5f | 410 | EXPORT_SYMBOL_GPL(cpsw_ale_del_mcast); |
db82173f | 411 | |
b361da83 KM |
412 | /* ALE NetCP NU switch specific vlan functions */ |
413 | static void cpsw_ale_set_vlan_mcast(struct cpsw_ale *ale, u32 *ale_entry, | |
414 | int reg_mcast, int unreg_mcast) | |
415 | { | |
416 | int idx; | |
417 | ||
418 | /* Set VLAN registered multicast flood mask */ | |
419 | idx = cpsw_ale_get_vlan_reg_mcast_idx(ale_entry); | |
420 | writel(reg_mcast, ale->params.ale_regs + ALE_VLAN_MASK_MUX(idx)); | |
421 | ||
422 | /* Set VLAN unregistered multicast flood mask */ | |
423 | idx = cpsw_ale_get_vlan_unreg_mcast_idx(ale_entry); | |
424 | writel(unreg_mcast, ale->params.ale_regs + ALE_VLAN_MASK_MUX(idx)); | |
425 | } | |
426 | ||
e11b220f M |
427 | int cpsw_ale_add_vlan(struct cpsw_ale *ale, u16 vid, int port, int untag, |
428 | int reg_mcast, int unreg_mcast) | |
429 | { | |
430 | u32 ale_entry[ALE_ENTRY_WORDS] = {0, 0, 0}; | |
431 | int idx; | |
432 | ||
433 | idx = cpsw_ale_match_vlan(ale, vid); | |
434 | if (idx >= 0) | |
435 | cpsw_ale_read(ale, idx, ale_entry); | |
436 | ||
437 | cpsw_ale_set_entry_type(ale_entry, ALE_TYPE_VLAN); | |
438 | cpsw_ale_set_vlan_id(ale_entry, vid); | |
439 | ||
b361da83 KM |
440 | cpsw_ale_set_vlan_untag_force(ale_entry, untag, ale->vlan_field_bits); |
441 | if (!ale->params.nu_switch_ale) { | |
442 | cpsw_ale_set_vlan_reg_mcast(ale_entry, reg_mcast, | |
443 | ale->vlan_field_bits); | |
444 | cpsw_ale_set_vlan_unreg_mcast(ale_entry, unreg_mcast, | |
445 | ale->vlan_field_bits); | |
446 | } else { | |
447 | cpsw_ale_set_vlan_mcast(ale, ale_entry, reg_mcast, unreg_mcast); | |
448 | } | |
449 | cpsw_ale_set_vlan_member_list(ale_entry, port, ale->vlan_field_bits); | |
e11b220f M |
450 | |
451 | if (idx < 0) | |
452 | idx = cpsw_ale_match_free(ale); | |
453 | if (idx < 0) | |
454 | idx = cpsw_ale_find_ageable(ale); | |
455 | if (idx < 0) | |
456 | return -ENOMEM; | |
457 | ||
458 | cpsw_ale_write(ale, idx, ale_entry); | |
459 | return 0; | |
460 | } | |
58c11b5f | 461 | EXPORT_SYMBOL_GPL(cpsw_ale_add_vlan); |
e11b220f M |
462 | |
463 | int cpsw_ale_del_vlan(struct cpsw_ale *ale, u16 vid, int port_mask) | |
464 | { | |
465 | u32 ale_entry[ALE_ENTRY_WORDS] = {0, 0, 0}; | |
466 | int idx; | |
467 | ||
468 | idx = cpsw_ale_match_vlan(ale, vid); | |
469 | if (idx < 0) | |
470 | return -ENOENT; | |
471 | ||
472 | cpsw_ale_read(ale, idx, ale_entry); | |
473 | ||
474 | if (port_mask) | |
b361da83 KM |
475 | cpsw_ale_set_vlan_member_list(ale_entry, port_mask, |
476 | ale->vlan_field_bits); | |
e11b220f M |
477 | else |
478 | cpsw_ale_set_entry_type(ale_entry, ALE_TYPE_FREE); | |
479 | ||
480 | cpsw_ale_write(ale, idx, ale_entry); | |
481 | return 0; | |
482 | } | |
58c11b5f | 483 | EXPORT_SYMBOL_GPL(cpsw_ale_del_vlan); |
e11b220f | 484 | |
1e5c4bc4 LS |
485 | void cpsw_ale_set_allmulti(struct cpsw_ale *ale, int allmulti) |
486 | { | |
487 | u32 ale_entry[ALE_ENTRY_WORDS]; | |
488 | int type, idx; | |
489 | int unreg_mcast = 0; | |
490 | ||
491 | /* Only bother doing the work if the setting is actually changing */ | |
492 | if (ale->allmulti == allmulti) | |
493 | return; | |
494 | ||
495 | /* Remember the new setting to check against next time */ | |
496 | ale->allmulti = allmulti; | |
497 | ||
498 | for (idx = 0; idx < ale->params.ale_entries; idx++) { | |
499 | cpsw_ale_read(ale, idx, ale_entry); | |
500 | type = cpsw_ale_get_entry_type(ale_entry); | |
501 | if (type != ALE_TYPE_VLAN) | |
502 | continue; | |
503 | ||
b361da83 KM |
504 | unreg_mcast = |
505 | cpsw_ale_get_vlan_unreg_mcast(ale_entry, | |
506 | ale->vlan_field_bits); | |
1e5c4bc4 LS |
507 | if (allmulti) |
508 | unreg_mcast |= 1; | |
509 | else | |
510 | unreg_mcast &= ~1; | |
b361da83 KM |
511 | cpsw_ale_set_vlan_unreg_mcast(ale_entry, unreg_mcast, |
512 | ale->vlan_field_bits); | |
1e5c4bc4 LS |
513 | cpsw_ale_write(ale, idx, ale_entry); |
514 | } | |
515 | } | |
58c11b5f | 516 | EXPORT_SYMBOL_GPL(cpsw_ale_set_allmulti); |
1e5c4bc4 | 517 | |
db82173f M |
518 | struct ale_control_info { |
519 | const char *name; | |
520 | int offset, port_offset; | |
521 | int shift, port_shift; | |
522 | int bits; | |
523 | }; | |
524 | ||
ca47130a | 525 | static struct ale_control_info ale_controls[ALE_NUM_CONTROLS] = { |
db82173f M |
526 | [ALE_ENABLE] = { |
527 | .name = "enable", | |
528 | .offset = ALE_CONTROL, | |
529 | .port_offset = 0, | |
530 | .shift = 31, | |
531 | .port_shift = 0, | |
532 | .bits = 1, | |
533 | }, | |
534 | [ALE_CLEAR] = { | |
535 | .name = "clear", | |
536 | .offset = ALE_CONTROL, | |
537 | .port_offset = 0, | |
538 | .shift = 30, | |
539 | .port_shift = 0, | |
540 | .bits = 1, | |
541 | }, | |
542 | [ALE_AGEOUT] = { | |
543 | .name = "ageout", | |
544 | .offset = ALE_CONTROL, | |
545 | .port_offset = 0, | |
546 | .shift = 29, | |
547 | .port_shift = 0, | |
548 | .bits = 1, | |
549 | }, | |
0cd8f9cc M |
550 | [ALE_P0_UNI_FLOOD] = { |
551 | .name = "port0_unicast_flood", | |
552 | .offset = ALE_CONTROL, | |
553 | .port_offset = 0, | |
554 | .shift = 8, | |
555 | .port_shift = 0, | |
556 | .bits = 1, | |
557 | }, | |
db82173f M |
558 | [ALE_VLAN_NOLEARN] = { |
559 | .name = "vlan_nolearn", | |
560 | .offset = ALE_CONTROL, | |
561 | .port_offset = 0, | |
562 | .shift = 7, | |
563 | .port_shift = 0, | |
564 | .bits = 1, | |
565 | }, | |
566 | [ALE_NO_PORT_VLAN] = { | |
567 | .name = "no_port_vlan", | |
568 | .offset = ALE_CONTROL, | |
569 | .port_offset = 0, | |
570 | .shift = 6, | |
571 | .port_shift = 0, | |
572 | .bits = 1, | |
573 | }, | |
574 | [ALE_OUI_DENY] = { | |
575 | .name = "oui_deny", | |
576 | .offset = ALE_CONTROL, | |
577 | .port_offset = 0, | |
578 | .shift = 5, | |
579 | .port_shift = 0, | |
580 | .bits = 1, | |
581 | }, | |
582 | [ALE_BYPASS] = { | |
583 | .name = "bypass", | |
584 | .offset = ALE_CONTROL, | |
585 | .port_offset = 0, | |
586 | .shift = 4, | |
587 | .port_shift = 0, | |
588 | .bits = 1, | |
589 | }, | |
590 | [ALE_RATE_LIMIT_TX] = { | |
591 | .name = "rate_limit_tx", | |
592 | .offset = ALE_CONTROL, | |
593 | .port_offset = 0, | |
594 | .shift = 3, | |
595 | .port_shift = 0, | |
596 | .bits = 1, | |
597 | }, | |
598 | [ALE_VLAN_AWARE] = { | |
599 | .name = "vlan_aware", | |
600 | .offset = ALE_CONTROL, | |
601 | .port_offset = 0, | |
602 | .shift = 2, | |
603 | .port_shift = 0, | |
604 | .bits = 1, | |
605 | }, | |
606 | [ALE_AUTH_ENABLE] = { | |
607 | .name = "auth_enable", | |
608 | .offset = ALE_CONTROL, | |
609 | .port_offset = 0, | |
610 | .shift = 1, | |
611 | .port_shift = 0, | |
612 | .bits = 1, | |
613 | }, | |
614 | [ALE_RATE_LIMIT] = { | |
615 | .name = "rate_limit", | |
616 | .offset = ALE_CONTROL, | |
617 | .port_offset = 0, | |
618 | .shift = 0, | |
619 | .port_shift = 0, | |
620 | .bits = 1, | |
621 | }, | |
622 | [ALE_PORT_STATE] = { | |
623 | .name = "port_state", | |
624 | .offset = ALE_PORTCTL, | |
625 | .port_offset = 4, | |
626 | .shift = 0, | |
627 | .port_shift = 0, | |
628 | .bits = 2, | |
629 | }, | |
630 | [ALE_PORT_DROP_UNTAGGED] = { | |
631 | .name = "drop_untagged", | |
632 | .offset = ALE_PORTCTL, | |
633 | .port_offset = 4, | |
634 | .shift = 2, | |
635 | .port_shift = 0, | |
636 | .bits = 1, | |
637 | }, | |
638 | [ALE_PORT_DROP_UNKNOWN_VLAN] = { | |
639 | .name = "drop_unknown", | |
640 | .offset = ALE_PORTCTL, | |
641 | .port_offset = 4, | |
642 | .shift = 3, | |
643 | .port_shift = 0, | |
644 | .bits = 1, | |
645 | }, | |
646 | [ALE_PORT_NOLEARN] = { | |
647 | .name = "nolearn", | |
648 | .offset = ALE_PORTCTL, | |
649 | .port_offset = 4, | |
650 | .shift = 4, | |
651 | .port_shift = 0, | |
652 | .bits = 1, | |
653 | }, | |
0cd8f9cc M |
654 | [ALE_PORT_NO_SA_UPDATE] = { |
655 | .name = "no_source_update", | |
656 | .offset = ALE_PORTCTL, | |
657 | .port_offset = 4, | |
658 | .shift = 5, | |
659 | .port_shift = 0, | |
660 | .bits = 1, | |
661 | }, | |
db82173f M |
662 | [ALE_PORT_MCAST_LIMIT] = { |
663 | .name = "mcast_limit", | |
664 | .offset = ALE_PORTCTL, | |
665 | .port_offset = 4, | |
666 | .shift = 16, | |
667 | .port_shift = 0, | |
668 | .bits = 8, | |
669 | }, | |
670 | [ALE_PORT_BCAST_LIMIT] = { | |
671 | .name = "bcast_limit", | |
672 | .offset = ALE_PORTCTL, | |
673 | .port_offset = 4, | |
674 | .shift = 24, | |
675 | .port_shift = 0, | |
676 | .bits = 8, | |
677 | }, | |
678 | [ALE_PORT_UNKNOWN_VLAN_MEMBER] = { | |
679 | .name = "unknown_vlan_member", | |
680 | .offset = ALE_UNKNOWNVLAN, | |
681 | .port_offset = 0, | |
682 | .shift = 0, | |
683 | .port_shift = 0, | |
684 | .bits = 6, | |
685 | }, | |
686 | [ALE_PORT_UNKNOWN_MCAST_FLOOD] = { | |
687 | .name = "unknown_mcast_flood", | |
688 | .offset = ALE_UNKNOWNVLAN, | |
689 | .port_offset = 0, | |
690 | .shift = 8, | |
691 | .port_shift = 0, | |
692 | .bits = 6, | |
693 | }, | |
694 | [ALE_PORT_UNKNOWN_REG_MCAST_FLOOD] = { | |
695 | .name = "unknown_reg_flood", | |
696 | .offset = ALE_UNKNOWNVLAN, | |
697 | .port_offset = 0, | |
698 | .shift = 16, | |
699 | .port_shift = 0, | |
700 | .bits = 6, | |
701 | }, | |
702 | [ALE_PORT_UNTAGGED_EGRESS] = { | |
703 | .name = "untagged_egress", | |
704 | .offset = ALE_UNKNOWNVLAN, | |
705 | .port_offset = 0, | |
706 | .shift = 24, | |
707 | .port_shift = 0, | |
708 | .bits = 6, | |
709 | }, | |
710 | }; | |
711 | ||
712 | int cpsw_ale_control_set(struct cpsw_ale *ale, int port, int control, | |
713 | int value) | |
714 | { | |
715 | const struct ale_control_info *info; | |
716 | int offset, shift; | |
717 | u32 tmp, mask; | |
718 | ||
719 | if (control < 0 || control >= ARRAY_SIZE(ale_controls)) | |
720 | return -EINVAL; | |
721 | ||
722 | info = &ale_controls[control]; | |
723 | if (info->port_offset == 0 && info->port_shift == 0) | |
724 | port = 0; /* global, port is a dont care */ | |
725 | ||
726 | if (port < 0 || port > ale->params.ale_ports) | |
727 | return -EINVAL; | |
728 | ||
729 | mask = BITMASK(info->bits); | |
730 | if (value & ~mask) | |
731 | return -EINVAL; | |
732 | ||
733 | offset = info->offset + (port * info->port_offset); | |
734 | shift = info->shift + (port * info->port_shift); | |
735 | ||
736 | tmp = __raw_readl(ale->params.ale_regs + offset); | |
737 | tmp = (tmp & ~(mask << shift)) | (value << shift); | |
738 | __raw_writel(tmp, ale->params.ale_regs + offset); | |
739 | ||
740 | return 0; | |
741 | } | |
58c11b5f | 742 | EXPORT_SYMBOL_GPL(cpsw_ale_control_set); |
db82173f M |
743 | |
744 | int cpsw_ale_control_get(struct cpsw_ale *ale, int port, int control) | |
745 | { | |
746 | const struct ale_control_info *info; | |
747 | int offset, shift; | |
748 | u32 tmp; | |
749 | ||
750 | if (control < 0 || control >= ARRAY_SIZE(ale_controls)) | |
751 | return -EINVAL; | |
752 | ||
753 | info = &ale_controls[control]; | |
754 | if (info->port_offset == 0 && info->port_shift == 0) | |
755 | port = 0; /* global, port is a dont care */ | |
756 | ||
757 | if (port < 0 || port > ale->params.ale_ports) | |
758 | return -EINVAL; | |
759 | ||
760 | offset = info->offset + (port * info->port_offset); | |
761 | shift = info->shift + (port * info->port_shift); | |
762 | ||
763 | tmp = __raw_readl(ale->params.ale_regs + offset) >> shift; | |
764 | return tmp & BITMASK(info->bits); | |
765 | } | |
58c11b5f | 766 | EXPORT_SYMBOL_GPL(cpsw_ale_control_get); |
db82173f | 767 | |
e99e88a9 | 768 | static void cpsw_ale_timer(struct timer_list *t) |
db82173f | 769 | { |
e99e88a9 | 770 | struct cpsw_ale *ale = from_timer(ale, t, timer); |
db82173f M |
771 | |
772 | cpsw_ale_control_set(ale, 0, ALE_AGEOUT, 1); | |
773 | ||
774 | if (ale->ageout) { | |
775 | ale->timer.expires = jiffies + ale->ageout; | |
776 | add_timer(&ale->timer); | |
777 | } | |
778 | } | |
779 | ||
db82173f M |
780 | void cpsw_ale_start(struct cpsw_ale *ale) |
781 | { | |
7938a0d7 | 782 | u32 rev, ale_entries; |
db82173f M |
783 | |
784 | rev = __raw_readl(ale->params.ale_regs + ALE_IDVER); | |
ca47130a KM |
785 | if (!ale->params.major_ver_mask) |
786 | ale->params.major_ver_mask = 0xff; | |
787 | ale->version = | |
788 | (ALE_VERSION_MAJOR(rev, ale->params.major_ver_mask) << 8) | | |
789 | ALE_VERSION_MINOR(rev); | |
790 | dev_info(ale->params.dev, "initialized cpsw ale version %d.%d\n", | |
791 | ALE_VERSION_MAJOR(rev, ale->params.major_ver_mask), | |
792 | ALE_VERSION_MINOR(rev)); | |
793 | ||
7938a0d7 KM |
794 | if (!ale->params.ale_entries) { |
795 | ale_entries = | |
796 | __raw_readl(ale->params.ale_regs + ALE_STATUS) & | |
797 | ALE_STATUS_SIZE_MASK; | |
798 | /* ALE available on newer NetCP switches has introduced | |
799 | * a register, ALE_STATUS, to indicate the size of ALE | |
800 | * table which shows the size as a multiple of 1024 entries. | |
801 | * For these, params.ale_entries will be set to zero. So | |
802 | * read the register and update the value of ale_entries. | |
803 | * ALE table on NetCP lite, is much smaller and is indicated | |
804 | * by a value of zero in ALE_STATUS. So use a default value | |
805 | * of ALE_TABLE_SIZE_DEFAULT for this. Caller is expected | |
806 | * to set the value of ale_entries for all other versions | |
807 | * of ALE. | |
808 | */ | |
809 | if (!ale_entries) | |
810 | ale_entries = ALE_TABLE_SIZE_DEFAULT; | |
811 | else | |
812 | ale_entries *= ALE_TABLE_SIZE_MULTIPLIER; | |
813 | ale->params.ale_entries = ale_entries; | |
814 | } | |
815 | dev_info(ale->params.dev, | |
816 | "ALE Table size %ld\n", ale->params.ale_entries); | |
817 | ||
b361da83 KM |
818 | /* set default bits for existing h/w */ |
819 | ale->port_mask_bits = 3; | |
820 | ale->port_num_bits = 2; | |
821 | ale->vlan_field_bits = 3; | |
822 | ||
823 | /* Set defaults override for ALE on NetCP NU switch and for version | |
824 | * 1R3 | |
825 | */ | |
ca47130a KM |
826 | if (ale->params.nu_switch_ale) { |
827 | /* Separate registers for unknown vlan configuration. | |
828 | * Also there are N bits, where N is number of ale | |
829 | * ports and shift value should be 0 | |
830 | */ | |
831 | ale_controls[ALE_PORT_UNKNOWN_VLAN_MEMBER].bits = | |
832 | ale->params.ale_ports; | |
833 | ale_controls[ALE_PORT_UNKNOWN_VLAN_MEMBER].offset = | |
834 | ALE_UNKNOWNVLAN_MEMBER; | |
835 | ale_controls[ALE_PORT_UNKNOWN_MCAST_FLOOD].bits = | |
836 | ale->params.ale_ports; | |
837 | ale_controls[ALE_PORT_UNKNOWN_MCAST_FLOOD].shift = 0; | |
838 | ale_controls[ALE_PORT_UNKNOWN_MCAST_FLOOD].offset = | |
839 | ALE_UNKNOWNVLAN_UNREG_MCAST_FLOOD; | |
840 | ale_controls[ALE_PORT_UNKNOWN_REG_MCAST_FLOOD].bits = | |
841 | ale->params.ale_ports; | |
842 | ale_controls[ALE_PORT_UNKNOWN_REG_MCAST_FLOOD].shift = 0; | |
843 | ale_controls[ALE_PORT_UNKNOWN_REG_MCAST_FLOOD].offset = | |
844 | ALE_UNKNOWNVLAN_REG_MCAST_FLOOD; | |
845 | ale_controls[ALE_PORT_UNTAGGED_EGRESS].bits = | |
846 | ale->params.ale_ports; | |
847 | ale_controls[ALE_PORT_UNTAGGED_EGRESS].shift = 0; | |
848 | ale_controls[ALE_PORT_UNTAGGED_EGRESS].offset = | |
849 | ALE_UNKNOWNVLAN_FORCE_UNTAG_EGRESS; | |
b361da83 KM |
850 | ale->port_mask_bits = ale->params.ale_ports; |
851 | ale->port_num_bits = ale->params.ale_ports - 1; | |
852 | ale->vlan_field_bits = ale->params.ale_ports; | |
853 | } else if (ale->version == ALE_VERSION_1R3) { | |
854 | ale->port_mask_bits = ale->params.ale_ports; | |
855 | ale->port_num_bits = 3; | |
856 | ale->vlan_field_bits = ale->params.ale_ports; | |
ca47130a KM |
857 | } |
858 | ||
db82173f M |
859 | cpsw_ale_control_set(ale, 0, ALE_ENABLE, 1); |
860 | cpsw_ale_control_set(ale, 0, ALE_CLEAR, 1); | |
861 | ||
e99e88a9 | 862 | timer_setup(&ale->timer, cpsw_ale_timer, 0); |
db82173f M |
863 | if (ale->ageout) { |
864 | ale->timer.expires = jiffies + ale->ageout; | |
865 | add_timer(&ale->timer); | |
866 | } | |
867 | } | |
58c11b5f | 868 | EXPORT_SYMBOL_GPL(cpsw_ale_start); |
db82173f M |
869 | |
870 | void cpsw_ale_stop(struct cpsw_ale *ale) | |
871 | { | |
872 | del_timer_sync(&ale->timer); | |
873 | } | |
58c11b5f | 874 | EXPORT_SYMBOL_GPL(cpsw_ale_stop); |
db82173f M |
875 | |
876 | struct cpsw_ale *cpsw_ale_create(struct cpsw_ale_params *params) | |
877 | { | |
878 | struct cpsw_ale *ale; | |
879 | ||
880 | ale = kzalloc(sizeof(*ale), GFP_KERNEL); | |
881 | if (!ale) | |
882 | return NULL; | |
883 | ||
884 | ale->params = *params; | |
885 | ale->ageout = ale->params.ale_ageout * HZ; | |
886 | ||
887 | return ale; | |
888 | } | |
58c11b5f | 889 | EXPORT_SYMBOL_GPL(cpsw_ale_create); |
db82173f M |
890 | |
891 | int cpsw_ale_destroy(struct cpsw_ale *ale) | |
892 | { | |
893 | if (!ale) | |
894 | return -EINVAL; | |
db82173f M |
895 | cpsw_ale_control_set(ale, 0, ALE_ENABLE, 0); |
896 | kfree(ale); | |
897 | return 0; | |
898 | } | |
58c11b5f | 899 | EXPORT_SYMBOL_GPL(cpsw_ale_destroy); |
52c4f0ec M |
900 | |
901 | void cpsw_ale_dump(struct cpsw_ale *ale, u32 *data) | |
902 | { | |
903 | int i; | |
904 | ||
905 | for (i = 0; i < ale->params.ale_entries; i++) { | |
906 | cpsw_ale_read(ale, i, data); | |
907 | data += ALE_ENTRY_WORDS; | |
908 | } | |
909 | } | |
58c11b5f KM |
910 | EXPORT_SYMBOL_GPL(cpsw_ale_dump); |
911 | ||
912 | MODULE_LICENSE("GPL v2"); | |
913 | MODULE_DESCRIPTION("TI CPSW ALE driver"); | |
914 | MODULE_AUTHOR("Texas Instruments"); |