diff options
Diffstat (limited to 'net/dsa/slave.c')
-rw-r--r-- | net/dsa/slave.c | 288 |
1 files changed, 288 insertions, 0 deletions
diff --git a/net/dsa/slave.c b/net/dsa/slave.c new file mode 100644 index 000000000000..3cb331e98b89 --- /dev/null +++ b/net/dsa/slave.c | |||
@@ -0,0 +1,288 @@ | |||
1 | /* | ||
2 | * net/dsa/slave.c - Slave device handling | ||
3 | * Copyright (c) 2008 Marvell Semiconductor | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify | ||
6 | * it under the terms of the GNU General Public License as published by | ||
7 | * the Free Software Foundation; either version 2 of the License, or | ||
8 | * (at your option) any later version. | ||
9 | */ | ||
10 | |||
11 | #include <linux/list.h> | ||
12 | #include <linux/netdevice.h> | ||
13 | #include <linux/phy.h> | ||
14 | #include "dsa_priv.h" | ||
15 | |||
16 | /* slave mii_bus handling ***************************************************/ | ||
17 | static int dsa_slave_phy_read(struct mii_bus *bus, int addr, int reg) | ||
18 | { | ||
19 | struct dsa_switch *ds = bus->priv; | ||
20 | |||
21 | if (ds->valid_port_mask & (1 << addr)) | ||
22 | return ds->drv->phy_read(ds, addr, reg); | ||
23 | |||
24 | return 0xffff; | ||
25 | } | ||
26 | |||
27 | static int dsa_slave_phy_write(struct mii_bus *bus, int addr, int reg, u16 val) | ||
28 | { | ||
29 | struct dsa_switch *ds = bus->priv; | ||
30 | |||
31 | if (ds->valid_port_mask & (1 << addr)) | ||
32 | return ds->drv->phy_write(ds, addr, reg, val); | ||
33 | |||
34 | return 0; | ||
35 | } | ||
36 | |||
37 | void dsa_slave_mii_bus_init(struct dsa_switch *ds) | ||
38 | { | ||
39 | ds->slave_mii_bus->priv = (void *)ds; | ||
40 | ds->slave_mii_bus->name = "dsa slave smi"; | ||
41 | ds->slave_mii_bus->read = dsa_slave_phy_read; | ||
42 | ds->slave_mii_bus->write = dsa_slave_phy_write; | ||
43 | snprintf(ds->slave_mii_bus->id, MII_BUS_ID_SIZE, "%s:%.2x", | ||
44 | ds->master_mii_bus->id, ds->pd->sw_addr); | ||
45 | ds->slave_mii_bus->parent = &(ds->master_mii_bus->dev); | ||
46 | } | ||
47 | |||
48 | |||
49 | /* slave device handling ****************************************************/ | ||
50 | static int dsa_slave_open(struct net_device *dev) | ||
51 | { | ||
52 | return 0; | ||
53 | } | ||
54 | |||
55 | static int dsa_slave_close(struct net_device *dev) | ||
56 | { | ||
57 | return 0; | ||
58 | } | ||
59 | |||
60 | static void dsa_slave_change_rx_flags(struct net_device *dev, int change) | ||
61 | { | ||
62 | struct dsa_slave_priv *p = netdev_priv(dev); | ||
63 | struct net_device *master = p->parent->master_netdev; | ||
64 | |||
65 | if (change & IFF_ALLMULTI) | ||
66 | dev_set_allmulti(master, dev->flags & IFF_ALLMULTI ? 1 : -1); | ||
67 | if (change & IFF_PROMISC) | ||
68 | dev_set_promiscuity(master, dev->flags & IFF_PROMISC ? 1 : -1); | ||
69 | } | ||
70 | |||
71 | static void dsa_slave_set_rx_mode(struct net_device *dev) | ||
72 | { | ||
73 | struct dsa_slave_priv *p = netdev_priv(dev); | ||
74 | struct net_device *master = p->parent->master_netdev; | ||
75 | |||
76 | dev_mc_sync(master, dev); | ||
77 | dev_unicast_sync(master, dev); | ||
78 | } | ||
79 | |||
80 | static int dsa_slave_set_mac_address(struct net_device *dev, void *addr) | ||
81 | { | ||
82 | memcpy(dev->dev_addr, addr + 2, 6); | ||
83 | |||
84 | return 0; | ||
85 | } | ||
86 | |||
87 | static int dsa_slave_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) | ||
88 | { | ||
89 | struct dsa_slave_priv *p = netdev_priv(dev); | ||
90 | struct mii_ioctl_data *mii_data = if_mii(ifr); | ||
91 | |||
92 | if (p->phy != NULL) | ||
93 | return phy_mii_ioctl(p->phy, mii_data, cmd); | ||
94 | |||
95 | return -EOPNOTSUPP; | ||
96 | } | ||
97 | |||
98 | |||
99 | /* ethtool operations *******************************************************/ | ||
100 | static int | ||
101 | dsa_slave_get_settings(struct net_device *dev, struct ethtool_cmd *cmd) | ||
102 | { | ||
103 | struct dsa_slave_priv *p = netdev_priv(dev); | ||
104 | int err; | ||
105 | |||
106 | err = -EOPNOTSUPP; | ||
107 | if (p->phy != NULL) { | ||
108 | err = phy_read_status(p->phy); | ||
109 | if (err == 0) | ||
110 | err = phy_ethtool_gset(p->phy, cmd); | ||
111 | } | ||
112 | |||
113 | return err; | ||
114 | } | ||
115 | |||
116 | static int | ||
117 | dsa_slave_set_settings(struct net_device *dev, struct ethtool_cmd *cmd) | ||
118 | { | ||
119 | struct dsa_slave_priv *p = netdev_priv(dev); | ||
120 | |||
121 | if (p->phy != NULL) | ||
122 | return phy_ethtool_sset(p->phy, cmd); | ||
123 | |||
124 | return -EOPNOTSUPP; | ||
125 | } | ||
126 | |||
127 | static void dsa_slave_get_drvinfo(struct net_device *dev, | ||
128 | struct ethtool_drvinfo *drvinfo) | ||
129 | { | ||
130 | strncpy(drvinfo->driver, "dsa", 32); | ||
131 | strncpy(drvinfo->version, dsa_driver_version, 32); | ||
132 | strncpy(drvinfo->fw_version, "N/A", 32); | ||
133 | strncpy(drvinfo->bus_info, "platform", 32); | ||
134 | } | ||
135 | |||
136 | static int dsa_slave_nway_reset(struct net_device *dev) | ||
137 | { | ||
138 | struct dsa_slave_priv *p = netdev_priv(dev); | ||
139 | |||
140 | if (p->phy != NULL) | ||
141 | return genphy_restart_aneg(p->phy); | ||
142 | |||
143 | return -EOPNOTSUPP; | ||
144 | } | ||
145 | |||
146 | static u32 dsa_slave_get_link(struct net_device *dev) | ||
147 | { | ||
148 | struct dsa_slave_priv *p = netdev_priv(dev); | ||
149 | |||
150 | if (p->phy != NULL) { | ||
151 | genphy_update_link(p->phy); | ||
152 | return p->phy->link; | ||
153 | } | ||
154 | |||
155 | return -EOPNOTSUPP; | ||
156 | } | ||
157 | |||
158 | static void dsa_slave_get_strings(struct net_device *dev, | ||
159 | uint32_t stringset, uint8_t *data) | ||
160 | { | ||
161 | struct dsa_slave_priv *p = netdev_priv(dev); | ||
162 | struct dsa_switch *ds = p->parent; | ||
163 | |||
164 | if (stringset == ETH_SS_STATS) { | ||
165 | int len = ETH_GSTRING_LEN; | ||
166 | |||
167 | strncpy(data, "tx_packets", len); | ||
168 | strncpy(data + len, "tx_bytes", len); | ||
169 | strncpy(data + 2 * len, "rx_packets", len); | ||
170 | strncpy(data + 3 * len, "rx_bytes", len); | ||
171 | if (ds->drv->get_strings != NULL) | ||
172 | ds->drv->get_strings(ds, p->port, data + 4 * len); | ||
173 | } | ||
174 | } | ||
175 | |||
176 | static void dsa_slave_get_ethtool_stats(struct net_device *dev, | ||
177 | struct ethtool_stats *stats, | ||
178 | uint64_t *data) | ||
179 | { | ||
180 | struct dsa_slave_priv *p = netdev_priv(dev); | ||
181 | struct dsa_switch *ds = p->parent; | ||
182 | |||
183 | data[0] = p->dev->stats.tx_packets; | ||
184 | data[1] = p->dev->stats.tx_bytes; | ||
185 | data[2] = p->dev->stats.rx_packets; | ||
186 | data[3] = p->dev->stats.rx_bytes; | ||
187 | if (ds->drv->get_ethtool_stats != NULL) | ||
188 | ds->drv->get_ethtool_stats(ds, p->port, data + 4); | ||
189 | } | ||
190 | |||
191 | static int dsa_slave_get_sset_count(struct net_device *dev, int sset) | ||
192 | { | ||
193 | struct dsa_slave_priv *p = netdev_priv(dev); | ||
194 | struct dsa_switch *ds = p->parent; | ||
195 | |||
196 | if (sset == ETH_SS_STATS) { | ||
197 | int count; | ||
198 | |||
199 | count = 4; | ||
200 | if (ds->drv->get_sset_count != NULL) | ||
201 | count += ds->drv->get_sset_count(ds); | ||
202 | |||
203 | return count; | ||
204 | } | ||
205 | |||
206 | return -EOPNOTSUPP; | ||
207 | } | ||
208 | |||
209 | static const struct ethtool_ops dsa_slave_ethtool_ops = { | ||
210 | .get_settings = dsa_slave_get_settings, | ||
211 | .set_settings = dsa_slave_set_settings, | ||
212 | .get_drvinfo = dsa_slave_get_drvinfo, | ||
213 | .nway_reset = dsa_slave_nway_reset, | ||
214 | .get_link = dsa_slave_get_link, | ||
215 | .set_sg = ethtool_op_set_sg, | ||
216 | .get_strings = dsa_slave_get_strings, | ||
217 | .get_ethtool_stats = dsa_slave_get_ethtool_stats, | ||
218 | .get_sset_count = dsa_slave_get_sset_count, | ||
219 | }; | ||
220 | |||
221 | |||
222 | /* slave device setup *******************************************************/ | ||
223 | struct net_device * | ||
224 | dsa_slave_create(struct dsa_switch *ds, struct device *parent, | ||
225 | int port, char *name) | ||
226 | { | ||
227 | struct net_device *master = ds->master_netdev; | ||
228 | struct net_device *slave_dev; | ||
229 | struct dsa_slave_priv *p; | ||
230 | int ret; | ||
231 | |||
232 | slave_dev = alloc_netdev(sizeof(struct dsa_slave_priv), | ||
233 | name, ether_setup); | ||
234 | if (slave_dev == NULL) | ||
235 | return slave_dev; | ||
236 | |||
237 | slave_dev->features = master->vlan_features; | ||
238 | SET_ETHTOOL_OPS(slave_dev, &dsa_slave_ethtool_ops); | ||
239 | memcpy(slave_dev->dev_addr, master->dev_addr, ETH_ALEN); | ||
240 | slave_dev->tx_queue_len = 0; | ||
241 | switch (ds->tag_protocol) { | ||
242 | #ifdef CONFIG_NET_DSA_TAG_EDSA | ||
243 | case htons(ETH_P_EDSA): | ||
244 | slave_dev->hard_start_xmit = edsa_xmit; | ||
245 | break; | ||
246 | #endif | ||
247 | default: | ||
248 | BUG(); | ||
249 | } | ||
250 | slave_dev->open = dsa_slave_open; | ||
251 | slave_dev->stop = dsa_slave_close; | ||
252 | slave_dev->change_rx_flags = dsa_slave_change_rx_flags; | ||
253 | slave_dev->set_rx_mode = dsa_slave_set_rx_mode; | ||
254 | slave_dev->set_multicast_list = dsa_slave_set_rx_mode; | ||
255 | slave_dev->set_mac_address = dsa_slave_set_mac_address; | ||
256 | slave_dev->do_ioctl = dsa_slave_ioctl; | ||
257 | SET_NETDEV_DEV(slave_dev, parent); | ||
258 | slave_dev->vlan_features = master->vlan_features; | ||
259 | |||
260 | p = netdev_priv(slave_dev); | ||
261 | p->dev = slave_dev; | ||
262 | p->parent = ds; | ||
263 | p->port = port; | ||
264 | p->phy = ds->slave_mii_bus->phy_map[port]; | ||
265 | |||
266 | ret = register_netdev(slave_dev); | ||
267 | if (ret) { | ||
268 | printk(KERN_ERR "%s: error %d registering interface %s\n", | ||
269 | master->name, ret, slave_dev->name); | ||
270 | free_netdev(slave_dev); | ||
271 | return NULL; | ||
272 | } | ||
273 | |||
274 | netif_carrier_off(slave_dev); | ||
275 | |||
276 | if (p->phy != NULL) { | ||
277 | phy_attach(slave_dev, p->phy->dev.bus_id, | ||
278 | 0, PHY_INTERFACE_MODE_GMII); | ||
279 | |||
280 | p->phy->autoneg = AUTONEG_ENABLE; | ||
281 | p->phy->speed = 0; | ||
282 | p->phy->duplex = 0; | ||
283 | p->phy->advertising = p->phy->supported | ADVERTISED_Autoneg; | ||
284 | phy_start_aneg(p->phy); | ||
285 | } | ||
286 | |||
287 | return slave_dev; | ||
288 | } | ||