blob: 5e19e08b691ba4d2173d3339b2e0606da98cfdbc [file] [log] [blame]
Johannes Bergab69bde2013-06-17 22:44:02 +02001/*
2 * Copyright (c) 2013 Johannes Berg <johannes@sipsolutions.net>
3 *
4 * This file is free software: you may copy, redistribute and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation, either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This file is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * This file incorporates work covered by the following copyright and
18 * permission notice:
19 *
20 * Copyright (c) 2012 Qualcomm Atheros, Inc.
21 *
22 * Permission to use, copy, modify, and/or distribute this software for any
23 * purpose with or without fee is hereby granted, provided that the above
24 * copyright notice and this permission notice appear in all copies.
25 *
26 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
27 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
28 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
29 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
30 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
31 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
32 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
33 */
34
35#include <linux/pci.h>
36#include <linux/ip.h>
37#include <linux/tcp.h>
38#include <linux/netdevice.h>
39#include <linux/etherdevice.h>
40#include <linux/ethtool.h>
41#include <linux/mdio.h>
42#include <linux/interrupt.h>
43#include <asm/byteorder.h>
44
45#include "alx.h"
46#include "reg.h"
47#include "hw.h"
48
49
50static int alx_get_settings(struct net_device *netdev, struct ethtool_cmd *ecmd)
51{
52 struct alx_priv *alx = netdev_priv(netdev);
53 struct alx_hw *hw = &alx->hw;
54
55 ecmd->supported = SUPPORTED_10baseT_Half |
56 SUPPORTED_10baseT_Full |
57 SUPPORTED_100baseT_Half |
58 SUPPORTED_100baseT_Full |
59 SUPPORTED_Autoneg |
60 SUPPORTED_TP |
61 SUPPORTED_Pause;
62 if (alx_hw_giga(hw))
63 ecmd->supported |= SUPPORTED_1000baseT_Full;
64
65 ecmd->advertising = ADVERTISED_TP;
66 if (hw->adv_cfg & ADVERTISED_Autoneg)
67 ecmd->advertising |= hw->adv_cfg;
68
69 ecmd->port = PORT_TP;
70 ecmd->phy_address = 0;
71 if (hw->adv_cfg & ADVERTISED_Autoneg)
72 ecmd->autoneg = AUTONEG_ENABLE;
73 else
74 ecmd->autoneg = AUTONEG_DISABLE;
75 ecmd->transceiver = XCVR_INTERNAL;
76
77 if (hw->flowctrl & ALX_FC_ANEG && hw->adv_cfg & ADVERTISED_Autoneg) {
78 if (hw->flowctrl & ALX_FC_RX) {
79 ecmd->advertising |= ADVERTISED_Pause;
80
81 if (!(hw->flowctrl & ALX_FC_TX))
82 ecmd->advertising |= ADVERTISED_Asym_Pause;
83 } else if (hw->flowctrl & ALX_FC_TX) {
84 ecmd->advertising |= ADVERTISED_Asym_Pause;
85 }
86 }
87
Johannes Berga5b87cc2013-06-29 19:23:17 +020088 ethtool_cmd_speed_set(ecmd, hw->link_speed);
89 ecmd->duplex = hw->duplex;
Johannes Bergab69bde2013-06-17 22:44:02 +020090
91 return 0;
92}
93
94static int alx_set_settings(struct net_device *netdev, struct ethtool_cmd *ecmd)
95{
96 struct alx_priv *alx = netdev_priv(netdev);
97 struct alx_hw *hw = &alx->hw;
98 u32 adv_cfg;
99
100 ASSERT_RTNL();
101
102 if (ecmd->autoneg == AUTONEG_ENABLE) {
103 if (ecmd->advertising & ADVERTISED_1000baseT_Half)
104 return -EINVAL;
105 adv_cfg = ecmd->advertising | ADVERTISED_Autoneg;
106 } else {
Johannes Berga5b87cc2013-06-29 19:23:17 +0200107 adv_cfg = alx_speed_to_ethadv(ethtool_cmd_speed(ecmd),
108 ecmd->duplex);
Johannes Bergab69bde2013-06-17 22:44:02 +0200109
Johannes Berga5b87cc2013-06-29 19:23:17 +0200110 if (!adv_cfg || adv_cfg == ADVERTISED_1000baseT_Full)
Johannes Bergab69bde2013-06-17 22:44:02 +0200111 return -EINVAL;
Johannes Bergab69bde2013-06-17 22:44:02 +0200112 }
113
114 hw->adv_cfg = adv_cfg;
115 return alx_setup_speed_duplex(hw, adv_cfg, hw->flowctrl);
116}
117
118static void alx_get_pauseparam(struct net_device *netdev,
119 struct ethtool_pauseparam *pause)
120{
121 struct alx_priv *alx = netdev_priv(netdev);
122 struct alx_hw *hw = &alx->hw;
123
124 if (hw->flowctrl & ALX_FC_ANEG &&
125 hw->adv_cfg & ADVERTISED_Autoneg)
126 pause->autoneg = AUTONEG_ENABLE;
127 else
128 pause->autoneg = AUTONEG_DISABLE;
129
130 if (hw->flowctrl & ALX_FC_TX)
131 pause->tx_pause = 1;
132 else
133 pause->tx_pause = 0;
134
135 if (hw->flowctrl & ALX_FC_RX)
136 pause->rx_pause = 1;
137 else
138 pause->rx_pause = 0;
139}
140
141
142static int alx_set_pauseparam(struct net_device *netdev,
143 struct ethtool_pauseparam *pause)
144{
145 struct alx_priv *alx = netdev_priv(netdev);
146 struct alx_hw *hw = &alx->hw;
147 int err = 0;
148 bool reconfig_phy = false;
149 u8 fc = 0;
150
151 if (pause->tx_pause)
152 fc |= ALX_FC_TX;
153 if (pause->rx_pause)
154 fc |= ALX_FC_RX;
155 if (pause->autoneg)
156 fc |= ALX_FC_ANEG;
157
158 ASSERT_RTNL();
159
160 /* restart auto-neg for auto-mode */
161 if (hw->adv_cfg & ADVERTISED_Autoneg) {
162 if (!((fc ^ hw->flowctrl) & ALX_FC_ANEG))
163 reconfig_phy = true;
164 if (fc & hw->flowctrl & ALX_FC_ANEG &&
165 (fc ^ hw->flowctrl) & (ALX_FC_RX | ALX_FC_TX))
166 reconfig_phy = true;
167 }
168
169 if (reconfig_phy) {
170 err = alx_setup_speed_duplex(hw, hw->adv_cfg, fc);
Johannes Bergef0cc4b2013-06-29 19:23:13 +0200171 if (err)
172 return err;
Johannes Bergab69bde2013-06-17 22:44:02 +0200173 }
174
175 /* flow control on mac */
176 if ((fc ^ hw->flowctrl) & (ALX_FC_RX | ALX_FC_TX))
177 alx_cfg_mac_flowcontrol(hw, fc);
178
179 hw->flowctrl = fc;
180
181 return 0;
182}
183
184static u32 alx_get_msglevel(struct net_device *netdev)
185{
186 struct alx_priv *alx = netdev_priv(netdev);
187
188 return alx->msg_enable;
189}
190
191static void alx_set_msglevel(struct net_device *netdev, u32 data)
192{
193 struct alx_priv *alx = netdev_priv(netdev);
194
195 alx->msg_enable = data;
196}
197
198static void alx_get_wol(struct net_device *netdev, struct ethtool_wolinfo *wol)
199{
200 struct alx_priv *alx = netdev_priv(netdev);
201 struct alx_hw *hw = &alx->hw;
202
203 wol->supported = WAKE_MAGIC | WAKE_PHY;
204 wol->wolopts = 0;
205
206 if (hw->sleep_ctrl & ALX_SLEEP_WOL_MAGIC)
207 wol->wolopts |= WAKE_MAGIC;
208 if (hw->sleep_ctrl & ALX_SLEEP_WOL_PHY)
209 wol->wolopts |= WAKE_PHY;
210}
211
212static int alx_set_wol(struct net_device *netdev, struct ethtool_wolinfo *wol)
213{
214 struct alx_priv *alx = netdev_priv(netdev);
215 struct alx_hw *hw = &alx->hw;
216
217 if (wol->wolopts & (WAKE_ARP | WAKE_MAGICSECURE |
218 WAKE_UCAST | WAKE_BCAST | WAKE_MCAST))
219 return -EOPNOTSUPP;
220
221 hw->sleep_ctrl = 0;
222
223 if (wol->wolopts & WAKE_MAGIC)
224 hw->sleep_ctrl |= ALX_SLEEP_WOL_MAGIC;
225 if (wol->wolopts & WAKE_PHY)
226 hw->sleep_ctrl |= ALX_SLEEP_WOL_PHY;
227
228 device_set_wakeup_enable(&alx->hw.pdev->dev, hw->sleep_ctrl);
229
230 return 0;
231}
232
233static void alx_get_drvinfo(struct net_device *netdev,
234 struct ethtool_drvinfo *drvinfo)
235{
236 struct alx_priv *alx = netdev_priv(netdev);
237
238 strlcpy(drvinfo->driver, alx_drv_name, sizeof(drvinfo->driver));
239 strlcpy(drvinfo->bus_info, pci_name(alx->hw.pdev),
240 sizeof(drvinfo->bus_info));
241}
242
243const struct ethtool_ops alx_ethtool_ops = {
244 .get_settings = alx_get_settings,
245 .set_settings = alx_set_settings,
246 .get_pauseparam = alx_get_pauseparam,
247 .set_pauseparam = alx_set_pauseparam,
248 .get_drvinfo = alx_get_drvinfo,
249 .get_msglevel = alx_get_msglevel,
250 .set_msglevel = alx_set_msglevel,
251 .get_wol = alx_get_wol,
252 .set_wol = alx_set_wol,
253 .get_link = ethtool_op_get_link,
254};