1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Broadcom STB ASP 2.0 Driver |
4 | * |
5 | * Copyright (c) 2023 Broadcom |
6 | */ |
7 | #include <linux/etherdevice.h> |
8 | #include <linux/if_vlan.h> |
9 | #include <linux/init.h> |
10 | #include <linux/interrupt.h> |
11 | #include <linux/module.h> |
12 | #include <linux/kernel.h> |
13 | #include <linux/platform_device.h> |
14 | #include <linux/of.h> |
15 | #include <linux/of_address.h> |
16 | #include <linux/of_platform.h> |
17 | #include <linux/clk.h> |
18 | |
19 | #include "bcmasp.h" |
20 | #include "bcmasp_intf_defs.h" |
21 | |
22 | static void _intr2_mask_clear(struct bcmasp_priv *priv, u32 mask) |
23 | { |
24 | intr2_core_wl(priv, val: mask, ASP_INTR2_MASK_CLEAR); |
25 | priv->irq_mask &= ~mask; |
26 | } |
27 | |
28 | static void _intr2_mask_set(struct bcmasp_priv *priv, u32 mask) |
29 | { |
30 | intr2_core_wl(priv, val: mask, ASP_INTR2_MASK_SET); |
31 | priv->irq_mask |= mask; |
32 | } |
33 | |
34 | void bcmasp_enable_phy_irq(struct bcmasp_intf *intf, int en) |
35 | { |
36 | struct bcmasp_priv *priv = intf->parent; |
37 | |
38 | /* Only supported with internal phys */ |
39 | if (!intf->internal_phy) |
40 | return; |
41 | |
42 | if (en) |
43 | _intr2_mask_clear(priv, ASP_INTR2_PHY_EVENT(intf->channel)); |
44 | else |
45 | _intr2_mask_set(priv, ASP_INTR2_PHY_EVENT(intf->channel)); |
46 | } |
47 | |
48 | void bcmasp_enable_tx_irq(struct bcmasp_intf *intf, int en) |
49 | { |
50 | struct bcmasp_priv *priv = intf->parent; |
51 | |
52 | if (en) |
53 | _intr2_mask_clear(priv, ASP_INTR2_TX_DESC(intf->channel)); |
54 | else |
55 | _intr2_mask_set(priv, ASP_INTR2_TX_DESC(intf->channel)); |
56 | } |
57 | EXPORT_SYMBOL_GPL(bcmasp_enable_tx_irq); |
58 | |
59 | void bcmasp_enable_rx_irq(struct bcmasp_intf *intf, int en) |
60 | { |
61 | struct bcmasp_priv *priv = intf->parent; |
62 | |
63 | if (en) |
64 | _intr2_mask_clear(priv, ASP_INTR2_RX_ECH(intf->channel)); |
65 | else |
66 | _intr2_mask_set(priv, ASP_INTR2_RX_ECH(intf->channel)); |
67 | } |
68 | EXPORT_SYMBOL_GPL(bcmasp_enable_rx_irq); |
69 | |
70 | static void bcmasp_intr2_mask_set_all(struct bcmasp_priv *priv) |
71 | { |
72 | _intr2_mask_set(priv, mask: 0xffffffff); |
73 | priv->irq_mask = 0xffffffff; |
74 | } |
75 | |
76 | static void bcmasp_intr2_clear_all(struct bcmasp_priv *priv) |
77 | { |
78 | intr2_core_wl(priv, val: 0xffffffff, ASP_INTR2_CLEAR); |
79 | } |
80 | |
81 | static void bcmasp_intr2_handling(struct bcmasp_intf *intf, u32 status) |
82 | { |
83 | if (status & ASP_INTR2_RX_ECH(intf->channel)) { |
84 | if (likely(napi_schedule_prep(&intf->rx_napi))) { |
85 | bcmasp_enable_rx_irq(intf, 0); |
86 | __napi_schedule_irqoff(n: &intf->rx_napi); |
87 | } |
88 | } |
89 | |
90 | if (status & ASP_INTR2_TX_DESC(intf->channel)) { |
91 | if (likely(napi_schedule_prep(&intf->tx_napi))) { |
92 | bcmasp_enable_tx_irq(intf, 0); |
93 | __napi_schedule_irqoff(n: &intf->tx_napi); |
94 | } |
95 | } |
96 | |
97 | if (status & ASP_INTR2_PHY_EVENT(intf->channel)) |
98 | phy_mac_interrupt(phydev: intf->ndev->phydev); |
99 | } |
100 | |
101 | static irqreturn_t bcmasp_isr(int irq, void *data) |
102 | { |
103 | struct bcmasp_priv *priv = data; |
104 | struct bcmasp_intf *intf; |
105 | u32 status; |
106 | |
107 | status = intr2_core_rl(priv, ASP_INTR2_STATUS) & |
108 | ~intr2_core_rl(priv, ASP_INTR2_MASK_STATUS); |
109 | |
110 | intr2_core_wl(priv, val: status, ASP_INTR2_CLEAR); |
111 | |
112 | if (unlikely(status == 0)) { |
113 | dev_warn(&priv->pdev->dev, "l2 spurious interrupt\n" ); |
114 | return IRQ_NONE; |
115 | } |
116 | |
117 | /* Handle intferfaces */ |
118 | list_for_each_entry(intf, &priv->intfs, list) |
119 | bcmasp_intr2_handling(intf, status); |
120 | |
121 | return IRQ_HANDLED; |
122 | } |
123 | |
124 | void bcmasp_flush_rx_port(struct bcmasp_intf *intf) |
125 | { |
126 | struct bcmasp_priv *priv = intf->parent; |
127 | u32 mask; |
128 | |
129 | switch (intf->port) { |
130 | case 0: |
131 | mask = ASP_CTRL_UMAC0_FLUSH_MASK; |
132 | break; |
133 | case 1: |
134 | mask = ASP_CTRL_UMAC1_FLUSH_MASK; |
135 | break; |
136 | case 2: |
137 | mask = ASP_CTRL_SPB_FLUSH_MASK; |
138 | break; |
139 | default: |
140 | /* Not valid port */ |
141 | return; |
142 | } |
143 | |
144 | rx_ctrl_core_wl(priv, val: mask, off: priv->hw_info->rx_ctrl_flush); |
145 | } |
146 | |
147 | static void bcmasp_netfilt_hw_en_wake(struct bcmasp_priv *priv, |
148 | struct bcmasp_net_filter *nfilt) |
149 | { |
150 | rx_filter_core_wl(priv, ASP_RX_FILTER_NET_OFFSET_L3_1(64), |
151 | ASP_RX_FILTER_NET_OFFSET(nfilt->hw_index)); |
152 | |
153 | rx_filter_core_wl(priv, ASP_RX_FILTER_NET_OFFSET_L2(32) | |
154 | ASP_RX_FILTER_NET_OFFSET_L3_0(32) | |
155 | ASP_RX_FILTER_NET_OFFSET_L3_1(96) | |
156 | ASP_RX_FILTER_NET_OFFSET_L4(32), |
157 | ASP_RX_FILTER_NET_OFFSET(nfilt->hw_index + 1)); |
158 | |
159 | rx_filter_core_wl(priv, ASP_RX_FILTER_NET_CFG_CH(nfilt->port + 8) | |
160 | ASP_RX_FILTER_NET_CFG_EN | |
161 | ASP_RX_FILTER_NET_CFG_L2_EN | |
162 | ASP_RX_FILTER_NET_CFG_L3_EN | |
163 | ASP_RX_FILTER_NET_CFG_L4_EN | |
164 | ASP_RX_FILTER_NET_CFG_L3_FRM(2) | |
165 | ASP_RX_FILTER_NET_CFG_L4_FRM(2) | |
166 | ASP_RX_FILTER_NET_CFG_UMC(nfilt->port), |
167 | ASP_RX_FILTER_NET_CFG(nfilt->hw_index)); |
168 | |
169 | rx_filter_core_wl(priv, ASP_RX_FILTER_NET_CFG_CH(nfilt->port + 8) | |
170 | ASP_RX_FILTER_NET_CFG_EN | |
171 | ASP_RX_FILTER_NET_CFG_L2_EN | |
172 | ASP_RX_FILTER_NET_CFG_L3_EN | |
173 | ASP_RX_FILTER_NET_CFG_L4_EN | |
174 | ASP_RX_FILTER_NET_CFG_L3_FRM(2) | |
175 | ASP_RX_FILTER_NET_CFG_L4_FRM(2) | |
176 | ASP_RX_FILTER_NET_CFG_UMC(nfilt->port), |
177 | ASP_RX_FILTER_NET_CFG(nfilt->hw_index + 1)); |
178 | } |
179 | |
180 | #define MAX_WAKE_FILTER_SIZE 256 |
181 | enum asp_netfilt_reg_type { |
182 | ASP_NETFILT_MATCH = 0, |
183 | ASP_NETFILT_MASK, |
184 | ASP_NETFILT_MAX |
185 | }; |
186 | |
187 | static int bcmasp_netfilt_get_reg_offset(struct bcmasp_priv *priv, |
188 | struct bcmasp_net_filter *nfilt, |
189 | enum asp_netfilt_reg_type reg_type, |
190 | u32 offset) |
191 | { |
192 | u32 block_index, filter_sel; |
193 | |
194 | if (offset < 32) { |
195 | block_index = ASP_RX_FILTER_NET_L2; |
196 | filter_sel = nfilt->hw_index; |
197 | } else if (offset < 64) { |
198 | block_index = ASP_RX_FILTER_NET_L2; |
199 | filter_sel = nfilt->hw_index + 1; |
200 | } else if (offset < 96) { |
201 | block_index = ASP_RX_FILTER_NET_L3_0; |
202 | filter_sel = nfilt->hw_index; |
203 | } else if (offset < 128) { |
204 | block_index = ASP_RX_FILTER_NET_L3_0; |
205 | filter_sel = nfilt->hw_index + 1; |
206 | } else if (offset < 160) { |
207 | block_index = ASP_RX_FILTER_NET_L3_1; |
208 | filter_sel = nfilt->hw_index; |
209 | } else if (offset < 192) { |
210 | block_index = ASP_RX_FILTER_NET_L3_1; |
211 | filter_sel = nfilt->hw_index + 1; |
212 | } else if (offset < 224) { |
213 | block_index = ASP_RX_FILTER_NET_L4; |
214 | filter_sel = nfilt->hw_index; |
215 | } else if (offset < 256) { |
216 | block_index = ASP_RX_FILTER_NET_L4; |
217 | filter_sel = nfilt->hw_index + 1; |
218 | } else { |
219 | return -EINVAL; |
220 | } |
221 | |
222 | switch (reg_type) { |
223 | case ASP_NETFILT_MATCH: |
224 | return ASP_RX_FILTER_NET_PAT(filter_sel, block_index, |
225 | (offset % 32)); |
226 | case ASP_NETFILT_MASK: |
227 | return ASP_RX_FILTER_NET_MASK(filter_sel, block_index, |
228 | (offset % 32)); |
229 | default: |
230 | return -EINVAL; |
231 | } |
232 | } |
233 | |
234 | static void bcmasp_netfilt_wr(struct bcmasp_priv *priv, |
235 | struct bcmasp_net_filter *nfilt, |
236 | enum asp_netfilt_reg_type reg_type, |
237 | u32 val, u32 offset) |
238 | { |
239 | int reg_offset; |
240 | |
241 | /* HW only accepts 4 byte aligned writes */ |
242 | if (!IS_ALIGNED(offset, 4) || offset > MAX_WAKE_FILTER_SIZE) |
243 | return; |
244 | |
245 | reg_offset = bcmasp_netfilt_get_reg_offset(priv, nfilt, reg_type, |
246 | offset); |
247 | |
248 | rx_filter_core_wl(priv, val, off: reg_offset); |
249 | } |
250 | |
251 | static u32 bcmasp_netfilt_rd(struct bcmasp_priv *priv, |
252 | struct bcmasp_net_filter *nfilt, |
253 | enum asp_netfilt_reg_type reg_type, |
254 | u32 offset) |
255 | { |
256 | int reg_offset; |
257 | |
258 | /* HW only accepts 4 byte aligned writes */ |
259 | if (!IS_ALIGNED(offset, 4) || offset > MAX_WAKE_FILTER_SIZE) |
260 | return 0; |
261 | |
262 | reg_offset = bcmasp_netfilt_get_reg_offset(priv, nfilt, reg_type, |
263 | offset); |
264 | |
265 | return rx_filter_core_rl(priv, off: reg_offset); |
266 | } |
267 | |
268 | static int bcmasp_netfilt_wr_m_wake(struct bcmasp_priv *priv, |
269 | struct bcmasp_net_filter *nfilt, |
270 | u32 offset, void *match, void *mask, |
271 | size_t size) |
272 | { |
273 | u32 shift, mask_val = 0, match_val = 0; |
274 | bool first_byte = true; |
275 | |
276 | if ((offset + size) > MAX_WAKE_FILTER_SIZE) |
277 | return -EINVAL; |
278 | |
279 | while (size--) { |
280 | /* The HW only accepts 4 byte aligned writes, so if we |
281 | * begin unaligned or if remaining bytes less than 4, |
282 | * we need to read then write to avoid losing current |
283 | * register state |
284 | */ |
285 | if (first_byte && (!IS_ALIGNED(offset, 4) || size < 3)) { |
286 | match_val = bcmasp_netfilt_rd(priv, nfilt, |
287 | reg_type: ASP_NETFILT_MATCH, |
288 | ALIGN_DOWN(offset, 4)); |
289 | mask_val = bcmasp_netfilt_rd(priv, nfilt, |
290 | reg_type: ASP_NETFILT_MASK, |
291 | ALIGN_DOWN(offset, 4)); |
292 | } |
293 | |
294 | shift = (3 - (offset % 4)) * 8; |
295 | match_val &= ~GENMASK(shift + 7, shift); |
296 | mask_val &= ~GENMASK(shift + 7, shift); |
297 | match_val |= (u32)(*((u8 *)match) << shift); |
298 | mask_val |= (u32)(*((u8 *)mask) << shift); |
299 | |
300 | /* If last byte or last byte of word, write to reg */ |
301 | if (!size || ((offset % 4) == 3)) { |
302 | bcmasp_netfilt_wr(priv, nfilt, reg_type: ASP_NETFILT_MATCH, |
303 | val: match_val, ALIGN_DOWN(offset, 4)); |
304 | bcmasp_netfilt_wr(priv, nfilt, reg_type: ASP_NETFILT_MASK, |
305 | val: mask_val, ALIGN_DOWN(offset, 4)); |
306 | first_byte = true; |
307 | } else { |
308 | first_byte = false; |
309 | } |
310 | |
311 | offset++; |
312 | match++; |
313 | mask++; |
314 | } |
315 | |
316 | return 0; |
317 | } |
318 | |
319 | static void bcmasp_netfilt_reset_hw(struct bcmasp_priv *priv, |
320 | struct bcmasp_net_filter *nfilt) |
321 | { |
322 | int i; |
323 | |
324 | for (i = 0; i < MAX_WAKE_FILTER_SIZE; i += 4) { |
325 | bcmasp_netfilt_wr(priv, nfilt, reg_type: ASP_NETFILT_MATCH, val: 0, offset: i); |
326 | bcmasp_netfilt_wr(priv, nfilt, reg_type: ASP_NETFILT_MASK, val: 0, offset: i); |
327 | } |
328 | } |
329 | |
330 | static void bcmasp_netfilt_tcpip4_wr(struct bcmasp_priv *priv, |
331 | struct bcmasp_net_filter *nfilt, |
332 | struct ethtool_tcpip4_spec *match, |
333 | struct ethtool_tcpip4_spec *mask, |
334 | u32 offset) |
335 | { |
336 | __be16 val_16, mask_16; |
337 | |
338 | val_16 = htons(ETH_P_IP); |
339 | mask_16 = htons(0xFFFF); |
340 | bcmasp_netfilt_wr_m_wake(priv, nfilt, offset: (ETH_ALEN * 2) + offset, |
341 | match: &val_16, mask: &mask_16, size: sizeof(val_16)); |
342 | bcmasp_netfilt_wr_m_wake(priv, nfilt, ETH_HLEN + offset + 1, |
343 | match: &match->tos, mask: &mask->tos, |
344 | size: sizeof(match->tos)); |
345 | bcmasp_netfilt_wr_m_wake(priv, nfilt, ETH_HLEN + offset + 12, |
346 | match: &match->ip4src, mask: &mask->ip4src, |
347 | size: sizeof(match->ip4src)); |
348 | bcmasp_netfilt_wr_m_wake(priv, nfilt, ETH_HLEN + offset + 16, |
349 | match: &match->ip4dst, mask: &mask->ip4dst, |
350 | size: sizeof(match->ip4dst)); |
351 | bcmasp_netfilt_wr_m_wake(priv, nfilt, ETH_HLEN + offset + 20, |
352 | match: &match->psrc, mask: &mask->psrc, |
353 | size: sizeof(match->psrc)); |
354 | bcmasp_netfilt_wr_m_wake(priv, nfilt, ETH_HLEN + offset + 22, |
355 | match: &match->pdst, mask: &mask->pdst, |
356 | size: sizeof(match->pdst)); |
357 | } |
358 | |
359 | static void bcmasp_netfilt_tcpip6_wr(struct bcmasp_priv *priv, |
360 | struct bcmasp_net_filter *nfilt, |
361 | struct ethtool_tcpip6_spec *match, |
362 | struct ethtool_tcpip6_spec *mask, |
363 | u32 offset) |
364 | { |
365 | __be16 val_16, mask_16; |
366 | |
367 | val_16 = htons(ETH_P_IPV6); |
368 | mask_16 = htons(0xFFFF); |
369 | bcmasp_netfilt_wr_m_wake(priv, nfilt, offset: (ETH_ALEN * 2) + offset, |
370 | match: &val_16, mask: &mask_16, size: sizeof(val_16)); |
371 | val_16 = htons(match->tclass << 4); |
372 | mask_16 = htons(mask->tclass << 4); |
373 | bcmasp_netfilt_wr_m_wake(priv, nfilt, ETH_HLEN + offset, |
374 | match: &val_16, mask: &mask_16, size: sizeof(val_16)); |
375 | bcmasp_netfilt_wr_m_wake(priv, nfilt, ETH_HLEN + offset + 8, |
376 | match: &match->ip6src, mask: &mask->ip6src, |
377 | size: sizeof(match->ip6src)); |
378 | bcmasp_netfilt_wr_m_wake(priv, nfilt, ETH_HLEN + offset + 24, |
379 | match: &match->ip6dst, mask: &mask->ip6dst, |
380 | size: sizeof(match->ip6dst)); |
381 | bcmasp_netfilt_wr_m_wake(priv, nfilt, ETH_HLEN + offset + 40, |
382 | match: &match->psrc, mask: &mask->psrc, |
383 | size: sizeof(match->psrc)); |
384 | bcmasp_netfilt_wr_m_wake(priv, nfilt, ETH_HLEN + offset + 42, |
385 | match: &match->pdst, mask: &mask->pdst, |
386 | size: sizeof(match->pdst)); |
387 | } |
388 | |
389 | static int bcmasp_netfilt_wr_to_hw(struct bcmasp_priv *priv, |
390 | struct bcmasp_net_filter *nfilt) |
391 | { |
392 | struct ethtool_rx_flow_spec *fs = &nfilt->fs; |
393 | unsigned int offset = 0; |
394 | __be16 val_16, mask_16; |
395 | u8 val_8, mask_8; |
396 | |
397 | /* Currently only supports wake filters */ |
398 | if (!nfilt->wake_filter) |
399 | return -EINVAL; |
400 | |
401 | bcmasp_netfilt_reset_hw(priv, nfilt); |
402 | |
403 | if (fs->flow_type & FLOW_MAC_EXT) { |
404 | bcmasp_netfilt_wr_m_wake(priv, nfilt, offset: 0, match: &fs->h_ext.h_dest, |
405 | mask: &fs->m_ext.h_dest, |
406 | size: sizeof(fs->h_ext.h_dest)); |
407 | } |
408 | |
409 | if ((fs->flow_type & FLOW_EXT) && |
410 | (fs->m_ext.vlan_etype || fs->m_ext.vlan_tci)) { |
411 | bcmasp_netfilt_wr_m_wake(priv, nfilt, offset: (ETH_ALEN * 2), |
412 | match: &fs->h_ext.vlan_etype, |
413 | mask: &fs->m_ext.vlan_etype, |
414 | size: sizeof(fs->h_ext.vlan_etype)); |
415 | bcmasp_netfilt_wr_m_wake(priv, nfilt, offset: ((ETH_ALEN * 2) + 2), |
416 | match: &fs->h_ext.vlan_tci, |
417 | mask: &fs->m_ext.vlan_tci, |
418 | size: sizeof(fs->h_ext.vlan_tci)); |
419 | offset += VLAN_HLEN; |
420 | } |
421 | |
422 | switch (fs->flow_type & ~(FLOW_EXT | FLOW_MAC_EXT)) { |
423 | case ETHER_FLOW: |
424 | bcmasp_netfilt_wr_m_wake(priv, nfilt, offset: 0, |
425 | match: &fs->h_u.ether_spec.h_dest, |
426 | mask: &fs->m_u.ether_spec.h_dest, |
427 | size: sizeof(fs->h_u.ether_spec.h_dest)); |
428 | bcmasp_netfilt_wr_m_wake(priv, nfilt, ETH_ALEN, |
429 | match: &fs->h_u.ether_spec.h_source, |
430 | mask: &fs->m_u.ether_spec.h_source, |
431 | size: sizeof(fs->h_u.ether_spec.h_source)); |
432 | bcmasp_netfilt_wr_m_wake(priv, nfilt, offset: (ETH_ALEN * 2) + offset, |
433 | match: &fs->h_u.ether_spec.h_proto, |
434 | mask: &fs->m_u.ether_spec.h_proto, |
435 | size: sizeof(fs->h_u.ether_spec.h_proto)); |
436 | |
437 | break; |
438 | case IP_USER_FLOW: |
439 | val_16 = htons(ETH_P_IP); |
440 | mask_16 = htons(0xFFFF); |
441 | bcmasp_netfilt_wr_m_wake(priv, nfilt, offset: (ETH_ALEN * 2) + offset, |
442 | match: &val_16, mask: &mask_16, size: sizeof(val_16)); |
443 | bcmasp_netfilt_wr_m_wake(priv, nfilt, ETH_HLEN + offset + 1, |
444 | match: &fs->h_u.usr_ip4_spec.tos, |
445 | mask: &fs->m_u.usr_ip4_spec.tos, |
446 | size: sizeof(fs->h_u.usr_ip4_spec.tos)); |
447 | bcmasp_netfilt_wr_m_wake(priv, nfilt, ETH_HLEN + offset + 9, |
448 | match: &fs->h_u.usr_ip4_spec.proto, |
449 | mask: &fs->m_u.usr_ip4_spec.proto, |
450 | size: sizeof(fs->h_u.usr_ip4_spec.proto)); |
451 | bcmasp_netfilt_wr_m_wake(priv, nfilt, ETH_HLEN + offset + 12, |
452 | match: &fs->h_u.usr_ip4_spec.ip4src, |
453 | mask: &fs->m_u.usr_ip4_spec.ip4src, |
454 | size: sizeof(fs->h_u.usr_ip4_spec.ip4src)); |
455 | bcmasp_netfilt_wr_m_wake(priv, nfilt, ETH_HLEN + offset + 16, |
456 | match: &fs->h_u.usr_ip4_spec.ip4dst, |
457 | mask: &fs->m_u.usr_ip4_spec.ip4dst, |
458 | size: sizeof(fs->h_u.usr_ip4_spec.ip4dst)); |
459 | if (!fs->m_u.usr_ip4_spec.l4_4_bytes) |
460 | break; |
461 | |
462 | /* Only supports 20 byte IPv4 header */ |
463 | val_8 = 0x45; |
464 | mask_8 = 0xFF; |
465 | bcmasp_netfilt_wr_m_wake(priv, nfilt, ETH_HLEN + offset, |
466 | match: &val_8, mask: &mask_8, size: sizeof(val_8)); |
467 | bcmasp_netfilt_wr_m_wake(priv, nfilt, |
468 | ETH_HLEN + 20 + offset, |
469 | match: &fs->h_u.usr_ip4_spec.l4_4_bytes, |
470 | mask: &fs->m_u.usr_ip4_spec.l4_4_bytes, |
471 | size: sizeof(fs->h_u.usr_ip4_spec.l4_4_bytes) |
472 | ); |
473 | break; |
474 | case TCP_V4_FLOW: |
475 | val_8 = IPPROTO_TCP; |
476 | mask_8 = 0xFF; |
477 | bcmasp_netfilt_tcpip4_wr(priv, nfilt, match: &fs->h_u.tcp_ip4_spec, |
478 | mask: &fs->m_u.tcp_ip4_spec, offset); |
479 | bcmasp_netfilt_wr_m_wake(priv, nfilt, ETH_HLEN + offset + 9, |
480 | match: &val_8, mask: &mask_8, size: sizeof(val_8)); |
481 | break; |
482 | case UDP_V4_FLOW: |
483 | val_8 = IPPROTO_UDP; |
484 | mask_8 = 0xFF; |
485 | bcmasp_netfilt_tcpip4_wr(priv, nfilt, match: &fs->h_u.udp_ip4_spec, |
486 | mask: &fs->m_u.udp_ip4_spec, offset); |
487 | |
488 | bcmasp_netfilt_wr_m_wake(priv, nfilt, ETH_HLEN + offset + 9, |
489 | match: &val_8, mask: &mask_8, size: sizeof(val_8)); |
490 | break; |
491 | case TCP_V6_FLOW: |
492 | val_8 = IPPROTO_TCP; |
493 | mask_8 = 0xFF; |
494 | bcmasp_netfilt_tcpip6_wr(priv, nfilt, match: &fs->h_u.tcp_ip6_spec, |
495 | mask: &fs->m_u.tcp_ip6_spec, offset); |
496 | bcmasp_netfilt_wr_m_wake(priv, nfilt, ETH_HLEN + offset + 6, |
497 | match: &val_8, mask: &mask_8, size: sizeof(val_8)); |
498 | break; |
499 | case UDP_V6_FLOW: |
500 | val_8 = IPPROTO_UDP; |
501 | mask_8 = 0xFF; |
502 | bcmasp_netfilt_tcpip6_wr(priv, nfilt, match: &fs->h_u.udp_ip6_spec, |
503 | mask: &fs->m_u.udp_ip6_spec, offset); |
504 | bcmasp_netfilt_wr_m_wake(priv, nfilt, ETH_HLEN + offset + 6, |
505 | match: &val_8, mask: &mask_8, size: sizeof(val_8)); |
506 | break; |
507 | } |
508 | |
509 | bcmasp_netfilt_hw_en_wake(priv, nfilt); |
510 | |
511 | return 0; |
512 | } |
513 | |
514 | void bcmasp_netfilt_suspend(struct bcmasp_intf *intf) |
515 | { |
516 | struct bcmasp_priv *priv = intf->parent; |
517 | bool write = false; |
518 | int ret, i; |
519 | |
520 | /* Write all filters to HW */ |
521 | for (i = 0; i < NUM_NET_FILTERS; i++) { |
522 | /* If the filter does not match the port, skip programming. */ |
523 | if (!priv->net_filters[i].claimed || |
524 | priv->net_filters[i].port != intf->port) |
525 | continue; |
526 | |
527 | if (i > 0 && (i % 2) && |
528 | priv->net_filters[i].wake_filter && |
529 | priv->net_filters[i - 1].wake_filter) |
530 | continue; |
531 | |
532 | ret = bcmasp_netfilt_wr_to_hw(priv, nfilt: &priv->net_filters[i]); |
533 | if (!ret) |
534 | write = true; |
535 | } |
536 | |
537 | /* Successfully programmed at least one wake filter |
538 | * so enable top level wake config |
539 | */ |
540 | if (write) |
541 | rx_filter_core_wl(priv, val: (ASP_RX_FILTER_OPUT_EN | |
542 | ASP_RX_FILTER_LNR_MD | |
543 | ASP_RX_FILTER_GEN_WK_EN | |
544 | ASP_RX_FILTER_NT_FLT_EN), |
545 | ASP_RX_FILTER_BLK_CTRL); |
546 | } |
547 | |
548 | int bcmasp_netfilt_get_all_active(struct bcmasp_intf *intf, u32 *rule_locs, |
549 | u32 *rule_cnt) |
550 | { |
551 | struct bcmasp_priv *priv = intf->parent; |
552 | int j = 0, i; |
553 | |
554 | for (i = 0; i < NUM_NET_FILTERS; i++) { |
555 | if (!priv->net_filters[i].claimed || |
556 | priv->net_filters[i].port != intf->port) |
557 | continue; |
558 | |
559 | if (i > 0 && (i % 2) && |
560 | priv->net_filters[i].wake_filter && |
561 | priv->net_filters[i - 1].wake_filter) |
562 | continue; |
563 | |
564 | if (j == *rule_cnt) |
565 | return -EMSGSIZE; |
566 | |
567 | rule_locs[j++] = priv->net_filters[i].fs.location; |
568 | } |
569 | |
570 | *rule_cnt = j; |
571 | |
572 | return 0; |
573 | } |
574 | |
575 | int bcmasp_netfilt_get_active(struct bcmasp_intf *intf) |
576 | { |
577 | struct bcmasp_priv *priv = intf->parent; |
578 | int cnt = 0, i; |
579 | |
580 | for (i = 0; i < NUM_NET_FILTERS; i++) { |
581 | if (!priv->net_filters[i].claimed || |
582 | priv->net_filters[i].port != intf->port) |
583 | continue; |
584 | |
585 | /* Skip over a wake filter pair */ |
586 | if (i > 0 && (i % 2) && |
587 | priv->net_filters[i].wake_filter && |
588 | priv->net_filters[i - 1].wake_filter) |
589 | continue; |
590 | |
591 | cnt++; |
592 | } |
593 | |
594 | return cnt; |
595 | } |
596 | |
597 | bool bcmasp_netfilt_check_dup(struct bcmasp_intf *intf, |
598 | struct ethtool_rx_flow_spec *fs) |
599 | { |
600 | struct bcmasp_priv *priv = intf->parent; |
601 | struct ethtool_rx_flow_spec *cur; |
602 | size_t fs_size = 0; |
603 | int i; |
604 | |
605 | for (i = 0; i < NUM_NET_FILTERS; i++) { |
606 | if (!priv->net_filters[i].claimed || |
607 | priv->net_filters[i].port != intf->port) |
608 | continue; |
609 | |
610 | cur = &priv->net_filters[i].fs; |
611 | |
612 | if (cur->flow_type != fs->flow_type || |
613 | cur->ring_cookie != fs->ring_cookie) |
614 | continue; |
615 | |
616 | switch (fs->flow_type & ~(FLOW_EXT | FLOW_MAC_EXT)) { |
617 | case ETHER_FLOW: |
618 | fs_size = sizeof(struct ethhdr); |
619 | break; |
620 | case IP_USER_FLOW: |
621 | fs_size = sizeof(struct ethtool_usrip4_spec); |
622 | break; |
623 | case TCP_V6_FLOW: |
624 | case UDP_V6_FLOW: |
625 | fs_size = sizeof(struct ethtool_tcpip6_spec); |
626 | break; |
627 | case TCP_V4_FLOW: |
628 | case UDP_V4_FLOW: |
629 | fs_size = sizeof(struct ethtool_tcpip4_spec); |
630 | break; |
631 | default: |
632 | continue; |
633 | } |
634 | |
635 | if (memcmp(p: &cur->h_u, q: &fs->h_u, size: fs_size) || |
636 | memcmp(p: &cur->m_u, q: &fs->m_u, size: fs_size)) |
637 | continue; |
638 | |
639 | if (cur->flow_type & FLOW_EXT) { |
640 | if (cur->h_ext.vlan_etype != fs->h_ext.vlan_etype || |
641 | cur->m_ext.vlan_etype != fs->m_ext.vlan_etype || |
642 | cur->h_ext.vlan_tci != fs->h_ext.vlan_tci || |
643 | cur->m_ext.vlan_tci != fs->m_ext.vlan_tci || |
644 | cur->h_ext.data[0] != fs->h_ext.data[0]) |
645 | continue; |
646 | } |
647 | if (cur->flow_type & FLOW_MAC_EXT) { |
648 | if (memcmp(p: &cur->h_ext.h_dest, |
649 | q: &fs->h_ext.h_dest, ETH_ALEN) || |
650 | memcmp(p: &cur->m_ext.h_dest, |
651 | q: &fs->m_ext.h_dest, ETH_ALEN)) |
652 | continue; |
653 | } |
654 | |
655 | return true; |
656 | } |
657 | |
658 | return false; |
659 | } |
660 | |
661 | /* If no network filter found, return open filter. |
662 | * If no more open filters return NULL |
663 | */ |
664 | struct bcmasp_net_filter *bcmasp_netfilt_get_init(struct bcmasp_intf *intf, |
665 | u32 loc, bool wake_filter, |
666 | bool init) |
667 | { |
668 | struct bcmasp_net_filter *nfilter = NULL; |
669 | struct bcmasp_priv *priv = intf->parent; |
670 | int i, open_index = -1; |
671 | |
672 | /* Check whether we exceed the filter table capacity */ |
673 | if (loc != RX_CLS_LOC_ANY && loc >= NUM_NET_FILTERS) |
674 | return ERR_PTR(error: -EINVAL); |
675 | |
676 | /* If the filter location is busy (already claimed) and we are initializing |
677 | * the filter (insertion), return a busy error code. |
678 | */ |
679 | if (loc != RX_CLS_LOC_ANY && init && priv->net_filters[loc].claimed) |
680 | return ERR_PTR(error: -EBUSY); |
681 | |
682 | /* We need two filters for wake-up, so we cannot use an odd filter */ |
683 | if (wake_filter && loc != RX_CLS_LOC_ANY && (loc % 2)) |
684 | return ERR_PTR(error: -EINVAL); |
685 | |
686 | /* Initialize the loop index based on the desired location or from 0 */ |
687 | i = loc == RX_CLS_LOC_ANY ? 0 : loc; |
688 | |
689 | for ( ; i < NUM_NET_FILTERS; i++) { |
690 | /* Found matching network filter */ |
691 | if (!init && |
692 | priv->net_filters[i].claimed && |
693 | priv->net_filters[i].hw_index == i && |
694 | priv->net_filters[i].port == intf->port) |
695 | return &priv->net_filters[i]; |
696 | |
697 | /* If we don't need a new filter or new filter already found */ |
698 | if (!init || open_index >= 0) |
699 | continue; |
700 | |
701 | /* Wake filter conslidates two filters to cover more bytes |
702 | * Wake filter is open if... |
703 | * 1. It is an even filter |
704 | * 2. The current and next filter is not claimed |
705 | */ |
706 | if (wake_filter && !(i % 2) && !priv->net_filters[i].claimed && |
707 | !priv->net_filters[i + 1].claimed) |
708 | open_index = i; |
709 | else if (!priv->net_filters[i].claimed) |
710 | open_index = i; |
711 | } |
712 | |
713 | if (open_index >= 0) { |
714 | nfilter = &priv->net_filters[open_index]; |
715 | nfilter->claimed = true; |
716 | nfilter->port = intf->port; |
717 | nfilter->hw_index = open_index; |
718 | } |
719 | |
720 | if (wake_filter && open_index >= 0) { |
721 | /* Claim next filter */ |
722 | priv->net_filters[open_index + 1].claimed = true; |
723 | priv->net_filters[open_index + 1].wake_filter = true; |
724 | nfilter->wake_filter = true; |
725 | } |
726 | |
727 | return nfilter ? nfilter : ERR_PTR(error: -EINVAL); |
728 | } |
729 | |
730 | void bcmasp_netfilt_release(struct bcmasp_intf *intf, |
731 | struct bcmasp_net_filter *nfilt) |
732 | { |
733 | struct bcmasp_priv *priv = intf->parent; |
734 | |
735 | if (nfilt->wake_filter) { |
736 | memset(&priv->net_filters[nfilt->hw_index + 1], 0, |
737 | sizeof(struct bcmasp_net_filter)); |
738 | } |
739 | |
740 | memset(nfilt, 0, sizeof(struct bcmasp_net_filter)); |
741 | } |
742 | |
743 | static void bcmasp_addr_to_uint(unsigned char *addr, u32 *high, u32 *low) |
744 | { |
745 | *high = (u32)(addr[0] << 8 | addr[1]); |
746 | *low = (u32)(addr[2] << 24 | addr[3] << 16 | addr[4] << 8 | |
747 | addr[5]); |
748 | } |
749 | |
750 | static void bcmasp_set_mda_filter(struct bcmasp_intf *intf, |
751 | const unsigned char *addr, |
752 | unsigned char *mask, |
753 | unsigned int i) |
754 | { |
755 | struct bcmasp_priv *priv = intf->parent; |
756 | u32 addr_h, addr_l, mask_h, mask_l; |
757 | |
758 | /* Set local copy */ |
759 | ether_addr_copy(dst: priv->mda_filters[i].mask, src: mask); |
760 | ether_addr_copy(dst: priv->mda_filters[i].addr, src: addr); |
761 | |
762 | /* Write to HW */ |
763 | bcmasp_addr_to_uint(addr: priv->mda_filters[i].mask, high: &mask_h, low: &mask_l); |
764 | bcmasp_addr_to_uint(addr: priv->mda_filters[i].addr, high: &addr_h, low: &addr_l); |
765 | rx_filter_core_wl(priv, val: addr_h, ASP_RX_FILTER_MDA_PAT_H(i)); |
766 | rx_filter_core_wl(priv, val: addr_l, ASP_RX_FILTER_MDA_PAT_L(i)); |
767 | rx_filter_core_wl(priv, val: mask_h, ASP_RX_FILTER_MDA_MSK_H(i)); |
768 | rx_filter_core_wl(priv, val: mask_l, ASP_RX_FILTER_MDA_MSK_L(i)); |
769 | } |
770 | |
771 | static void bcmasp_en_mda_filter(struct bcmasp_intf *intf, bool en, |
772 | unsigned int i) |
773 | { |
774 | struct bcmasp_priv *priv = intf->parent; |
775 | |
776 | if (priv->mda_filters[i].en == en) |
777 | return; |
778 | |
779 | priv->mda_filters[i].en = en; |
780 | priv->mda_filters[i].port = intf->port; |
781 | |
782 | rx_filter_core_wl(priv, val: ((intf->channel + 8) | |
783 | (en << ASP_RX_FILTER_MDA_CFG_EN_SHIFT) | |
784 | ASP_RX_FILTER_MDA_CFG_UMC_SEL(intf->port)), |
785 | ASP_RX_FILTER_MDA_CFG(i)); |
786 | } |
787 | |
788 | /* There are 32 MDA filters shared between all ports, we reserve 4 filters per |
789 | * port for the following. |
790 | * - Promisc: Filter to allow all packets when promisc is enabled |
791 | * - All Multicast |
792 | * - Broadcast |
793 | * - Own address |
794 | * |
795 | * The reserved filters are identified as so. |
796 | * - Promisc: (index * 4) + 0 |
797 | * - All Multicast: (index * 4) + 1 |
798 | * - Broadcast: (index * 4) + 2 |
799 | * - Own address: (index * 4) + 3 |
800 | */ |
801 | enum asp_rx_filter_id { |
802 | ASP_RX_FILTER_MDA_PROMISC = 0, |
803 | ASP_RX_FILTER_MDA_ALLMULTI, |
804 | ASP_RX_FILTER_MDA_BROADCAST, |
805 | ASP_RX_FILTER_MDA_OWN_ADDR, |
806 | ASP_RX_FILTER_MDA_RES_MAX, |
807 | }; |
808 | |
809 | #define ASP_RX_FILT_MDA(intf, name) (((intf)->index * \ |
810 | ASP_RX_FILTER_MDA_RES_MAX) \ |
811 | + ASP_RX_FILTER_MDA_##name) |
812 | |
813 | static int bcmasp_total_res_mda_cnt(struct bcmasp_priv *priv) |
814 | { |
815 | return list_count_nodes(head: &priv->intfs) * ASP_RX_FILTER_MDA_RES_MAX; |
816 | } |
817 | |
818 | void bcmasp_set_promisc(struct bcmasp_intf *intf, bool en) |
819 | { |
820 | unsigned int i = ASP_RX_FILT_MDA(intf, PROMISC); |
821 | unsigned char promisc[ETH_ALEN]; |
822 | |
823 | eth_zero_addr(addr: promisc); |
824 | /* Set mask to 00:00:00:00:00:00 to match all packets */ |
825 | bcmasp_set_mda_filter(intf, addr: promisc, mask: promisc, i); |
826 | bcmasp_en_mda_filter(intf, en, i); |
827 | } |
828 | |
829 | void bcmasp_set_allmulti(struct bcmasp_intf *intf, bool en) |
830 | { |
831 | unsigned char allmulti[] = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00}; |
832 | unsigned int i = ASP_RX_FILT_MDA(intf, ALLMULTI); |
833 | |
834 | /* Set mask to 01:00:00:00:00:00 to match all multicast */ |
835 | bcmasp_set_mda_filter(intf, addr: allmulti, mask: allmulti, i); |
836 | bcmasp_en_mda_filter(intf, en, i); |
837 | } |
838 | |
839 | void bcmasp_set_broad(struct bcmasp_intf *intf, bool en) |
840 | { |
841 | unsigned int i = ASP_RX_FILT_MDA(intf, BROADCAST); |
842 | unsigned char addr[ETH_ALEN]; |
843 | |
844 | eth_broadcast_addr(addr); |
845 | bcmasp_set_mda_filter(intf, addr, mask: addr, i); |
846 | bcmasp_en_mda_filter(intf, en, i); |
847 | } |
848 | |
849 | void bcmasp_set_oaddr(struct bcmasp_intf *intf, const unsigned char *addr, |
850 | bool en) |
851 | { |
852 | unsigned char mask[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; |
853 | unsigned int i = ASP_RX_FILT_MDA(intf, OWN_ADDR); |
854 | |
855 | bcmasp_set_mda_filter(intf, addr, mask, i); |
856 | bcmasp_en_mda_filter(intf, en, i); |
857 | } |
858 | |
859 | void bcmasp_disable_all_filters(struct bcmasp_intf *intf) |
860 | { |
861 | struct bcmasp_priv *priv = intf->parent; |
862 | unsigned int i; |
863 | int res_count; |
864 | |
865 | res_count = bcmasp_total_res_mda_cnt(priv: intf->parent); |
866 | |
867 | /* Disable all filters held by this port */ |
868 | for (i = res_count; i < NUM_MDA_FILTERS; i++) { |
869 | if (priv->mda_filters[i].en && |
870 | priv->mda_filters[i].port == intf->port) |
871 | bcmasp_en_mda_filter(intf, en: 0, i); |
872 | } |
873 | } |
874 | |
875 | static int bcmasp_combine_set_filter(struct bcmasp_intf *intf, |
876 | unsigned char *addr, unsigned char *mask, |
877 | int i) |
878 | { |
879 | struct bcmasp_priv *priv = intf->parent; |
880 | u64 addr1, addr2, mask1, mask2, mask3; |
881 | |
882 | /* Switch to u64 to help with the calculations */ |
883 | addr1 = ether_addr_to_u64(addr: priv->mda_filters[i].addr); |
884 | mask1 = ether_addr_to_u64(addr: priv->mda_filters[i].mask); |
885 | addr2 = ether_addr_to_u64(addr); |
886 | mask2 = ether_addr_to_u64(addr: mask); |
887 | |
888 | /* Check if one filter resides within the other */ |
889 | mask3 = mask1 & mask2; |
890 | if (mask3 == mask1 && ((addr1 & mask1) == (addr2 & mask1))) { |
891 | /* Filter 2 resides within filter 1, so everything is good */ |
892 | return 0; |
893 | } else if (mask3 == mask2 && ((addr1 & mask2) == (addr2 & mask2))) { |
894 | /* Filter 1 resides within filter 2, so swap filters */ |
895 | bcmasp_set_mda_filter(intf, addr, mask, i); |
896 | return 0; |
897 | } |
898 | |
899 | /* Unable to combine */ |
900 | return -EINVAL; |
901 | } |
902 | |
903 | int bcmasp_set_en_mda_filter(struct bcmasp_intf *intf, unsigned char *addr, |
904 | unsigned char *mask) |
905 | { |
906 | struct bcmasp_priv *priv = intf->parent; |
907 | int ret, res_count; |
908 | unsigned int i; |
909 | |
910 | res_count = bcmasp_total_res_mda_cnt(priv: intf->parent); |
911 | |
912 | for (i = res_count; i < NUM_MDA_FILTERS; i++) { |
913 | /* If filter not enabled or belongs to another port skip */ |
914 | if (!priv->mda_filters[i].en || |
915 | priv->mda_filters[i].port != intf->port) |
916 | continue; |
917 | |
918 | /* Attempt to combine filters */ |
919 | ret = bcmasp_combine_set_filter(intf, addr, mask, i); |
920 | if (!ret) { |
921 | intf->mib.filters_combine_cnt++; |
922 | return 0; |
923 | } |
924 | } |
925 | |
926 | /* Create new filter if possible */ |
927 | for (i = res_count; i < NUM_MDA_FILTERS; i++) { |
928 | if (priv->mda_filters[i].en) |
929 | continue; |
930 | |
931 | bcmasp_set_mda_filter(intf, addr, mask, i); |
932 | bcmasp_en_mda_filter(intf, en: 1, i); |
933 | return 0; |
934 | } |
935 | |
936 | /* No room for new filter */ |
937 | return -EINVAL; |
938 | } |
939 | |
940 | static void bcmasp_core_init_filters(struct bcmasp_priv *priv) |
941 | { |
942 | unsigned int i; |
943 | |
944 | /* Disable all filters and reset software view since the HW |
945 | * can lose context while in deep sleep suspend states |
946 | */ |
947 | for (i = 0; i < NUM_MDA_FILTERS; i++) { |
948 | rx_filter_core_wl(priv, val: 0x0, ASP_RX_FILTER_MDA_CFG(i)); |
949 | priv->mda_filters[i].en = 0; |
950 | } |
951 | |
952 | for (i = 0; i < NUM_NET_FILTERS; i++) |
953 | rx_filter_core_wl(priv, val: 0x0, ASP_RX_FILTER_NET_CFG(i)); |
954 | |
955 | /* Top level filter enable bit should be enabled at all times, set |
956 | * GEN_WAKE_CLEAR to clear the network filter wake-up which would |
957 | * otherwise be sticky |
958 | */ |
959 | rx_filter_core_wl(priv, val: (ASP_RX_FILTER_OPUT_EN | |
960 | ASP_RX_FILTER_MDA_EN | |
961 | ASP_RX_FILTER_GEN_WK_CLR | |
962 | ASP_RX_FILTER_NT_FLT_EN), |
963 | ASP_RX_FILTER_BLK_CTRL); |
964 | } |
965 | |
966 | /* ASP core initialization */ |
967 | static void bcmasp_core_init(struct bcmasp_priv *priv) |
968 | { |
969 | tx_analytics_core_wl(priv, val: 0x0, ASP_TX_ANALYTICS_CTRL); |
970 | rx_analytics_core_wl(priv, val: 0x4, ASP_RX_ANALYTICS_CTRL); |
971 | |
972 | rx_edpkt_core_wl(priv, val: (ASP_EDPKT_HDR_SZ_128 << ASP_EDPKT_HDR_SZ_SHIFT), |
973 | ASP_EDPKT_HDR_CFG); |
974 | rx_edpkt_core_wl(priv, |
975 | val: (ASP_EDPKT_ENDI_BT_SWP_WD << ASP_EDPKT_ENDI_DESC_SHIFT), |
976 | ASP_EDPKT_ENDI); |
977 | |
978 | rx_edpkt_core_wl(priv, val: 0x1b, ASP_EDPKT_BURST_BUF_PSCAL_TOUT); |
979 | rx_edpkt_core_wl(priv, val: 0x3e8, ASP_EDPKT_BURST_BUF_WRITE_TOUT); |
980 | rx_edpkt_core_wl(priv, val: 0x3e8, ASP_EDPKT_BURST_BUF_READ_TOUT); |
981 | |
982 | rx_edpkt_core_wl(priv, ASP_EDPKT_ENABLE_EN, ASP_EDPKT_ENABLE); |
983 | |
984 | /* Disable and clear both UniMAC's wake-up interrupts to avoid |
985 | * sticky interrupts. |
986 | */ |
987 | _intr2_mask_set(priv, ASP_INTR2_UMC0_WAKE | ASP_INTR2_UMC1_WAKE); |
988 | intr2_core_wl(priv, ASP_INTR2_UMC0_WAKE | ASP_INTR2_UMC1_WAKE, |
989 | ASP_INTR2_CLEAR); |
990 | } |
991 | |
992 | static void bcmasp_core_clock_select_many(struct bcmasp_priv *priv, bool slow) |
993 | { |
994 | u32 reg; |
995 | |
996 | reg = ctrl2_core_rl(priv, ASP_CTRL2_CORE_CLOCK_SELECT); |
997 | if (slow) |
998 | reg &= ~ASP_CTRL2_CORE_CLOCK_SELECT_MAIN; |
999 | else |
1000 | reg |= ASP_CTRL2_CORE_CLOCK_SELECT_MAIN; |
1001 | ctrl2_core_wl(priv, val: reg, ASP_CTRL2_CORE_CLOCK_SELECT); |
1002 | |
1003 | reg = ctrl2_core_rl(priv, ASP_CTRL2_CPU_CLOCK_SELECT); |
1004 | if (slow) |
1005 | reg &= ~ASP_CTRL2_CPU_CLOCK_SELECT_MAIN; |
1006 | else |
1007 | reg |= ASP_CTRL2_CPU_CLOCK_SELECT_MAIN; |
1008 | ctrl2_core_wl(priv, val: reg, ASP_CTRL2_CPU_CLOCK_SELECT); |
1009 | } |
1010 | |
1011 | static void bcmasp_core_clock_select_one(struct bcmasp_priv *priv, bool slow) |
1012 | { |
1013 | u32 reg; |
1014 | |
1015 | reg = ctrl_core_rl(priv, ASP_CTRL_CORE_CLOCK_SELECT); |
1016 | if (slow) |
1017 | reg &= ~ASP_CTRL_CORE_CLOCK_SELECT_MAIN; |
1018 | else |
1019 | reg |= ASP_CTRL_CORE_CLOCK_SELECT_MAIN; |
1020 | ctrl_core_wl(priv, val: reg, ASP_CTRL_CORE_CLOCK_SELECT); |
1021 | } |
1022 | |
1023 | static void bcmasp_core_clock_set_ll(struct bcmasp_priv *priv, u32 clr, u32 set) |
1024 | { |
1025 | u32 reg; |
1026 | |
1027 | reg = ctrl_core_rl(priv, ASP_CTRL_CLOCK_CTRL); |
1028 | reg &= ~clr; |
1029 | reg |= set; |
1030 | ctrl_core_wl(priv, val: reg, ASP_CTRL_CLOCK_CTRL); |
1031 | |
1032 | reg = ctrl_core_rl(priv, ASP_CTRL_SCRATCH_0); |
1033 | reg &= ~clr; |
1034 | reg |= set; |
1035 | ctrl_core_wl(priv, val: reg, ASP_CTRL_SCRATCH_0); |
1036 | } |
1037 | |
1038 | static void bcmasp_core_clock_set(struct bcmasp_priv *priv, u32 clr, u32 set) |
1039 | { |
1040 | unsigned long flags; |
1041 | |
1042 | spin_lock_irqsave(&priv->clk_lock, flags); |
1043 | bcmasp_core_clock_set_ll(priv, clr, set); |
1044 | spin_unlock_irqrestore(lock: &priv->clk_lock, flags); |
1045 | } |
1046 | |
1047 | void bcmasp_core_clock_set_intf(struct bcmasp_intf *intf, bool en) |
1048 | { |
1049 | u32 intf_mask = ASP_CTRL_CLOCK_CTRL_ASP_RGMII_DIS(intf->port); |
1050 | struct bcmasp_priv *priv = intf->parent; |
1051 | unsigned long flags; |
1052 | u32 reg; |
1053 | |
1054 | /* When enabling an interface, if the RX or TX clocks were not enabled, |
1055 | * enable them. Conversely, while disabling an interface, if this is |
1056 | * the last one enabled, we can turn off the shared RX and TX clocks as |
1057 | * well. We control enable bits which is why we test for equality on |
1058 | * the RGMII clock bit mask. |
1059 | */ |
1060 | spin_lock_irqsave(&priv->clk_lock, flags); |
1061 | if (en) { |
1062 | intf_mask |= ASP_CTRL_CLOCK_CTRL_ASP_TX_DISABLE | |
1063 | ASP_CTRL_CLOCK_CTRL_ASP_RX_DISABLE; |
1064 | bcmasp_core_clock_set_ll(priv, clr: intf_mask, set: 0); |
1065 | } else { |
1066 | reg = ctrl_core_rl(priv, ASP_CTRL_SCRATCH_0) | intf_mask; |
1067 | if ((reg & ASP_CTRL_CLOCK_CTRL_ASP_RGMII_MASK) == |
1068 | ASP_CTRL_CLOCK_CTRL_ASP_RGMII_MASK) |
1069 | intf_mask |= ASP_CTRL_CLOCK_CTRL_ASP_TX_DISABLE | |
1070 | ASP_CTRL_CLOCK_CTRL_ASP_RX_DISABLE; |
1071 | bcmasp_core_clock_set_ll(priv, clr: 0, set: intf_mask); |
1072 | } |
1073 | spin_unlock_irqrestore(lock: &priv->clk_lock, flags); |
1074 | } |
1075 | |
1076 | static irqreturn_t bcmasp_isr_wol(int irq, void *data) |
1077 | { |
1078 | struct bcmasp_priv *priv = data; |
1079 | u32 status; |
1080 | |
1081 | /* No L3 IRQ, so we good */ |
1082 | if (priv->wol_irq <= 0) |
1083 | goto irq_handled; |
1084 | |
1085 | status = wakeup_intr2_core_rl(priv, ASP_WAKEUP_INTR2_STATUS) & |
1086 | ~wakeup_intr2_core_rl(priv, ASP_WAKEUP_INTR2_MASK_STATUS); |
1087 | wakeup_intr2_core_wl(priv, val: status, ASP_WAKEUP_INTR2_CLEAR); |
1088 | |
1089 | irq_handled: |
1090 | pm_wakeup_event(dev: &priv->pdev->dev, msec: 0); |
1091 | return IRQ_HANDLED; |
1092 | } |
1093 | |
1094 | static int bcmasp_get_and_request_irq(struct bcmasp_priv *priv, int i) |
1095 | { |
1096 | struct platform_device *pdev = priv->pdev; |
1097 | int irq, ret; |
1098 | |
1099 | irq = platform_get_irq_optional(pdev, i); |
1100 | if (irq < 0) |
1101 | return irq; |
1102 | |
1103 | ret = devm_request_irq(dev: &pdev->dev, irq, handler: bcmasp_isr_wol, irqflags: 0, |
1104 | devname: pdev->name, dev_id: priv); |
1105 | if (ret) |
1106 | return ret; |
1107 | |
1108 | return irq; |
1109 | } |
1110 | |
1111 | static void bcmasp_init_wol_shared(struct bcmasp_priv *priv) |
1112 | { |
1113 | struct platform_device *pdev = priv->pdev; |
1114 | struct device *dev = &pdev->dev; |
1115 | int irq; |
1116 | |
1117 | irq = bcmasp_get_and_request_irq(priv, i: 1); |
1118 | if (irq < 0) { |
1119 | dev_warn(dev, "Failed to init WoL irq: %d\n" , irq); |
1120 | return; |
1121 | } |
1122 | |
1123 | priv->wol_irq = irq; |
1124 | priv->wol_irq_enabled_mask = 0; |
1125 | device_set_wakeup_capable(dev: &pdev->dev, capable: 1); |
1126 | } |
1127 | |
1128 | static void bcmasp_enable_wol_shared(struct bcmasp_intf *intf, bool en) |
1129 | { |
1130 | struct bcmasp_priv *priv = intf->parent; |
1131 | struct device *dev = &priv->pdev->dev; |
1132 | |
1133 | if (en) { |
1134 | if (priv->wol_irq_enabled_mask) { |
1135 | set_bit(nr: intf->port, addr: &priv->wol_irq_enabled_mask); |
1136 | return; |
1137 | } |
1138 | |
1139 | /* First enable */ |
1140 | set_bit(nr: intf->port, addr: &priv->wol_irq_enabled_mask); |
1141 | enable_irq_wake(irq: priv->wol_irq); |
1142 | device_set_wakeup_enable(dev, enable: 1); |
1143 | } else { |
1144 | if (!priv->wol_irq_enabled_mask) |
1145 | return; |
1146 | |
1147 | clear_bit(nr: intf->port, addr: &priv->wol_irq_enabled_mask); |
1148 | if (priv->wol_irq_enabled_mask) |
1149 | return; |
1150 | |
1151 | /* Last disable */ |
1152 | disable_irq_wake(irq: priv->wol_irq); |
1153 | device_set_wakeup_enable(dev, enable: 0); |
1154 | } |
1155 | } |
1156 | |
1157 | static void bcmasp_wol_irq_destroy_shared(struct bcmasp_priv *priv) |
1158 | { |
1159 | if (priv->wol_irq > 0) |
1160 | free_irq(priv->wol_irq, priv); |
1161 | } |
1162 | |
1163 | static void bcmasp_init_wol_per_intf(struct bcmasp_priv *priv) |
1164 | { |
1165 | struct platform_device *pdev = priv->pdev; |
1166 | struct device *dev = &pdev->dev; |
1167 | struct bcmasp_intf *intf; |
1168 | int irq; |
1169 | |
1170 | list_for_each_entry(intf, &priv->intfs, list) { |
1171 | irq = bcmasp_get_and_request_irq(priv, i: intf->port + 1); |
1172 | if (irq < 0) { |
1173 | dev_warn(dev, "Failed to init WoL irq(port %d): %d\n" , |
1174 | intf->port, irq); |
1175 | continue; |
1176 | } |
1177 | |
1178 | intf->wol_irq = irq; |
1179 | intf->wol_irq_enabled = false; |
1180 | device_set_wakeup_capable(dev: &pdev->dev, capable: 1); |
1181 | } |
1182 | } |
1183 | |
1184 | static void bcmasp_enable_wol_per_intf(struct bcmasp_intf *intf, bool en) |
1185 | { |
1186 | struct device *dev = &intf->parent->pdev->dev; |
1187 | |
1188 | if (en ^ intf->wol_irq_enabled) |
1189 | irq_set_irq_wake(irq: intf->wol_irq, on: en); |
1190 | |
1191 | intf->wol_irq_enabled = en; |
1192 | device_set_wakeup_enable(dev, enable: en); |
1193 | } |
1194 | |
1195 | static void bcmasp_wol_irq_destroy_per_intf(struct bcmasp_priv *priv) |
1196 | { |
1197 | struct bcmasp_intf *intf; |
1198 | |
1199 | list_for_each_entry(intf, &priv->intfs, list) { |
1200 | if (intf->wol_irq > 0) |
1201 | free_irq(intf->wol_irq, priv); |
1202 | } |
1203 | } |
1204 | |
1205 | static void bcmasp_eee_fixup(struct bcmasp_intf *intf, bool en) |
1206 | { |
1207 | u32 reg, phy_lpi_overwrite; |
1208 | |
1209 | reg = rx_edpkt_core_rl(priv: intf->parent, ASP_EDPKT_SPARE_REG); |
1210 | phy_lpi_overwrite = intf->internal_phy ? ASP_EDPKT_SPARE_REG_EPHY_LPI : |
1211 | ASP_EDPKT_SPARE_REG_GPHY_LPI; |
1212 | |
1213 | if (en) |
1214 | reg |= phy_lpi_overwrite; |
1215 | else |
1216 | reg &= ~phy_lpi_overwrite; |
1217 | |
1218 | rx_edpkt_core_wl(priv: intf->parent, val: reg, ASP_EDPKT_SPARE_REG); |
1219 | |
1220 | usleep_range(min: 50, max: 100); |
1221 | } |
1222 | |
1223 | static struct bcmasp_hw_info v20_hw_info = { |
1224 | .rx_ctrl_flush = ASP_RX_CTRL_FLUSH, |
1225 | .umac2fb = UMAC2FB_OFFSET, |
1226 | .rx_ctrl_fb_out_frame_count = ASP_RX_CTRL_FB_OUT_FRAME_COUNT, |
1227 | .rx_ctrl_fb_filt_out_frame_count = ASP_RX_CTRL_FB_FILT_OUT_FRAME_COUNT, |
1228 | .rx_ctrl_fb_rx_fifo_depth = ASP_RX_CTRL_FB_RX_FIFO_DEPTH, |
1229 | }; |
1230 | |
1231 | static const struct bcmasp_plat_data v20_plat_data = { |
1232 | .init_wol = bcmasp_init_wol_per_intf, |
1233 | .enable_wol = bcmasp_enable_wol_per_intf, |
1234 | .destroy_wol = bcmasp_wol_irq_destroy_per_intf, |
1235 | .core_clock_select = bcmasp_core_clock_select_one, |
1236 | .hw_info = &v20_hw_info, |
1237 | }; |
1238 | |
1239 | static struct bcmasp_hw_info v21_hw_info = { |
1240 | .rx_ctrl_flush = ASP_RX_CTRL_FLUSH_2_1, |
1241 | .umac2fb = UMAC2FB_OFFSET_2_1, |
1242 | .rx_ctrl_fb_out_frame_count = ASP_RX_CTRL_FB_OUT_FRAME_COUNT_2_1, |
1243 | .rx_ctrl_fb_filt_out_frame_count = |
1244 | ASP_RX_CTRL_FB_FILT_OUT_FRAME_COUNT_2_1, |
1245 | .rx_ctrl_fb_rx_fifo_depth = ASP_RX_CTRL_FB_RX_FIFO_DEPTH_2_1, |
1246 | }; |
1247 | |
1248 | static const struct bcmasp_plat_data v21_plat_data = { |
1249 | .init_wol = bcmasp_init_wol_shared, |
1250 | .enable_wol = bcmasp_enable_wol_shared, |
1251 | .destroy_wol = bcmasp_wol_irq_destroy_shared, |
1252 | .core_clock_select = bcmasp_core_clock_select_one, |
1253 | .hw_info = &v21_hw_info, |
1254 | }; |
1255 | |
1256 | static const struct bcmasp_plat_data v22_plat_data = { |
1257 | .init_wol = bcmasp_init_wol_shared, |
1258 | .enable_wol = bcmasp_enable_wol_shared, |
1259 | .destroy_wol = bcmasp_wol_irq_destroy_shared, |
1260 | .core_clock_select = bcmasp_core_clock_select_many, |
1261 | .hw_info = &v21_hw_info, |
1262 | .eee_fixup = bcmasp_eee_fixup, |
1263 | }; |
1264 | |
1265 | static void bcmasp_set_pdata(struct bcmasp_priv *priv, const struct bcmasp_plat_data *pdata) |
1266 | { |
1267 | priv->init_wol = pdata->init_wol; |
1268 | priv->enable_wol = pdata->enable_wol; |
1269 | priv->destroy_wol = pdata->destroy_wol; |
1270 | priv->core_clock_select = pdata->core_clock_select; |
1271 | priv->eee_fixup = pdata->eee_fixup; |
1272 | priv->hw_info = pdata->hw_info; |
1273 | } |
1274 | |
1275 | static const struct of_device_id bcmasp_of_match[] = { |
1276 | { .compatible = "brcm,asp-v2.0" , .data = &v20_plat_data }, |
1277 | { .compatible = "brcm,asp-v2.1" , .data = &v21_plat_data }, |
1278 | { .compatible = "brcm,asp-v2.2" , .data = &v22_plat_data }, |
1279 | { /* sentinel */ }, |
1280 | }; |
1281 | MODULE_DEVICE_TABLE(of, bcmasp_of_match); |
1282 | |
1283 | static const struct of_device_id bcmasp_mdio_of_match[] = { |
1284 | { .compatible = "brcm,asp-v2.2-mdio" , }, |
1285 | { .compatible = "brcm,asp-v2.1-mdio" , }, |
1286 | { .compatible = "brcm,asp-v2.0-mdio" , }, |
1287 | { /* sentinel */ }, |
1288 | }; |
1289 | MODULE_DEVICE_TABLE(of, bcmasp_mdio_of_match); |
1290 | |
1291 | static void bcmasp_remove_intfs(struct bcmasp_priv *priv) |
1292 | { |
1293 | struct bcmasp_intf *intf, *n; |
1294 | |
1295 | list_for_each_entry_safe(intf, n, &priv->intfs, list) { |
1296 | list_del(entry: &intf->list); |
1297 | bcmasp_interface_destroy(intf); |
1298 | } |
1299 | } |
1300 | |
1301 | static int bcmasp_probe(struct platform_device *pdev) |
1302 | { |
1303 | struct device_node *ports_node, *intf_node; |
1304 | const struct bcmasp_plat_data *pdata; |
1305 | struct device *dev = &pdev->dev; |
1306 | struct bcmasp_priv *priv; |
1307 | struct bcmasp_intf *intf; |
1308 | int ret = 0, count = 0; |
1309 | unsigned int i; |
1310 | |
1311 | priv = devm_kzalloc(dev, size: sizeof(*priv), GFP_KERNEL); |
1312 | if (!priv) |
1313 | return -ENOMEM; |
1314 | |
1315 | priv->irq = platform_get_irq(pdev, 0); |
1316 | if (priv->irq <= 0) |
1317 | return -EINVAL; |
1318 | |
1319 | priv->clk = devm_clk_get_optional_enabled(dev, id: "sw_asp" ); |
1320 | if (IS_ERR(ptr: priv->clk)) |
1321 | return dev_err_probe(dev, err: PTR_ERR(ptr: priv->clk), |
1322 | fmt: "failed to request clock\n" ); |
1323 | |
1324 | /* Base from parent node */ |
1325 | priv->base = devm_platform_ioremap_resource(pdev, index: 0); |
1326 | if (IS_ERR(ptr: priv->base)) |
1327 | return dev_err_probe(dev, err: PTR_ERR(ptr: priv->base), fmt: "failed to iomap\n" ); |
1328 | |
1329 | ret = dma_set_mask_and_coherent(dev: &pdev->dev, DMA_BIT_MASK(40)); |
1330 | if (ret) |
1331 | return dev_err_probe(dev, err: ret, fmt: "unable to set DMA mask: %d\n" , ret); |
1332 | |
1333 | dev_set_drvdata(dev: &pdev->dev, data: priv); |
1334 | priv->pdev = pdev; |
1335 | spin_lock_init(&priv->mda_lock); |
1336 | spin_lock_init(&priv->clk_lock); |
1337 | mutex_init(&priv->wol_lock); |
1338 | mutex_init(&priv->net_lock); |
1339 | INIT_LIST_HEAD(list: &priv->intfs); |
1340 | |
1341 | pdata = device_get_match_data(dev: &pdev->dev); |
1342 | if (!pdata) |
1343 | return dev_err_probe(dev, err: -EINVAL, fmt: "unable to find platform data\n" ); |
1344 | |
1345 | bcmasp_set_pdata(priv, pdata); |
1346 | |
1347 | /* Enable all clocks to ensure successful probing */ |
1348 | bcmasp_core_clock_set(priv, ASP_CTRL_CLOCK_CTRL_ASP_ALL_DISABLE, set: 0); |
1349 | |
1350 | /* Switch to the main clock */ |
1351 | priv->core_clock_select(priv, false); |
1352 | |
1353 | bcmasp_intr2_mask_set_all(priv); |
1354 | bcmasp_intr2_clear_all(priv); |
1355 | |
1356 | ret = devm_request_irq(dev: &pdev->dev, irq: priv->irq, handler: bcmasp_isr, irqflags: 0, |
1357 | devname: pdev->name, dev_id: priv); |
1358 | if (ret) |
1359 | return dev_err_probe(dev, err: ret, fmt: "failed to request ASP interrupt: %d" , ret); |
1360 | |
1361 | /* Register mdio child nodes */ |
1362 | of_platform_populate(root: dev->of_node, matches: bcmasp_mdio_of_match, NULL, parent: dev); |
1363 | |
1364 | /* ASP specific initialization, Needs to be done regardless of |
1365 | * how many interfaces come up. |
1366 | */ |
1367 | bcmasp_core_init(priv); |
1368 | bcmasp_core_init_filters(priv); |
1369 | |
1370 | ports_node = of_find_node_by_name(from: dev->of_node, name: "ethernet-ports" ); |
1371 | if (!ports_node) { |
1372 | dev_warn(dev, "No ports found\n" ); |
1373 | return -EINVAL; |
1374 | } |
1375 | |
1376 | i = 0; |
1377 | for_each_available_child_of_node(ports_node, intf_node) { |
1378 | intf = bcmasp_interface_create(priv, ndev_dn: intf_node, i); |
1379 | if (!intf) { |
1380 | dev_err(dev, "Cannot create eth interface %d\n" , i); |
1381 | bcmasp_remove_intfs(priv); |
1382 | of_node_put(node: intf_node); |
1383 | goto of_put_exit; |
1384 | } |
1385 | list_add_tail(new: &intf->list, head: &priv->intfs); |
1386 | i++; |
1387 | } |
1388 | |
1389 | /* Check and enable WoL */ |
1390 | priv->init_wol(priv); |
1391 | |
1392 | /* Drop the clock reference count now and let ndo_open()/ndo_close() |
1393 | * manage it for us from now on. |
1394 | */ |
1395 | bcmasp_core_clock_set(priv, clr: 0, ASP_CTRL_CLOCK_CTRL_ASP_ALL_DISABLE); |
1396 | |
1397 | clk_disable_unprepare(clk: priv->clk); |
1398 | |
1399 | /* Now do the registration of the network ports which will take care |
1400 | * of managing the clock properly. |
1401 | */ |
1402 | list_for_each_entry(intf, &priv->intfs, list) { |
1403 | ret = register_netdev(dev: intf->ndev); |
1404 | if (ret) { |
1405 | netdev_err(dev: intf->ndev, |
1406 | format: "failed to register net_device: %d\n" , ret); |
1407 | priv->destroy_wol(priv); |
1408 | bcmasp_remove_intfs(priv); |
1409 | goto of_put_exit; |
1410 | } |
1411 | count++; |
1412 | } |
1413 | |
1414 | dev_info(dev, "Initialized %d port(s)\n" , count); |
1415 | |
1416 | of_put_exit: |
1417 | of_node_put(node: ports_node); |
1418 | return ret; |
1419 | } |
1420 | |
1421 | static void bcmasp_remove(struct platform_device *pdev) |
1422 | { |
1423 | struct bcmasp_priv *priv = dev_get_drvdata(dev: &pdev->dev); |
1424 | |
1425 | if (!priv) |
1426 | return; |
1427 | |
1428 | priv->destroy_wol(priv); |
1429 | bcmasp_remove_intfs(priv); |
1430 | } |
1431 | |
1432 | static void bcmasp_shutdown(struct platform_device *pdev) |
1433 | { |
1434 | bcmasp_remove(pdev); |
1435 | } |
1436 | |
1437 | static int __maybe_unused bcmasp_suspend(struct device *d) |
1438 | { |
1439 | struct bcmasp_priv *priv = dev_get_drvdata(dev: d); |
1440 | struct bcmasp_intf *intf; |
1441 | int ret; |
1442 | |
1443 | list_for_each_entry(intf, &priv->intfs, list) { |
1444 | ret = bcmasp_interface_suspend(intf); |
1445 | if (ret) |
1446 | break; |
1447 | } |
1448 | |
1449 | ret = clk_prepare_enable(clk: priv->clk); |
1450 | if (ret) |
1451 | return ret; |
1452 | |
1453 | /* Whether Wake-on-LAN is enabled or not, we can always disable |
1454 | * the shared TX clock |
1455 | */ |
1456 | bcmasp_core_clock_set(priv, clr: 0, ASP_CTRL_CLOCK_CTRL_ASP_TX_DISABLE); |
1457 | |
1458 | priv->core_clock_select(priv, true); |
1459 | |
1460 | clk_disable_unprepare(clk: priv->clk); |
1461 | |
1462 | return ret; |
1463 | } |
1464 | |
1465 | static int __maybe_unused bcmasp_resume(struct device *d) |
1466 | { |
1467 | struct bcmasp_priv *priv = dev_get_drvdata(dev: d); |
1468 | struct bcmasp_intf *intf; |
1469 | int ret; |
1470 | |
1471 | ret = clk_prepare_enable(clk: priv->clk); |
1472 | if (ret) |
1473 | return ret; |
1474 | |
1475 | /* Switch to the main clock domain */ |
1476 | priv->core_clock_select(priv, false); |
1477 | |
1478 | /* Re-enable all clocks for re-initialization */ |
1479 | bcmasp_core_clock_set(priv, ASP_CTRL_CLOCK_CTRL_ASP_ALL_DISABLE, set: 0); |
1480 | |
1481 | bcmasp_core_init(priv); |
1482 | bcmasp_core_init_filters(priv); |
1483 | |
1484 | /* And disable them to let the network devices take care of them */ |
1485 | bcmasp_core_clock_set(priv, clr: 0, ASP_CTRL_CLOCK_CTRL_ASP_ALL_DISABLE); |
1486 | |
1487 | clk_disable_unprepare(clk: priv->clk); |
1488 | |
1489 | list_for_each_entry(intf, &priv->intfs, list) { |
1490 | ret = bcmasp_interface_resume(intf); |
1491 | if (ret) |
1492 | break; |
1493 | } |
1494 | |
1495 | return ret; |
1496 | } |
1497 | |
1498 | static SIMPLE_DEV_PM_OPS(bcmasp_pm_ops, |
1499 | bcmasp_suspend, bcmasp_resume); |
1500 | |
1501 | static struct platform_driver bcmasp_driver = { |
1502 | .probe = bcmasp_probe, |
1503 | .remove_new = bcmasp_remove, |
1504 | .shutdown = bcmasp_shutdown, |
1505 | .driver = { |
1506 | .name = "brcm,asp-v2" , |
1507 | .of_match_table = bcmasp_of_match, |
1508 | .pm = &bcmasp_pm_ops, |
1509 | }, |
1510 | }; |
1511 | module_platform_driver(bcmasp_driver); |
1512 | |
1513 | MODULE_DESCRIPTION("Broadcom ASP 2.0 Ethernet controller driver" ); |
1514 | MODULE_ALIAS("platform:brcm,asp-v2" ); |
1515 | MODULE_LICENSE("GPL" ); |
1516 | |