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(buf: mask, bitmap: sparx5->vlan_mask[vid], SPX5_PORTS); |
16 | |
17 | /* Output mask to respective registers */ |
18 | spx5_wr(val: mask[0], sparx5, ANA_L3_VLAN_MASK_CFG(vid)); |
19 | spx5_wr(val: mask[1], sparx5, ANA_L3_VLAN_MASK_CFG1(vid)); |
20 | spx5_wr(val: 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 | |
61 | /* Untagged egress vlan classification */ |
62 | if (untagged && port->vid != vid) { |
63 | if (port->vid) { |
64 | netdev_err(dev: port->ndev, |
65 | format: "Port already has a native VLAN: %d\n" , |
66 | port->vid); |
67 | return -EBUSY; |
68 | } |
69 | port->vid = vid; |
70 | } |
71 | |
72 | /* Make the port a member of the VLAN */ |
73 | set_bit(nr: port->portno, addr: 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 | |
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(nr: port->portno, addr: 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(dev: port->ndev, format: "Invalid port no: %d\n" , port->portno); |
138 | } |
139 | } |
140 | |
141 | void sparx5_pgid_clear(struct sparx5 *spx5, int pgid) |
142 | { |
143 | spx5_wr(val: 0, sparx5: spx5, ANA_AC_PGID_CFG(pgid)); |
144 | spx5_wr(val: 0, sparx5: spx5, ANA_AC_PGID_CFG1(pgid)); |
145 | spx5_wr(val: 0, sparx5: spx5, ANA_AC_PGID_CFG2(pgid)); |
146 | } |
147 | |
148 | void sparx5_pgid_read_mask(struct sparx5 *spx5, int pgid, u32 portmask[3]) |
149 | { |
150 | portmask[0] = spx5_rd(sparx5: spx5, ANA_AC_PGID_CFG(pgid)); |
151 | portmask[1] = spx5_rd(sparx5: spx5, ANA_AC_PGID_CFG1(pgid)); |
152 | portmask[2] = spx5_rd(sparx5: spx5, ANA_AC_PGID_CFG2(pgid)); |
153 | } |
154 | |
155 | void sparx5_update_fwd(struct sparx5 *sparx5) |
156 | { |
157 | DECLARE_BITMAP(workmask, SPX5_PORTS); |
158 | u32 mask[3]; |
159 | int port; |
160 | |
161 | /* Divide up fwd mask in 32 bit words */ |
162 | bitmap_to_arr32(buf: mask, bitmap: sparx5->bridge_fwd_mask, SPX5_PORTS); |
163 | |
164 | /* Update flood masks */ |
165 | for (port = PGID_UC_FLOOD; port <= PGID_BCAST; port++) { |
166 | spx5_wr(val: mask[0], sparx5, ANA_AC_PGID_CFG(port)); |
167 | spx5_wr(val: mask[1], sparx5, ANA_AC_PGID_CFG1(port)); |
168 | spx5_wr(val: mask[2], sparx5, ANA_AC_PGID_CFG2(port)); |
169 | } |
170 | |
171 | /* Update SRC masks */ |
172 | for (port = 0; port < SPX5_PORTS; port++) { |
173 | if (test_bit(port, sparx5->bridge_fwd_mask)) { |
174 | /* Allow to send to all bridged but self */ |
175 | bitmap_copy(dst: workmask, src: sparx5->bridge_fwd_mask, SPX5_PORTS); |
176 | clear_bit(nr: port, addr: workmask); |
177 | bitmap_to_arr32(buf: mask, bitmap: workmask, SPX5_PORTS); |
178 | spx5_wr(val: mask[0], sparx5, ANA_AC_SRC_CFG(port)); |
179 | spx5_wr(val: mask[1], sparx5, ANA_AC_SRC_CFG1(port)); |
180 | spx5_wr(val: mask[2], sparx5, ANA_AC_SRC_CFG2(port)); |
181 | } else { |
182 | spx5_wr(val: 0, sparx5, ANA_AC_SRC_CFG(port)); |
183 | spx5_wr(val: 0, sparx5, ANA_AC_SRC_CFG1(port)); |
184 | spx5_wr(val: 0, sparx5, ANA_AC_SRC_CFG2(port)); |
185 | } |
186 | } |
187 | |
188 | /* Learning enabled only for bridged ports */ |
189 | bitmap_and(dst: workmask, src1: sparx5->bridge_fwd_mask, |
190 | src2: sparx5->bridge_lrn_mask, SPX5_PORTS); |
191 | bitmap_to_arr32(buf: mask, bitmap: workmask, SPX5_PORTS); |
192 | |
193 | /* Apply learning mask */ |
194 | spx5_wr(val: mask[0], sparx5, ANA_L2_AUTO_LRN_CFG); |
195 | spx5_wr(val: mask[1], sparx5, ANA_L2_AUTO_LRN_CFG1); |
196 | spx5_wr(val: mask[2], sparx5, ANA_L2_AUTO_LRN_CFG2); |
197 | } |
198 | |
199 | void sparx5_vlan_port_apply(struct sparx5 *sparx5, |
200 | struct sparx5_port *port) |
201 | |
202 | { |
203 | u32 val; |
204 | |
205 | /* Configure PVID, vlan aware */ |
206 | val = ANA_CL_VLAN_CTRL_VLAN_AWARE_ENA_SET(port->vlan_aware) | |
207 | ANA_CL_VLAN_CTRL_VLAN_POP_CNT_SET(port->vlan_aware) | |
208 | ANA_CL_VLAN_CTRL_PORT_VID_SET(port->pvid); |
209 | spx5_wr(val, sparx5, ANA_CL_VLAN_CTRL(port->portno)); |
210 | |
211 | val = 0; |
212 | if (port->vlan_aware && !port->pvid) |
213 | /* If port is vlan-aware and tagged, drop untagged and |
214 | * priority tagged frames. |
215 | */ |
216 | val = ANA_CL_VLAN_FILTER_CTRL_TAG_REQUIRED_ENA_SET(1) | |
217 | ANA_CL_VLAN_FILTER_CTRL_PRIO_CTAG_DIS_SET(1) | |
218 | ANA_CL_VLAN_FILTER_CTRL_PRIO_STAG_DIS_SET(1); |
219 | spx5_wr(val, sparx5, |
220 | ANA_CL_VLAN_FILTER_CTRL(port->portno, 0)); |
221 | |
222 | /* Egress configuration (REW_TAG_CFG): VLAN tag selected via IFH */ |
223 | val = REW_TAG_CTRL_TAG_TPID_CFG_SET(5); |
224 | if (port->vlan_aware) { |
225 | if (port->vid) |
226 | /* Tag all frames except when VID == DEFAULT_VLAN */ |
227 | val |= REW_TAG_CTRL_TAG_CFG_SET(1); |
228 | else |
229 | val |= REW_TAG_CTRL_TAG_CFG_SET(3); |
230 | } |
231 | spx5_wr(val, sparx5, REW_TAG_CTRL(port->portno)); |
232 | |
233 | /* Egress VID */ |
234 | spx5_rmw(REW_PORT_VLAN_CFG_PORT_VID_SET(port->vid), |
235 | REW_PORT_VLAN_CFG_PORT_VID, |
236 | sparx5, |
237 | REW_PORT_VLAN_CFG(port->portno)); |
238 | } |
239 | |