1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * This file is based on code from OCTEON SDK by Cavium Networks. |
4 | * |
5 | * Copyright (c) 2003-2007 Cavium Networks |
6 | */ |
7 | |
8 | #include <linux/kernel.h> |
9 | #include <linux/ethtool.h> |
10 | #include <linux/phy.h> |
11 | #include <linux/ratelimit.h> |
12 | #include <linux/of_mdio.h> |
13 | #include <net/dst.h> |
14 | |
15 | #include "octeon-ethernet.h" |
16 | #include "ethernet-defines.h" |
17 | #include "ethernet-mdio.h" |
18 | #include "ethernet-util.h" |
19 | |
20 | static void cvm_oct_get_drvinfo(struct net_device *dev, |
21 | struct ethtool_drvinfo *info) |
22 | { |
23 | strscpy(info->driver, KBUILD_MODNAME, sizeof(info->driver)); |
24 | strscpy(info->bus_info, "Builtin" , sizeof(info->bus_info)); |
25 | } |
26 | |
27 | static int cvm_oct_nway_reset(struct net_device *dev) |
28 | { |
29 | if (!capable(CAP_NET_ADMIN)) |
30 | return -EPERM; |
31 | |
32 | if (dev->phydev) |
33 | return phy_start_aneg(phydev: dev->phydev); |
34 | |
35 | return -EINVAL; |
36 | } |
37 | |
38 | const struct ethtool_ops cvm_oct_ethtool_ops = { |
39 | .get_drvinfo = cvm_oct_get_drvinfo, |
40 | .nway_reset = cvm_oct_nway_reset, |
41 | .get_link = ethtool_op_get_link, |
42 | .get_link_ksettings = phy_ethtool_get_link_ksettings, |
43 | .set_link_ksettings = phy_ethtool_set_link_ksettings, |
44 | }; |
45 | |
46 | /** |
47 | * cvm_oct_ioctl - IOCTL support for PHY control |
48 | * @dev: Device to change |
49 | * @rq: the request |
50 | * @cmd: the command |
51 | * |
52 | * Returns Zero on success |
53 | */ |
54 | int cvm_oct_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) |
55 | { |
56 | if (!netif_running(dev)) |
57 | return -EINVAL; |
58 | |
59 | if (!dev->phydev) |
60 | return -EINVAL; |
61 | |
62 | return phy_mii_ioctl(phydev: dev->phydev, ifr: rq, cmd); |
63 | } |
64 | |
65 | void cvm_oct_note_carrier(struct octeon_ethernet *priv, |
66 | union cvmx_helper_link_info li) |
67 | { |
68 | if (li.s.link_up) { |
69 | pr_notice_ratelimited("%s: %u Mbps %s duplex, port %d, queue %d\n" , |
70 | netdev_name(priv->netdev), li.s.speed, |
71 | (li.s.full_duplex) ? "Full" : "Half" , |
72 | priv->port, priv->queue); |
73 | } else { |
74 | pr_notice_ratelimited("%s: Link down\n" , |
75 | netdev_name(priv->netdev)); |
76 | } |
77 | } |
78 | |
79 | void cvm_oct_adjust_link(struct net_device *dev) |
80 | { |
81 | struct octeon_ethernet *priv = netdev_priv(dev); |
82 | union cvmx_helper_link_info link_info; |
83 | |
84 | link_info.u64 = 0; |
85 | link_info.s.link_up = dev->phydev->link ? 1 : 0; |
86 | link_info.s.full_duplex = dev->phydev->duplex ? 1 : 0; |
87 | link_info.s.speed = dev->phydev->speed; |
88 | priv->link_info = link_info.u64; |
89 | |
90 | /* |
91 | * The polling task need to know about link status changes. |
92 | */ |
93 | if (priv->poll) |
94 | priv->poll(dev); |
95 | |
96 | if (priv->last_link != dev->phydev->link) { |
97 | priv->last_link = dev->phydev->link; |
98 | cvmx_helper_link_set(ipd_port: priv->port, link_info); |
99 | cvm_oct_note_carrier(priv, li: link_info); |
100 | } |
101 | } |
102 | |
103 | int cvm_oct_common_stop(struct net_device *dev) |
104 | { |
105 | struct octeon_ethernet *priv = netdev_priv(dev); |
106 | int interface = INTERFACE(ipd_port: priv->port); |
107 | union cvmx_helper_link_info link_info; |
108 | union cvmx_gmxx_prtx_cfg gmx_cfg; |
109 | int index = INDEX(ipd_port: priv->port); |
110 | |
111 | gmx_cfg.u64 = cvmx_read_csr(CVMX_GMXX_PRTX_CFG(index, interface)); |
112 | gmx_cfg.s.en = 0; |
113 | cvmx_write_csr(CVMX_GMXX_PRTX_CFG(index, interface), val: gmx_cfg.u64); |
114 | |
115 | priv->poll = NULL; |
116 | |
117 | if (dev->phydev) |
118 | phy_disconnect(phydev: dev->phydev); |
119 | |
120 | if (priv->last_link) { |
121 | link_info.u64 = 0; |
122 | priv->last_link = 0; |
123 | |
124 | cvmx_helper_link_set(ipd_port: priv->port, link_info); |
125 | cvm_oct_note_carrier(priv, li: link_info); |
126 | } |
127 | return 0; |
128 | } |
129 | |
130 | /** |
131 | * cvm_oct_phy_setup_device - setup the PHY |
132 | * |
133 | * @dev: Device to setup |
134 | * |
135 | * Returns Zero on success, negative on failure |
136 | */ |
137 | int cvm_oct_phy_setup_device(struct net_device *dev) |
138 | { |
139 | struct octeon_ethernet *priv = netdev_priv(dev); |
140 | struct device_node *phy_node; |
141 | struct phy_device *phydev = NULL; |
142 | |
143 | if (!priv->of_node) |
144 | goto no_phy; |
145 | |
146 | phy_node = of_parse_phandle(np: priv->of_node, phandle_name: "phy-handle" , index: 0); |
147 | if (!phy_node && of_phy_is_fixed_link(np: priv->of_node)) |
148 | phy_node = of_node_get(node: priv->of_node); |
149 | if (!phy_node) |
150 | goto no_phy; |
151 | |
152 | phydev = of_phy_connect(dev, phy_np: phy_node, hndlr: cvm_oct_adjust_link, flags: 0, |
153 | iface: priv->phy_mode); |
154 | of_node_put(node: phy_node); |
155 | |
156 | if (!phydev) |
157 | return -EPROBE_DEFER; |
158 | |
159 | priv->last_link = 0; |
160 | phy_start(phydev); |
161 | |
162 | return 0; |
163 | no_phy: |
164 | /* If there is no phy, assume a direct MAC connection and that |
165 | * the link is up. |
166 | */ |
167 | netif_carrier_on(dev); |
168 | return 0; |
169 | } |
170 | |