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 | #include "sparx5_port.h" |
10 | #include "sparx5_tc.h" |
11 | |
12 | /* The IFH bit position of the first VSTAX bit. This is because the |
13 | * VSTAX bit positions in Data sheet is starting from zero. |
14 | */ |
15 | #define VSTAX 73 |
16 | |
17 | #define ifh_encode_bitfield(ifh, value, pos, _width) \ |
18 | ({ \ |
19 | u32 width = (_width); \ |
20 | \ |
21 | /* Max width is 5 bytes - 40 bits. In worst case this will |
22 | * spread over 6 bytes - 48 bits |
23 | */ \ |
24 | compiletime_assert(width <= 40, \ |
25 | "Unsupported width, must be <= 40"); \ |
26 | __ifh_encode_bitfield((ifh), (value), (pos), width); \ |
27 | }) |
28 | |
29 | static void __ifh_encode_bitfield(void *ifh, u64 value, u32 pos, u32 width) |
30 | { |
31 | u8 *ifh_hdr = ifh; |
32 | /* Calculate the Start IFH byte position of this IFH bit position */ |
33 | u32 byte = (35 - (pos / 8)); |
34 | /* Calculate the Start bit position in the Start IFH byte */ |
35 | u32 bit = (pos % 8); |
36 | u64 encode = GENMASK_ULL(bit + width - 1, bit) & (value << bit); |
37 | |
38 | /* The b0-b7 goes into the start IFH byte */ |
39 | if (encode & 0xFF) |
40 | ifh_hdr[byte] |= (u8)((encode & 0xFF)); |
41 | /* The b8-b15 goes into the next IFH byte */ |
42 | if (encode & 0xFF00) |
43 | ifh_hdr[byte - 1] |= (u8)((encode & 0xFF00) >> 8); |
44 | /* The b16-b23 goes into the next IFH byte */ |
45 | if (encode & 0xFF0000) |
46 | ifh_hdr[byte - 2] |= (u8)((encode & 0xFF0000) >> 16); |
47 | /* The b24-b31 goes into the next IFH byte */ |
48 | if (encode & 0xFF000000) |
49 | ifh_hdr[byte - 3] |= (u8)((encode & 0xFF000000) >> 24); |
50 | /* The b32-b39 goes into the next IFH byte */ |
51 | if (encode & 0xFF00000000) |
52 | ifh_hdr[byte - 4] |= (u8)((encode & 0xFF00000000) >> 32); |
53 | /* The b40-b47 goes into the next IFH byte */ |
54 | if (encode & 0xFF0000000000) |
55 | ifh_hdr[byte - 5] |= (u8)((encode & 0xFF0000000000) >> 40); |
56 | } |
57 | |
58 | void sparx5_set_port_ifh(void *ifh_hdr, u16 portno) |
59 | { |
60 | /* VSTAX.RSV = 1. MSBit must be 1 */ |
61 | ifh_encode_bitfield(ifh_hdr, 1, VSTAX + 79, 1); |
62 | /* VSTAX.INGR_DROP_MODE = Enable. Don't make head-of-line blocking */ |
63 | ifh_encode_bitfield(ifh_hdr, 1, VSTAX + 55, 1); |
64 | /* MISC.CPU_MASK/DPORT = Destination port */ |
65 | ifh_encode_bitfield(ifh_hdr, portno, 29, 8); |
66 | /* MISC.PIPELINE_PT */ |
67 | ifh_encode_bitfield(ifh_hdr, 16, 37, 5); |
68 | /* MISC.PIPELINE_ACT */ |
69 | ifh_encode_bitfield(ifh_hdr, 1, 42, 3); |
70 | /* FWD.SRC_PORT = CPU */ |
71 | ifh_encode_bitfield(ifh_hdr, SPX5_PORT_CPU, 46, 7); |
72 | /* FWD.SFLOW_ID (disable SFlow sampling) */ |
73 | ifh_encode_bitfield(ifh_hdr, 124, 57, 7); |
74 | /* FWD.UPDATE_FCS = Enable. Enforce update of FCS. */ |
75 | ifh_encode_bitfield(ifh_hdr, 1, 67, 1); |
76 | } |
77 | |
78 | void sparx5_set_port_ifh_rew_op(void *ifh_hdr, u32 rew_op) |
79 | { |
80 | ifh_encode_bitfield(ifh_hdr, rew_op, VSTAX + 32, 10); |
81 | } |
82 | |
83 | void sparx5_set_port_ifh_pdu_type(void *ifh_hdr, u32 pdu_type) |
84 | { |
85 | ifh_encode_bitfield(ifh_hdr, pdu_type, 191, 4); |
86 | } |
87 | |
88 | void sparx5_set_port_ifh_pdu_w16_offset(void *ifh_hdr, u32 pdu_w16_offset) |
89 | { |
90 | ifh_encode_bitfield(ifh_hdr, pdu_w16_offset, 195, 6); |
91 | } |
92 | |
93 | void sparx5_set_port_ifh_timestamp(void *ifh_hdr, u64 timestamp) |
94 | { |
95 | ifh_encode_bitfield(ifh_hdr, timestamp, 232, 40); |
96 | } |
97 | |
98 | static int sparx5_port_open(struct net_device *ndev) |
99 | { |
100 | struct sparx5_port *port = netdev_priv(dev: ndev); |
101 | int err = 0; |
102 | |
103 | sparx5_port_enable(port, enable: true); |
104 | err = phylink_of_phy_connect(port->phylink, port->of_node, flags: 0); |
105 | if (err) { |
106 | netdev_err(dev: ndev, format: "Could not attach to PHY\n" ); |
107 | goto err_connect; |
108 | } |
109 | |
110 | phylink_start(port->phylink); |
111 | |
112 | if (!ndev->phydev) { |
113 | /* power up serdes */ |
114 | port->conf.power_down = false; |
115 | if (port->conf.serdes_reset) |
116 | err = sparx5_serdes_set(sparx5: port->sparx5, spx5_port: port, conf: &port->conf); |
117 | else |
118 | err = phy_power_on(phy: port->serdes); |
119 | if (err) { |
120 | netdev_err(dev: ndev, format: "%s failed\n" , __func__); |
121 | goto out_power; |
122 | } |
123 | } |
124 | |
125 | return 0; |
126 | |
127 | out_power: |
128 | phylink_stop(port->phylink); |
129 | phylink_disconnect_phy(port->phylink); |
130 | err_connect: |
131 | sparx5_port_enable(port, enable: false); |
132 | |
133 | return err; |
134 | } |
135 | |
136 | static int sparx5_port_stop(struct net_device *ndev) |
137 | { |
138 | struct sparx5_port *port = netdev_priv(dev: ndev); |
139 | int err = 0; |
140 | |
141 | sparx5_port_enable(port, enable: false); |
142 | phylink_stop(port->phylink); |
143 | phylink_disconnect_phy(port->phylink); |
144 | |
145 | if (!ndev->phydev) { |
146 | /* power down serdes */ |
147 | port->conf.power_down = true; |
148 | if (port->conf.serdes_reset) |
149 | err = sparx5_serdes_set(sparx5: port->sparx5, spx5_port: port, conf: &port->conf); |
150 | else |
151 | err = phy_power_off(phy: port->serdes); |
152 | if (err) |
153 | netdev_err(dev: ndev, format: "%s failed\n" , __func__); |
154 | } |
155 | return 0; |
156 | } |
157 | |
158 | static void sparx5_set_rx_mode(struct net_device *dev) |
159 | { |
160 | struct sparx5_port *port = netdev_priv(dev); |
161 | struct sparx5 *sparx5 = port->sparx5; |
162 | |
163 | if (!test_bit(port->portno, sparx5->bridge_mask)) |
164 | __dev_mc_sync(dev, sync: sparx5_mc_sync, unsync: sparx5_mc_unsync); |
165 | } |
166 | |
167 | static int sparx5_port_get_phys_port_name(struct net_device *dev, |
168 | char *buf, size_t len) |
169 | { |
170 | struct sparx5_port *port = netdev_priv(dev); |
171 | int ret; |
172 | |
173 | ret = snprintf(buf, size: len, fmt: "p%d" , port->portno); |
174 | if (ret >= len) |
175 | return -EINVAL; |
176 | |
177 | return 0; |
178 | } |
179 | |
180 | static int sparx5_set_mac_address(struct net_device *dev, void *p) |
181 | { |
182 | struct sparx5_port *port = netdev_priv(dev); |
183 | struct sparx5 *sparx5 = port->sparx5; |
184 | const struct sockaddr *addr = p; |
185 | |
186 | if (!is_valid_ether_addr(addr: addr->sa_data)) |
187 | return -EADDRNOTAVAIL; |
188 | |
189 | /* Remove current */ |
190 | sparx5_mact_forget(sparx5, mac: dev->dev_addr, vid: port->pvid); |
191 | |
192 | /* Add new */ |
193 | sparx5_mact_learn(sparx5, PGID_CPU, mac: addr->sa_data, vid: port->pvid); |
194 | |
195 | /* Record the address */ |
196 | eth_hw_addr_set(dev, addr: addr->sa_data); |
197 | |
198 | return 0; |
199 | } |
200 | |
201 | static int sparx5_get_port_parent_id(struct net_device *dev, |
202 | struct netdev_phys_item_id *ppid) |
203 | { |
204 | struct sparx5_port *sparx5_port = netdev_priv(dev); |
205 | struct sparx5 *sparx5 = sparx5_port->sparx5; |
206 | |
207 | ppid->id_len = sizeof(sparx5->base_mac); |
208 | memcpy(&ppid->id, &sparx5->base_mac, ppid->id_len); |
209 | |
210 | return 0; |
211 | } |
212 | |
213 | static int sparx5_port_hwtstamp_get(struct net_device *dev, |
214 | struct kernel_hwtstamp_config *cfg) |
215 | { |
216 | struct sparx5_port *sparx5_port = netdev_priv(dev); |
217 | struct sparx5 *sparx5 = sparx5_port->sparx5; |
218 | |
219 | if (!sparx5->ptp) |
220 | return -EOPNOTSUPP; |
221 | |
222 | sparx5_ptp_hwtstamp_get(port: sparx5_port, cfg); |
223 | |
224 | return 0; |
225 | } |
226 | |
227 | static int sparx5_port_hwtstamp_set(struct net_device *dev, |
228 | struct kernel_hwtstamp_config *cfg, |
229 | struct netlink_ext_ack *extack) |
230 | { |
231 | struct sparx5_port *sparx5_port = netdev_priv(dev); |
232 | struct sparx5 *sparx5 = sparx5_port->sparx5; |
233 | |
234 | if (!sparx5->ptp) |
235 | return -EOPNOTSUPP; |
236 | |
237 | return sparx5_ptp_hwtstamp_set(port: sparx5_port, cfg, extack); |
238 | } |
239 | |
240 | static const struct net_device_ops sparx5_port_netdev_ops = { |
241 | .ndo_open = sparx5_port_open, |
242 | .ndo_stop = sparx5_port_stop, |
243 | .ndo_start_xmit = sparx5_port_xmit_impl, |
244 | .ndo_set_rx_mode = sparx5_set_rx_mode, |
245 | .ndo_get_phys_port_name = sparx5_port_get_phys_port_name, |
246 | .ndo_set_mac_address = sparx5_set_mac_address, |
247 | .ndo_validate_addr = eth_validate_addr, |
248 | .ndo_get_stats64 = sparx5_get_stats64, |
249 | .ndo_get_port_parent_id = sparx5_get_port_parent_id, |
250 | .ndo_eth_ioctl = phy_do_ioctl, |
251 | .ndo_setup_tc = sparx5_port_setup_tc, |
252 | .ndo_hwtstamp_get = sparx5_port_hwtstamp_get, |
253 | .ndo_hwtstamp_set = sparx5_port_hwtstamp_set, |
254 | }; |
255 | |
256 | bool sparx5_netdevice_check(const struct net_device *dev) |
257 | { |
258 | return dev && (dev->netdev_ops == &sparx5_port_netdev_ops); |
259 | } |
260 | |
261 | struct net_device *sparx5_create_netdev(struct sparx5 *sparx5, u32 portno) |
262 | { |
263 | struct sparx5_port *spx5_port; |
264 | struct net_device *ndev; |
265 | |
266 | ndev = devm_alloc_etherdev_mqs(dev: sparx5->dev, sizeof_priv: sizeof(struct sparx5_port), |
267 | SPX5_PRIOS, rxqs: 1); |
268 | if (!ndev) |
269 | return ERR_PTR(error: -ENOMEM); |
270 | |
271 | ndev->hw_features |= NETIF_F_HW_TC; |
272 | ndev->features |= NETIF_F_HW_TC; |
273 | |
274 | SET_NETDEV_DEV(ndev, sparx5->dev); |
275 | spx5_port = netdev_priv(dev: ndev); |
276 | spx5_port->ndev = ndev; |
277 | spx5_port->sparx5 = sparx5; |
278 | spx5_port->portno = portno; |
279 | |
280 | ndev->netdev_ops = &sparx5_port_netdev_ops; |
281 | ndev->ethtool_ops = &sparx5_ethtool_ops; |
282 | |
283 | eth_hw_addr_gen(dev: ndev, base_addr: sparx5->base_mac, id: portno + 1); |
284 | |
285 | return ndev; |
286 | } |
287 | |
288 | int sparx5_register_netdevs(struct sparx5 *sparx5) |
289 | { |
290 | int portno; |
291 | int err; |
292 | |
293 | for (portno = 0; portno < SPX5_PORTS; portno++) |
294 | if (sparx5->ports[portno]) { |
295 | err = register_netdev(dev: sparx5->ports[portno]->ndev); |
296 | if (err) { |
297 | dev_err(sparx5->dev, |
298 | "port: %02u: netdev registration failed\n" , |
299 | portno); |
300 | return err; |
301 | } |
302 | sparx5_port_inj_timer_setup(port: sparx5->ports[portno]); |
303 | } |
304 | return 0; |
305 | } |
306 | |
307 | void sparx5_destroy_netdevs(struct sparx5 *sparx5) |
308 | { |
309 | struct sparx5_port *port; |
310 | int portno; |
311 | |
312 | for (portno = 0; portno < SPX5_PORTS; portno++) { |
313 | port = sparx5->ports[portno]; |
314 | if (port && port->phylink) { |
315 | /* Disconnect the phy */ |
316 | rtnl_lock(); |
317 | sparx5_port_stop(ndev: port->ndev); |
318 | phylink_disconnect_phy(port->phylink); |
319 | rtnl_unlock(); |
320 | phylink_destroy(port->phylink); |
321 | port->phylink = NULL; |
322 | } |
323 | } |
324 | } |
325 | |
326 | void sparx5_unregister_netdevs(struct sparx5 *sparx5) |
327 | { |
328 | int portno; |
329 | |
330 | for (portno = 0; portno < SPX5_PORTS; portno++) |
331 | if (sparx5->ports[portno]) |
332 | unregister_netdev(dev: sparx5->ports[portno]->ndev); |
333 | } |
334 | |
335 | |