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