]>
Commit | Line | Data |
---|---|---|
78eab33b SH |
1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* Microchip Sparx5 Switch driver | |
3 | * | |
4 | * Copyright (c) 2021 Microchip Technology Inc. and its subsidiaries. | |
5 | */ | |
6 | ||
7 | #include "sparx5_main_regs.h" | |
8 | #include "sparx5_main.h" | |
9 | ||
10 | static int sparx5_vlant_set_mask(struct sparx5 *sparx5, u16 vid) | |
11 | { | |
12 | u32 mask[3]; | |
13 | ||
14 | /* Divide up mask in 32 bit words */ | |
15 | bitmap_to_arr32(mask, sparx5->vlan_mask[vid], SPX5_PORTS); | |
16 | ||
17 | /* Output mask to respective registers */ | |
18 | spx5_wr(mask[0], sparx5, ANA_L3_VLAN_MASK_CFG(vid)); | |
19 | spx5_wr(mask[1], sparx5, ANA_L3_VLAN_MASK_CFG1(vid)); | |
20 | spx5_wr(mask[2], sparx5, ANA_L3_VLAN_MASK_CFG2(vid)); | |
21 | ||
22 | return 0; | |
23 | } | |
24 | ||
25 | void sparx5_vlan_init(struct sparx5 *sparx5) | |
26 | { | |
27 | u16 vid; | |
28 | ||
29 | spx5_rmw(ANA_L3_VLAN_CTRL_VLAN_ENA_SET(1), | |
30 | ANA_L3_VLAN_CTRL_VLAN_ENA, | |
31 | sparx5, | |
32 | ANA_L3_VLAN_CTRL); | |
33 | ||
34 | /* Map VLAN = FID */ | |
35 | for (vid = NULL_VID; vid < VLAN_N_VID; vid++) | |
36 | spx5_rmw(ANA_L3_VLAN_CFG_VLAN_FID_SET(vid), | |
37 | ANA_L3_VLAN_CFG_VLAN_FID, | |
38 | sparx5, | |
39 | ANA_L3_VLAN_CFG(vid)); | |
40 | } | |
41 | ||
42 | void sparx5_vlan_port_setup(struct sparx5 *sparx5, int portno) | |
43 | { | |
44 | struct sparx5_port *port = sparx5->ports[portno]; | |
45 | ||
46 | /* Configure PVID */ | |
47 | spx5_rmw(ANA_CL_VLAN_CTRL_VLAN_AWARE_ENA_SET(0) | | |
48 | ANA_CL_VLAN_CTRL_PORT_VID_SET(port->pvid), | |
49 | ANA_CL_VLAN_CTRL_VLAN_AWARE_ENA | | |
50 | ANA_CL_VLAN_CTRL_PORT_VID, | |
51 | sparx5, | |
52 | ANA_CL_VLAN_CTRL(port->portno)); | |
53 | } | |
54 | ||
55 | int sparx5_vlan_vid_add(struct sparx5_port *port, u16 vid, bool pvid, | |
56 | bool untagged) | |
57 | { | |
58 | struct sparx5 *sparx5 = port->sparx5; | |
59 | int ret; | |
60 | ||
78eab33b SH |
61 | /* Untagged egress vlan classification */ |
62 | if (untagged && port->vid != vid) { | |
63 | if (port->vid) { | |
64 | netdev_err(port->ndev, | |
65 | "Port already has a native VLAN: %d\n", | |
66 | port->vid); | |
67 | return -EBUSY; | |
68 | } | |
69 | port->vid = vid; | |
70 | } | |
71 | ||
cf9f15d0 CA |
72 | /* Make the port a member of the VLAN */ |
73 | set_bit(port->portno, sparx5->vlan_mask[vid]); | |
74 | ret = sparx5_vlant_set_mask(sparx5, vid); | |
75 | if (ret) | |
76 | return ret; | |
77 | ||
78 | /* Default ingress vlan classification */ | |
79 | if (pvid) | |
80 | port->pvid = vid; | |
81 | ||
78eab33b SH |
82 | sparx5_vlan_port_apply(sparx5, port); |
83 | ||
84 | return 0; | |
85 | } | |
86 | ||
87 | int sparx5_vlan_vid_del(struct sparx5_port *port, u16 vid) | |
88 | { | |
89 | struct sparx5 *sparx5 = port->sparx5; | |
90 | int ret; | |
91 | ||
92 | /* 8021q removes VID 0 on module unload for all interfaces | |
93 | * with VLAN filtering feature. We need to keep it to receive | |
94 | * untagged traffic. | |
95 | */ | |
96 | if (vid == 0) | |
97 | return 0; | |
98 | ||
99 | /* Stop the port from being a member of the vlan */ | |
100 | clear_bit(port->portno, sparx5->vlan_mask[vid]); | |
101 | ret = sparx5_vlant_set_mask(sparx5, vid); | |
102 | if (ret) | |
103 | return ret; | |
104 | ||
105 | /* Ingress */ | |
106 | if (port->pvid == vid) | |
107 | port->pvid = 0; | |
108 | ||
109 | /* Egress */ | |
110 | if (port->vid == vid) | |
111 | port->vid = 0; | |
112 | ||
113 | sparx5_vlan_port_apply(sparx5, port); | |
114 | ||
115 | return 0; | |
116 | } | |
117 | ||
118 | void sparx5_pgid_update_mask(struct sparx5_port *port, int pgid, bool enable) | |
119 | { | |
120 | struct sparx5 *sparx5 = port->sparx5; | |
121 | u32 val, mask; | |
122 | ||
123 | /* mask is spread across 3 registers x 32 bit */ | |
124 | if (port->portno < 32) { | |
125 | mask = BIT(port->portno); | |
126 | val = enable ? mask : 0; | |
127 | spx5_rmw(val, mask, sparx5, ANA_AC_PGID_CFG(pgid)); | |
128 | } else if (port->portno < 64) { | |
129 | mask = BIT(port->portno - 32); | |
130 | val = enable ? mask : 0; | |
131 | spx5_rmw(val, mask, sparx5, ANA_AC_PGID_CFG1(pgid)); | |
132 | } else if (port->portno < SPX5_PORTS) { | |
133 | mask = BIT(port->portno - 64); | |
134 | val = enable ? mask : 0; | |
135 | spx5_rmw(val, mask, sparx5, ANA_AC_PGID_CFG2(pgid)); | |
136 | } else { | |
137 | netdev_err(port->ndev, "Invalid port no: %d\n", port->portno); | |
138 | } | |
139 | } | |
140 | ||
141 | void sparx5_update_fwd(struct sparx5 *sparx5) | |
142 | { | |
143 | DECLARE_BITMAP(workmask, SPX5_PORTS); | |
144 | u32 mask[3]; | |
145 | int port; | |
146 | ||
147 | /* Divide up fwd mask in 32 bit words */ | |
148 | bitmap_to_arr32(mask, sparx5->bridge_fwd_mask, SPX5_PORTS); | |
149 | ||
150 | /* Update flood masks */ | |
151 | for (port = PGID_UC_FLOOD; port <= PGID_BCAST; port++) { | |
152 | spx5_wr(mask[0], sparx5, ANA_AC_PGID_CFG(port)); | |
153 | spx5_wr(mask[1], sparx5, ANA_AC_PGID_CFG1(port)); | |
154 | spx5_wr(mask[2], sparx5, ANA_AC_PGID_CFG2(port)); | |
155 | } | |
156 | ||
157 | /* Update SRC masks */ | |
158 | for (port = 0; port < SPX5_PORTS; port++) { | |
159 | if (test_bit(port, sparx5->bridge_fwd_mask)) { | |
160 | /* Allow to send to all bridged but self */ | |
161 | bitmap_copy(workmask, sparx5->bridge_fwd_mask, SPX5_PORTS); | |
162 | clear_bit(port, workmask); | |
163 | bitmap_to_arr32(mask, workmask, SPX5_PORTS); | |
164 | spx5_wr(mask[0], sparx5, ANA_AC_SRC_CFG(port)); | |
165 | spx5_wr(mask[1], sparx5, ANA_AC_SRC_CFG1(port)); | |
166 | spx5_wr(mask[2], sparx5, ANA_AC_SRC_CFG2(port)); | |
167 | } else { | |
168 | spx5_wr(0, sparx5, ANA_AC_SRC_CFG(port)); | |
169 | spx5_wr(0, sparx5, ANA_AC_SRC_CFG1(port)); | |
170 | spx5_wr(0, sparx5, ANA_AC_SRC_CFG2(port)); | |
171 | } | |
172 | } | |
173 | ||
174 | /* Learning enabled only for bridged ports */ | |
175 | bitmap_and(workmask, sparx5->bridge_fwd_mask, | |
176 | sparx5->bridge_lrn_mask, SPX5_PORTS); | |
177 | bitmap_to_arr32(mask, workmask, SPX5_PORTS); | |
178 | ||
179 | /* Apply learning mask */ | |
180 | spx5_wr(mask[0], sparx5, ANA_L2_AUTO_LRN_CFG); | |
181 | spx5_wr(mask[1], sparx5, ANA_L2_AUTO_LRN_CFG1); | |
182 | spx5_wr(mask[2], sparx5, ANA_L2_AUTO_LRN_CFG2); | |
183 | } | |
184 | ||
185 | void sparx5_vlan_port_apply(struct sparx5 *sparx5, | |
186 | struct sparx5_port *port) | |
187 | ||
188 | { | |
189 | u32 val; | |
190 | ||
191 | /* Configure PVID, vlan aware */ | |
192 | val = ANA_CL_VLAN_CTRL_VLAN_AWARE_ENA_SET(port->vlan_aware) | | |
193 | ANA_CL_VLAN_CTRL_VLAN_POP_CNT_SET(port->vlan_aware) | | |
194 | ANA_CL_VLAN_CTRL_PORT_VID_SET(port->pvid); | |
195 | spx5_wr(val, sparx5, ANA_CL_VLAN_CTRL(port->portno)); | |
196 | ||
197 | val = 0; | |
198 | if (port->vlan_aware && !port->pvid) | |
199 | /* If port is vlan-aware and tagged, drop untagged and | |
200 | * priority tagged frames. | |
201 | */ | |
202 | val = ANA_CL_VLAN_FILTER_CTRL_TAG_REQUIRED_ENA_SET(1) | | |
203 | ANA_CL_VLAN_FILTER_CTRL_PRIO_CTAG_DIS_SET(1) | | |
204 | ANA_CL_VLAN_FILTER_CTRL_PRIO_STAG_DIS_SET(1); | |
205 | spx5_wr(val, sparx5, | |
206 | ANA_CL_VLAN_FILTER_CTRL(port->portno, 0)); | |
207 | ||
208 | /* Egress configuration (REW_TAG_CFG): VLAN tag type to 8021Q */ | |
209 | val = REW_TAG_CTRL_TAG_TPID_CFG_SET(0); | |
210 | if (port->vlan_aware) { | |
211 | if (port->vid) | |
212 | /* Tag all frames except when VID == DEFAULT_VLAN */ | |
213 | val |= REW_TAG_CTRL_TAG_CFG_SET(1); | |
214 | else | |
215 | val |= REW_TAG_CTRL_TAG_CFG_SET(3); | |
216 | } | |
217 | spx5_wr(val, sparx5, REW_TAG_CTRL(port->portno)); | |
218 | ||
219 | /* Egress VID */ | |
220 | spx5_rmw(REW_PORT_VLAN_CFG_PORT_VID_SET(port->vid), | |
221 | REW_PORT_VLAN_CFG_PORT_VID, | |
222 | sparx5, | |
223 | REW_PORT_VLAN_CFG(port->portno)); | |
224 | } |