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 <linux/module.h> |
8 | #include <linux/phylink.h> |
9 | #include <linux/device.h> |
10 | #include <linux/netdevice.h> |
11 | #include <linux/sfp.h> |
12 | |
13 | #include "sparx5_main_regs.h" |
14 | #include "sparx5_main.h" |
15 | #include "sparx5_port.h" |
16 | |
17 | static bool port_conf_has_changed(struct sparx5_port_config *a, struct sparx5_port_config *b) |
18 | { |
19 | if (a->speed != b->speed || |
20 | a->portmode != b->portmode || |
21 | a->autoneg != b->autoneg || |
22 | a->pause_adv != b->pause_adv || |
23 | a->power_down != b->power_down || |
24 | a->media != b->media) |
25 | return true; |
26 | return false; |
27 | } |
28 | |
29 | static struct phylink_pcs * |
30 | sparx5_phylink_mac_select_pcs(struct phylink_config *config, |
31 | phy_interface_t interface) |
32 | { |
33 | struct sparx5_port *port = netdev_priv(to_net_dev(config->dev)); |
34 | |
35 | return &port->phylink_pcs; |
36 | } |
37 | |
38 | static void sparx5_phylink_mac_config(struct phylink_config *config, |
39 | unsigned int mode, |
40 | const struct phylink_link_state *state) |
41 | { |
42 | /* Currently not used */ |
43 | } |
44 | |
45 | static void sparx5_phylink_mac_link_up(struct phylink_config *config, |
46 | struct phy_device *phy, |
47 | unsigned int mode, |
48 | phy_interface_t interface, |
49 | int speed, int duplex, |
50 | bool tx_pause, bool rx_pause) |
51 | { |
52 | struct sparx5_port *port = netdev_priv(to_net_dev(config->dev)); |
53 | struct sparx5_port_config conf; |
54 | int err; |
55 | |
56 | conf = port->conf; |
57 | conf.duplex = duplex; |
58 | conf.pause = 0; |
59 | conf.pause |= tx_pause ? MLO_PAUSE_TX : 0; |
60 | conf.pause |= rx_pause ? MLO_PAUSE_RX : 0; |
61 | conf.speed = speed; |
62 | /* Configure the port to speed/duplex/pause */ |
63 | err = sparx5_port_config(sparx5: port->sparx5, spx5_port: port, conf: &conf); |
64 | if (err) |
65 | netdev_err(dev: port->ndev, format: "port config failed: %d\n" , err); |
66 | } |
67 | |
68 | static void sparx5_phylink_mac_link_down(struct phylink_config *config, |
69 | unsigned int mode, |
70 | phy_interface_t interface) |
71 | { |
72 | /* Currently not used */ |
73 | } |
74 | |
75 | static struct sparx5_port *sparx5_pcs_to_port(struct phylink_pcs *pcs) |
76 | { |
77 | return container_of(pcs, struct sparx5_port, phylink_pcs); |
78 | } |
79 | |
80 | static void sparx5_pcs_get_state(struct phylink_pcs *pcs, |
81 | struct phylink_link_state *state) |
82 | { |
83 | struct sparx5_port *port = sparx5_pcs_to_port(pcs); |
84 | struct sparx5_port_status status; |
85 | |
86 | sparx5_get_port_status(sparx5: port->sparx5, port, status: &status); |
87 | state->link = status.link && !status.link_down; |
88 | state->an_complete = status.an_complete; |
89 | state->speed = status.speed; |
90 | state->duplex = status.duplex; |
91 | state->pause = status.pause; |
92 | } |
93 | |
94 | static int sparx5_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode, |
95 | phy_interface_t interface, |
96 | const unsigned long *advertising, |
97 | bool permit_pause_to_mac) |
98 | { |
99 | struct sparx5_port *port = sparx5_pcs_to_port(pcs); |
100 | struct sparx5_port_config conf; |
101 | int ret = 0; |
102 | |
103 | conf = port->conf; |
104 | conf.power_down = false; |
105 | conf.portmode = interface; |
106 | conf.inband = neg_mode == PHYLINK_PCS_NEG_INBAND_DISABLED || |
107 | neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED; |
108 | conf.autoneg = neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED; |
109 | conf.pause_adv = 0; |
110 | if (phylink_test(advertising, Pause)) |
111 | conf.pause_adv |= ADVERTISE_1000XPAUSE; |
112 | if (phylink_test(advertising, Asym_Pause)) |
113 | conf.pause_adv |= ADVERTISE_1000XPSE_ASYM; |
114 | if (sparx5_is_baser(interface)) { |
115 | if (phylink_test(advertising, FIBRE)) |
116 | conf.media = PHY_MEDIA_SR; |
117 | else |
118 | conf.media = PHY_MEDIA_DAC; |
119 | } |
120 | if (!port_conf_has_changed(a: &port->conf, b: &conf)) |
121 | return ret; |
122 | /* Enable the PCS matching this interface type */ |
123 | ret = sparx5_port_pcs_set(sparx5: port->sparx5, port, conf: &conf); |
124 | if (ret) |
125 | netdev_err(dev: port->ndev, format: "port PCS config failed: %d\n" , ret); |
126 | return ret; |
127 | } |
128 | |
129 | static void sparx5_pcs_aneg_restart(struct phylink_pcs *pcs) |
130 | { |
131 | /* Currently not used */ |
132 | } |
133 | |
134 | const struct phylink_pcs_ops sparx5_phylink_pcs_ops = { |
135 | .pcs_get_state = sparx5_pcs_get_state, |
136 | .pcs_config = sparx5_pcs_config, |
137 | .pcs_an_restart = sparx5_pcs_aneg_restart, |
138 | }; |
139 | |
140 | const struct phylink_mac_ops sparx5_phylink_mac_ops = { |
141 | .mac_select_pcs = sparx5_phylink_mac_select_pcs, |
142 | .mac_config = sparx5_phylink_mac_config, |
143 | .mac_link_down = sparx5_phylink_mac_link_down, |
144 | .mac_link_up = sparx5_phylink_mac_link_up, |
145 | }; |
146 | |