OpenWrt – Blame information for rev 4
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
4 | office | 1 | /* |
2 | * Lantiq PSB6970 (Tantos) Switch driver |
||
3 | * |
||
4 | * Copyright (c) 2009,2010 Team Embedded. |
||
5 | * |
||
6 | * This program is free software; you can redistribute it and/or modify it |
||
7 | * under the terms of the GNU General Public License v2 as published by the |
||
8 | * Free Software Foundation. |
||
9 | * |
||
10 | * The switch programming done in this driver follows the |
||
11 | * "Ethernet Traffic Separation using VLAN" Application Note as |
||
12 | * published by Lantiq. |
||
13 | */ |
||
14 | |||
15 | #include <linux/module.h> |
||
16 | #include <linux/netdevice.h> |
||
17 | #include <linux/switch.h> |
||
18 | #include <linux/phy.h> |
||
19 | |||
20 | #define PSB6970_MAX_VLANS 16 |
||
21 | #define PSB6970_NUM_PORTS 7 |
||
22 | #define PSB6970_DEFAULT_PORT_CPU 6 |
||
23 | #define PSB6970_IS_CPU_PORT(x) ((x) > 4) |
||
24 | |||
25 | #define PHYADDR(_reg) ((_reg >> 5) & 0xff), (_reg & 0x1f) |
||
26 | |||
27 | /* --- Identification --- */ |
||
28 | #define PSB6970_CI0 0x0100 |
||
29 | #define PSB6970_CI0_MASK 0x000f |
||
30 | #define PSB6970_CI1 0x0101 |
||
31 | #define PSB6970_CI1_VAL 0x2599 |
||
32 | #define PSB6970_CI1_MASK 0xffff |
||
33 | |||
34 | /* --- VLAN filter table --- */ |
||
35 | #define PSB6970_VFxL(i) ((i)*2+0x10) /* VLAN Filter Low */ |
||
36 | #define PSB6970_VFxL_VV (1 << 15) /* VLAN_Valid */ |
||
37 | |||
38 | #define PSB6970_VFxH(i) ((i)*2+0x11) /* VLAN Filter High */ |
||
39 | #define PSB6970_VFxH_TM_SHIFT 7 /* Tagged Member */ |
||
40 | |||
41 | /* --- Port registers --- */ |
||
42 | #define PSB6970_EC(p) ((p)*0x20+2) /* Extended Control */ |
||
43 | #define PSB6970_EC_IFNTE (1 << 1) /* Input Force No Tag Enable */ |
||
44 | |||
45 | #define PSB6970_PBVM(p) ((p)*0x20+3) /* Port Base VLAN Map */ |
||
46 | #define PSB6970_PBVM_VMCE (1 << 8) |
||
47 | #define PSB6970_PBVM_AOVTP (1 << 9) |
||
48 | #define PSB6970_PBVM_VSD (1 << 10) |
||
49 | #define PSB6970_PBVM_VC (1 << 11) /* VID Check with VID table */ |
||
50 | #define PSB6970_PBVM_TBVE (1 << 13) /* Tag-Based VLAN enable */ |
||
51 | |||
52 | #define PSB6970_DVID(p) ((p)*0x20+4) /* Default VLAN ID & Priority */ |
||
53 | |||
54 | struct psb6970_priv { |
||
55 | struct switch_dev dev; |
||
56 | struct phy_device *phy; |
||
57 | u16 (*read) (struct phy_device* phydev, int reg); |
||
58 | void (*write) (struct phy_device* phydev, int reg, u16 val); |
||
59 | struct mutex reg_mutex; |
||
60 | |||
61 | /* all fields below are cleared on reset */ |
||
62 | bool vlan; |
||
63 | u16 vlan_id[PSB6970_MAX_VLANS]; |
||
64 | u8 vlan_table[PSB6970_MAX_VLANS]; |
||
65 | u8 vlan_tagged; |
||
66 | u16 pvid[PSB6970_NUM_PORTS]; |
||
67 | }; |
||
68 | |||
69 | #define to_psb6970(_dev) container_of(_dev, struct psb6970_priv, dev) |
||
70 | |||
71 | static u16 psb6970_mii_read(struct phy_device *phydev, int reg) |
||
72 | { |
||
73 | struct mii_bus *bus = phydev->mdio.bus; |
||
74 | |||
75 | return bus->read(bus, PHYADDR(reg)); |
||
76 | } |
||
77 | |||
78 | static void psb6970_mii_write(struct phy_device *phydev, int reg, u16 val) |
||
79 | { |
||
80 | struct mii_bus *bus = phydev->mdio.bus; |
||
81 | |||
82 | bus->write(bus, PHYADDR(reg), val); |
||
83 | } |
||
84 | |||
85 | static int |
||
86 | psb6970_set_vlan(struct switch_dev *dev, const struct switch_attr *attr, |
||
87 | struct switch_val *val) |
||
88 | { |
||
89 | struct psb6970_priv *priv = to_psb6970(dev); |
||
90 | priv->vlan = !!val->value.i; |
||
91 | return 0; |
||
92 | } |
||
93 | |||
94 | static int |
||
95 | psb6970_get_vlan(struct switch_dev *dev, const struct switch_attr *attr, |
||
96 | struct switch_val *val) |
||
97 | { |
||
98 | struct psb6970_priv *priv = to_psb6970(dev); |
||
99 | val->value.i = priv->vlan; |
||
100 | return 0; |
||
101 | } |
||
102 | |||
103 | static int psb6970_set_pvid(struct switch_dev *dev, int port, int vlan) |
||
104 | { |
||
105 | struct psb6970_priv *priv = to_psb6970(dev); |
||
106 | |||
107 | /* make sure no invalid PVIDs get set */ |
||
108 | if (vlan >= dev->vlans) |
||
109 | return -EINVAL; |
||
110 | |||
111 | priv->pvid[port] = vlan; |
||
112 | return 0; |
||
113 | } |
||
114 | |||
115 | static int psb6970_get_pvid(struct switch_dev *dev, int port, int *vlan) |
||
116 | { |
||
117 | struct psb6970_priv *priv = to_psb6970(dev); |
||
118 | *vlan = priv->pvid[port]; |
||
119 | return 0; |
||
120 | } |
||
121 | |||
122 | static int |
||
123 | psb6970_set_vid(struct switch_dev *dev, const struct switch_attr *attr, |
||
124 | struct switch_val *val) |
||
125 | { |
||
126 | struct psb6970_priv *priv = to_psb6970(dev); |
||
127 | priv->vlan_id[val->port_vlan] = val->value.i; |
||
128 | return 0; |
||
129 | } |
||
130 | |||
131 | static int |
||
132 | psb6970_get_vid(struct switch_dev *dev, const struct switch_attr *attr, |
||
133 | struct switch_val *val) |
||
134 | { |
||
135 | struct psb6970_priv *priv = to_psb6970(dev); |
||
136 | val->value.i = priv->vlan_id[val->port_vlan]; |
||
137 | return 0; |
||
138 | } |
||
139 | |||
140 | static struct switch_attr psb6970_globals[] = { |
||
141 | { |
||
142 | .type = SWITCH_TYPE_INT, |
||
143 | .name = "enable_vlan", |
||
144 | .description = "Enable VLAN mode", |
||
145 | .set = psb6970_set_vlan, |
||
146 | .get = psb6970_get_vlan, |
||
147 | .max = 1}, |
||
148 | }; |
||
149 | |||
150 | static struct switch_attr psb6970_port[] = { |
||
151 | }; |
||
152 | |||
153 | static struct switch_attr psb6970_vlan[] = { |
||
154 | { |
||
155 | .type = SWITCH_TYPE_INT, |
||
156 | .name = "vid", |
||
157 | .description = "VLAN ID (0-4094)", |
||
158 | .set = psb6970_set_vid, |
||
159 | .get = psb6970_get_vid, |
||
160 | .max = 4094, |
||
161 | }, |
||
162 | }; |
||
163 | |||
164 | static int psb6970_get_ports(struct switch_dev *dev, struct switch_val *val) |
||
165 | { |
||
166 | struct psb6970_priv *priv = to_psb6970(dev); |
||
167 | u8 ports = priv->vlan_table[val->port_vlan]; |
||
168 | int i; |
||
169 | |||
170 | val->len = 0; |
||
171 | for (i = 0; i < PSB6970_NUM_PORTS; i++) { |
||
172 | struct switch_port *p; |
||
173 | |||
174 | if (!(ports & (1 << i))) |
||
175 | continue; |
||
176 | |||
177 | p = &val->value.ports[val->len++]; |
||
178 | p->id = i; |
||
179 | if (priv->vlan_tagged & (1 << i)) |
||
180 | p->flags = (1 << SWITCH_PORT_FLAG_TAGGED); |
||
181 | else |
||
182 | p->flags = 0; |
||
183 | } |
||
184 | return 0; |
||
185 | } |
||
186 | |||
187 | static int psb6970_set_ports(struct switch_dev *dev, struct switch_val *val) |
||
188 | { |
||
189 | struct psb6970_priv *priv = to_psb6970(dev); |
||
190 | u8 *vt = &priv->vlan_table[val->port_vlan]; |
||
191 | int i, j; |
||
192 | |||
193 | *vt = 0; |
||
194 | for (i = 0; i < val->len; i++) { |
||
195 | struct switch_port *p = &val->value.ports[i]; |
||
196 | |||
197 | if (p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) |
||
198 | priv->vlan_tagged |= (1 << p->id); |
||
199 | else { |
||
200 | priv->vlan_tagged &= ~(1 << p->id); |
||
201 | priv->pvid[p->id] = val->port_vlan; |
||
202 | |||
203 | /* make sure that an untagged port does not |
||
204 | * appear in other vlans */ |
||
205 | for (j = 0; j < PSB6970_MAX_VLANS; j++) { |
||
206 | if (j == val->port_vlan) |
||
207 | continue; |
||
208 | priv->vlan_table[j] &= ~(1 << p->id); |
||
209 | } |
||
210 | } |
||
211 | |||
212 | *vt |= 1 << p->id; |
||
213 | } |
||
214 | return 0; |
||
215 | } |
||
216 | |||
217 | static int psb6970_hw_apply(struct switch_dev *dev) |
||
218 | { |
||
219 | struct psb6970_priv *priv = to_psb6970(dev); |
||
220 | int i, j; |
||
221 | |||
222 | mutex_lock(&priv->reg_mutex); |
||
223 | |||
224 | if (priv->vlan) { |
||
225 | /* into the vlan translation unit */ |
||
226 | for (j = 0; j < PSB6970_MAX_VLANS; j++) { |
||
227 | u8 vp = priv->vlan_table[j]; |
||
228 | |||
229 | if (vp) { |
||
230 | priv->write(priv->phy, PSB6970_VFxL(j), |
||
231 | PSB6970_VFxL_VV | priv->vlan_id[j]); |
||
232 | priv->write(priv->phy, PSB6970_VFxH(j), |
||
233 | ((vp & priv-> |
||
234 | vlan_tagged) << |
||
235 | PSB6970_VFxH_TM_SHIFT) | vp); |
||
236 | } else /* clear VLAN Valid flag for unused vlans */ |
||
237 | priv->write(priv->phy, PSB6970_VFxL(j), 0); |
||
238 | |||
239 | } |
||
240 | } |
||
241 | |||
242 | /* update the port destination mask registers and tag settings */ |
||
243 | for (i = 0; i < PSB6970_NUM_PORTS; i++) { |
||
244 | int dvid = 1, pbvm = 0x7f | PSB6970_PBVM_VSD, ec = 0; |
||
245 | |||
246 | if (priv->vlan) { |
||
247 | ec = PSB6970_EC_IFNTE; |
||
248 | dvid = priv->vlan_id[priv->pvid[i]]; |
||
249 | pbvm |= PSB6970_PBVM_TBVE | PSB6970_PBVM_VMCE; |
||
250 | |||
251 | if ((i << 1) & priv->vlan_tagged) |
||
252 | pbvm |= PSB6970_PBVM_AOVTP | PSB6970_PBVM_VC; |
||
253 | } |
||
254 | |||
255 | priv->write(priv->phy, PSB6970_PBVM(i), pbvm); |
||
256 | |||
257 | if (!PSB6970_IS_CPU_PORT(i)) { |
||
258 | priv->write(priv->phy, PSB6970_EC(i), ec); |
||
259 | priv->write(priv->phy, PSB6970_DVID(i), dvid); |
||
260 | } |
||
261 | } |
||
262 | |||
263 | mutex_unlock(&priv->reg_mutex); |
||
264 | return 0; |
||
265 | } |
||
266 | |||
267 | static int psb6970_reset_switch(struct switch_dev *dev) |
||
268 | { |
||
269 | struct psb6970_priv *priv = to_psb6970(dev); |
||
270 | int i; |
||
271 | |||
272 | mutex_lock(&priv->reg_mutex); |
||
273 | |||
274 | memset(&priv->vlan, 0, sizeof(struct psb6970_priv) - |
||
275 | offsetof(struct psb6970_priv, vlan)); |
||
276 | |||
277 | for (i = 0; i < PSB6970_MAX_VLANS; i++) |
||
278 | priv->vlan_id[i] = i; |
||
279 | |||
280 | mutex_unlock(&priv->reg_mutex); |
||
281 | |||
282 | return psb6970_hw_apply(dev); |
||
283 | } |
||
284 | |||
285 | static const struct switch_dev_ops psb6970_ops = { |
||
286 | .attr_global = { |
||
287 | .attr = psb6970_globals, |
||
288 | .n_attr = ARRAY_SIZE(psb6970_globals), |
||
289 | }, |
||
290 | .attr_port = { |
||
291 | .attr = psb6970_port, |
||
292 | .n_attr = ARRAY_SIZE(psb6970_port), |
||
293 | }, |
||
294 | .attr_vlan = { |
||
295 | .attr = psb6970_vlan, |
||
296 | .n_attr = ARRAY_SIZE(psb6970_vlan), |
||
297 | }, |
||
298 | .get_port_pvid = psb6970_get_pvid, |
||
299 | .set_port_pvid = psb6970_set_pvid, |
||
300 | .get_vlan_ports = psb6970_get_ports, |
||
301 | .set_vlan_ports = psb6970_set_ports, |
||
302 | .apply_config = psb6970_hw_apply, |
||
303 | .reset_switch = psb6970_reset_switch, |
||
304 | }; |
||
305 | |||
306 | static int psb6970_config_init(struct phy_device *pdev) |
||
307 | { |
||
308 | struct psb6970_priv *priv; |
||
309 | struct net_device *dev = pdev->attached_dev; |
||
310 | struct switch_dev *swdev; |
||
311 | int ret; |
||
312 | |||
313 | priv = kzalloc(sizeof(struct psb6970_priv), GFP_KERNEL); |
||
314 | if (priv == NULL) |
||
315 | return -ENOMEM; |
||
316 | |||
317 | priv->phy = pdev; |
||
318 | |||
319 | if (pdev->mdio.addr == 0) |
||
320 | printk(KERN_INFO "%s: psb6970 switch driver attached.\n", |
||
321 | pdev->attached_dev->name); |
||
322 | |||
323 | if (pdev->mdio.addr != 0) { |
||
324 | kfree(priv); |
||
325 | return 0; |
||
326 | } |
||
327 | |||
328 | pdev->supported = pdev->advertising = SUPPORTED_100baseT_Full; |
||
329 | |||
330 | mutex_init(&priv->reg_mutex); |
||
331 | priv->read = psb6970_mii_read; |
||
332 | priv->write = psb6970_mii_write; |
||
333 | |||
334 | pdev->priv = priv; |
||
335 | |||
336 | swdev = &priv->dev; |
||
337 | swdev->cpu_port = PSB6970_DEFAULT_PORT_CPU; |
||
338 | swdev->ops = &psb6970_ops; |
||
339 | |||
340 | swdev->name = "Lantiq PSB6970"; |
||
341 | swdev->vlans = PSB6970_MAX_VLANS; |
||
342 | swdev->ports = PSB6970_NUM_PORTS; |
||
343 | |||
344 | if ((ret = register_switch(&priv->dev, pdev->attached_dev)) < 0) { |
||
345 | kfree(priv); |
||
346 | goto done; |
||
347 | } |
||
348 | |||
349 | ret = psb6970_reset_switch(&priv->dev); |
||
350 | if (ret) { |
||
351 | kfree(priv); |
||
352 | goto done; |
||
353 | } |
||
354 | |||
355 | dev->phy_ptr = priv; |
||
356 | |||
357 | done: |
||
358 | return ret; |
||
359 | } |
||
360 | |||
361 | static int psb6970_read_status(struct phy_device *phydev) |
||
362 | { |
||
363 | phydev->speed = SPEED_100; |
||
364 | phydev->duplex = DUPLEX_FULL; |
||
365 | phydev->link = 1; |
||
366 | |||
367 | phydev->state = PHY_RUNNING; |
||
368 | netif_carrier_on(phydev->attached_dev); |
||
369 | phydev->adjust_link(phydev->attached_dev); |
||
370 | |||
371 | return 0; |
||
372 | } |
||
373 | |||
374 | static int psb6970_config_aneg(struct phy_device *phydev) |
||
375 | { |
||
376 | return 0; |
||
377 | } |
||
378 | |||
379 | static int psb6970_probe(struct phy_device *pdev) |
||
380 | { |
||
381 | return 0; |
||
382 | } |
||
383 | |||
384 | static void psb6970_remove(struct phy_device *pdev) |
||
385 | { |
||
386 | struct psb6970_priv *priv = pdev->priv; |
||
387 | |||
388 | if (!priv) |
||
389 | return; |
||
390 | |||
391 | if (pdev->mdio.addr == 0) |
||
392 | unregister_switch(&priv->dev); |
||
393 | kfree(priv); |
||
394 | } |
||
395 | |||
396 | static int psb6970_fixup(struct phy_device *dev) |
||
397 | { |
||
398 | struct mii_bus *bus = dev->mdio.bus; |
||
399 | u16 reg; |
||
400 | |||
401 | /* look for the switch on the bus */ |
||
402 | reg = bus->read(bus, PHYADDR(PSB6970_CI1)) & PSB6970_CI1_MASK; |
||
403 | if (reg != PSB6970_CI1_VAL) |
||
404 | return 0; |
||
405 | |||
406 | dev->phy_id = (reg << 16); |
||
407 | dev->phy_id |= bus->read(bus, PHYADDR(PSB6970_CI0)) & PSB6970_CI0_MASK; |
||
408 | |||
409 | return 0; |
||
410 | } |
||
411 | |||
412 | static struct phy_driver psb6970_driver = { |
||
413 | .name = "Lantiq PSB6970", |
||
414 | .phy_id = PSB6970_CI1_VAL << 16, |
||
415 | .phy_id_mask = 0xffff0000, |
||
416 | .features = PHY_BASIC_FEATURES, |
||
417 | .probe = psb6970_probe, |
||
418 | .remove = psb6970_remove, |
||
419 | .config_init = &psb6970_config_init, |
||
420 | .config_aneg = &psb6970_config_aneg, |
||
421 | .read_status = &psb6970_read_status, |
||
422 | }; |
||
423 | |||
424 | int __init psb6970_init(void) |
||
425 | { |
||
426 | phy_register_fixup_for_id(PHY_ANY_ID, psb6970_fixup); |
||
427 | return phy_driver_register(&psb6970_driver, THIS_MODULE); |
||
428 | } |
||
429 | |||
430 | module_init(psb6970_init); |
||
431 | |||
432 | void __exit psb6970_exit(void) |
||
433 | { |
||
434 | phy_driver_unregister(&psb6970_driver); |
||
435 | } |
||
436 | |||
437 | module_exit(psb6970_exit); |
||
438 | |||
439 | MODULE_DESCRIPTION("Lantiq PSB6970 Switch"); |
||
440 | MODULE_AUTHOR("Ithamar R. Adema <ithamar.adema@team-embedded.nl>"); |
||
441 | MODULE_LICENSE("GPL"); |