]>
Commit | Line | Data |
---|---|---|
91da11f8 LB |
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) { | |
cf85d08f LB |
242 | #ifdef CONFIG_NET_DSA_TAG_DSA |
243 | case htons(ETH_P_DSA): | |
244 | slave_dev->hard_start_xmit = dsa_xmit; | |
245 | break; | |
246 | #endif | |
91da11f8 LB |
247 | #ifdef CONFIG_NET_DSA_TAG_EDSA |
248 | case htons(ETH_P_EDSA): | |
249 | slave_dev->hard_start_xmit = edsa_xmit; | |
250 | break; | |
396138f0 LB |
251 | #endif |
252 | #ifdef CONFIG_NET_DSA_TAG_TRAILER | |
253 | case htons(ETH_P_TRAILER): | |
254 | slave_dev->hard_start_xmit = trailer_xmit; | |
255 | break; | |
91da11f8 LB |
256 | #endif |
257 | default: | |
258 | BUG(); | |
259 | } | |
260 | slave_dev->open = dsa_slave_open; | |
261 | slave_dev->stop = dsa_slave_close; | |
262 | slave_dev->change_rx_flags = dsa_slave_change_rx_flags; | |
263 | slave_dev->set_rx_mode = dsa_slave_set_rx_mode; | |
264 | slave_dev->set_multicast_list = dsa_slave_set_rx_mode; | |
265 | slave_dev->set_mac_address = dsa_slave_set_mac_address; | |
266 | slave_dev->do_ioctl = dsa_slave_ioctl; | |
267 | SET_NETDEV_DEV(slave_dev, parent); | |
268 | slave_dev->vlan_features = master->vlan_features; | |
269 | ||
270 | p = netdev_priv(slave_dev); | |
271 | p->dev = slave_dev; | |
272 | p->parent = ds; | |
273 | p->port = port; | |
274 | p->phy = ds->slave_mii_bus->phy_map[port]; | |
275 | ||
276 | ret = register_netdev(slave_dev); | |
277 | if (ret) { | |
278 | printk(KERN_ERR "%s: error %d registering interface %s\n", | |
279 | master->name, ret, slave_dev->name); | |
280 | free_netdev(slave_dev); | |
281 | return NULL; | |
282 | } | |
283 | ||
284 | netif_carrier_off(slave_dev); | |
285 | ||
286 | if (p->phy != NULL) { | |
287 | phy_attach(slave_dev, p->phy->dev.bus_id, | |
288 | 0, PHY_INTERFACE_MODE_GMII); | |
289 | ||
290 | p->phy->autoneg = AUTONEG_ENABLE; | |
291 | p->phy->speed = 0; | |
292 | p->phy->duplex = 0; | |
293 | p->phy->advertising = p->phy->supported | ADVERTISED_Autoneg; | |
294 | phy_start_aneg(p->phy); | |
295 | } | |
296 | ||
297 | return slave_dev; | |
298 | } |