diff options
Diffstat (limited to 'drivers/net/phy/fixed.c')
-rw-r--r-- | drivers/net/phy/fixed.c | 358 |
1 files changed, 358 insertions, 0 deletions
diff --git a/drivers/net/phy/fixed.c b/drivers/net/phy/fixed.c new file mode 100644 index 000000000000..341036df4710 --- /dev/null +++ b/drivers/net/phy/fixed.c | |||
@@ -0,0 +1,358 @@ | |||
1 | /* | ||
2 | * drivers/net/phy/fixed.c | ||
3 | * | ||
4 | * Driver for fixed PHYs, when transceiver is able to operate in one fixed mode. | ||
5 | * | ||
6 | * Author: Vitaly Bordug | ||
7 | * | ||
8 | * Copyright (c) 2006 MontaVista Software, Inc. | ||
9 | * | ||
10 | * This program is free software; you can redistribute it and/or modify it | ||
11 | * under the terms of the GNU General Public License as published by the | ||
12 | * Free Software Foundation; either version 2 of the License, or (at your | ||
13 | * option) any later version. | ||
14 | * | ||
15 | */ | ||
16 | #include <linux/config.h> | ||
17 | #include <linux/kernel.h> | ||
18 | #include <linux/sched.h> | ||
19 | #include <linux/string.h> | ||
20 | #include <linux/errno.h> | ||
21 | #include <linux/unistd.h> | ||
22 | #include <linux/slab.h> | ||
23 | #include <linux/interrupt.h> | ||
24 | #include <linux/init.h> | ||
25 | #include <linux/delay.h> | ||
26 | #include <linux/netdevice.h> | ||
27 | #include <linux/etherdevice.h> | ||
28 | #include <linux/skbuff.h> | ||
29 | #include <linux/spinlock.h> | ||
30 | #include <linux/mm.h> | ||
31 | #include <linux/module.h> | ||
32 | #include <linux/mii.h> | ||
33 | #include <linux/ethtool.h> | ||
34 | #include <linux/phy.h> | ||
35 | |||
36 | #include <asm/io.h> | ||
37 | #include <asm/irq.h> | ||
38 | #include <asm/uaccess.h> | ||
39 | |||
40 | #define MII_REGS_NUM 7 | ||
41 | |||
42 | /* | ||
43 | The idea is to emulate normal phy behavior by responding with | ||
44 | pre-defined values to mii BMCR read, so that read_status hook could | ||
45 | take all the needed info. | ||
46 | */ | ||
47 | |||
48 | struct fixed_phy_status { | ||
49 | u8 link; | ||
50 | u16 speed; | ||
51 | u8 duplex; | ||
52 | }; | ||
53 | |||
54 | /*----------------------------------------------------------------------------- | ||
55 | * Private information hoder for mii_bus | ||
56 | *-----------------------------------------------------------------------------*/ | ||
57 | struct fixed_info { | ||
58 | u16 *regs; | ||
59 | u8 regs_num; | ||
60 | struct fixed_phy_status phy_status; | ||
61 | struct phy_device *phydev; /* pointer to the container */ | ||
62 | /* link & speed cb */ | ||
63 | int(*link_update)(struct net_device*, struct fixed_phy_status*); | ||
64 | |||
65 | }; | ||
66 | |||
67 | /*----------------------------------------------------------------------------- | ||
68 | * If something weird is required to be done with link/speed, | ||
69 | * network driver is able to assign a function to implement this. | ||
70 | * May be useful for PHY's that need to be software-driven. | ||
71 | *-----------------------------------------------------------------------------*/ | ||
72 | int fixed_mdio_set_link_update(struct phy_device* phydev, | ||
73 | int(*link_update)(struct net_device*, struct fixed_phy_status*)) | ||
74 | { | ||
75 | struct fixed_info *fixed; | ||
76 | |||
77 | if(link_update == NULL) | ||
78 | return -EINVAL; | ||
79 | |||
80 | if(phydev) { | ||
81 | if(phydev->bus) { | ||
82 | fixed = phydev->bus->priv; | ||
83 | fixed->link_update = link_update; | ||
84 | return 0; | ||
85 | } | ||
86 | } | ||
87 | return -EINVAL; | ||
88 | } | ||
89 | EXPORT_SYMBOL(fixed_mdio_set_link_update); | ||
90 | |||
91 | /*----------------------------------------------------------------------------- | ||
92 | * This is used for updating internal mii regs from the status | ||
93 | *-----------------------------------------------------------------------------*/ | ||
94 | static int fixed_mdio_update_regs(struct fixed_info *fixed) | ||
95 | { | ||
96 | u16 *regs = fixed->regs; | ||
97 | u16 bmsr = 0; | ||
98 | u16 bmcr = 0; | ||
99 | |||
100 | if(!regs) { | ||
101 | printk(KERN_ERR "%s: regs not set up", __FUNCTION__); | ||
102 | return -EINVAL; | ||
103 | } | ||
104 | |||
105 | if(fixed->phy_status.link) | ||
106 | bmsr |= BMSR_LSTATUS; | ||
107 | |||
108 | if(fixed->phy_status.duplex) { | ||
109 | bmcr |= BMCR_FULLDPLX; | ||
110 | |||
111 | switch ( fixed->phy_status.speed ) { | ||
112 | case 100: | ||
113 | bmsr |= BMSR_100FULL; | ||
114 | bmcr |= BMCR_SPEED100; | ||
115 | break; | ||
116 | |||
117 | case 10: | ||
118 | bmsr |= BMSR_10FULL; | ||
119 | break; | ||
120 | } | ||
121 | } else { | ||
122 | switch ( fixed->phy_status.speed ) { | ||
123 | case 100: | ||
124 | bmsr |= BMSR_100HALF; | ||
125 | bmcr |= BMCR_SPEED100; | ||
126 | break; | ||
127 | |||
128 | case 10: | ||
129 | bmsr |= BMSR_100HALF; | ||
130 | break; | ||
131 | } | ||
132 | } | ||
133 | |||
134 | regs[MII_BMCR] = bmcr; | ||
135 | regs[MII_BMSR] = bmsr | 0x800; /*we are always capable of 10 hdx*/ | ||
136 | |||
137 | return 0; | ||
138 | } | ||
139 | |||
140 | static int fixed_mii_read(struct mii_bus *bus, int phy_id, int location) | ||
141 | { | ||
142 | struct fixed_info *fixed = bus->priv; | ||
143 | |||
144 | /* if user has registered link update callback, use it */ | ||
145 | if(fixed->phydev) | ||
146 | if(fixed->phydev->attached_dev) { | ||
147 | if(fixed->link_update) { | ||
148 | fixed->link_update(fixed->phydev->attached_dev, | ||
149 | &fixed->phy_status); | ||
150 | fixed_mdio_update_regs(fixed); | ||
151 | } | ||
152 | } | ||
153 | |||
154 | if ((unsigned int)location >= fixed->regs_num) | ||
155 | return -1; | ||
156 | return fixed->regs[location]; | ||
157 | } | ||
158 | |||
159 | static int fixed_mii_write(struct mii_bus *bus, int phy_id, int location, u16 val) | ||
160 | { | ||
161 | /* do nothing for now*/ | ||
162 | return 0; | ||
163 | } | ||
164 | |||
165 | static int fixed_mii_reset(struct mii_bus *bus) | ||
166 | { | ||
167 | /*nothing here - no way/need to reset it*/ | ||
168 | return 0; | ||
169 | } | ||
170 | |||
171 | static int fixed_config_aneg(struct phy_device *phydev) | ||
172 | { | ||
173 | /* :TODO:03/13/2006 09:45:37 PM:: | ||
174 | The full autoneg funcionality can be emulated, | ||
175 | but no need to have anything here for now | ||
176 | */ | ||
177 | return 0; | ||
178 | } | ||
179 | |||
180 | /*----------------------------------------------------------------------------- | ||
181 | * the manual bind will do the magic - with phy_id_mask == 0 | ||
182 | * match will never return true... | ||
183 | *-----------------------------------------------------------------------------*/ | ||
184 | static struct phy_driver fixed_mdio_driver = { | ||
185 | .name = "Fixed PHY", | ||
186 | .features = PHY_BASIC_FEATURES, | ||
187 | .config_aneg = fixed_config_aneg, | ||
188 | .read_status = genphy_read_status, | ||
189 | .driver = { .owner = THIS_MODULE,}, | ||
190 | }; | ||
191 | |||
192 | /*----------------------------------------------------------------------------- | ||
193 | * This func is used to create all the necessary stuff, bind | ||
194 | * the fixed phy driver and register all it on the mdio_bus_type. | ||
195 | * speed is either 10 or 100, duplex is boolean. | ||
196 | * number is used to create multiple fixed PHYs, so that several devices can | ||
197 | * utilize them simultaneously. | ||
198 | *-----------------------------------------------------------------------------*/ | ||
199 | static int fixed_mdio_register_device(int number, int speed, int duplex) | ||
200 | { | ||
201 | struct mii_bus *new_bus; | ||
202 | struct fixed_info *fixed; | ||
203 | struct phy_device *phydev; | ||
204 | int err = 0; | ||
205 | |||
206 | struct device* dev = kzalloc(sizeof(struct device), GFP_KERNEL); | ||
207 | |||
208 | if (NULL == dev) | ||
209 | return -ENOMEM; | ||
210 | |||
211 | new_bus = kzalloc(sizeof(struct mii_bus), GFP_KERNEL); | ||
212 | |||
213 | if (NULL == new_bus) { | ||
214 | kfree(dev); | ||
215 | return -ENOMEM; | ||
216 | } | ||
217 | fixed = kzalloc(sizeof(struct fixed_info), GFP_KERNEL); | ||
218 | |||
219 | if (NULL == fixed) { | ||
220 | kfree(dev); | ||
221 | kfree(new_bus); | ||
222 | return -ENOMEM; | ||
223 | } | ||
224 | |||
225 | fixed->regs = kzalloc(MII_REGS_NUM*sizeof(int), GFP_KERNEL); | ||
226 | fixed->regs_num = MII_REGS_NUM; | ||
227 | fixed->phy_status.speed = speed; | ||
228 | fixed->phy_status.duplex = duplex; | ||
229 | fixed->phy_status.link = 1; | ||
230 | |||
231 | new_bus->name = "Fixed MII Bus", | ||
232 | new_bus->read = &fixed_mii_read, | ||
233 | new_bus->write = &fixed_mii_write, | ||
234 | new_bus->reset = &fixed_mii_reset, | ||
235 | |||
236 | /*set up workspace*/ | ||
237 | fixed_mdio_update_regs(fixed); | ||
238 | new_bus->priv = fixed; | ||
239 | |||
240 | new_bus->dev = dev; | ||
241 | dev_set_drvdata(dev, new_bus); | ||
242 | |||
243 | /* create phy_device and register it on the mdio bus */ | ||
244 | phydev = phy_device_create(new_bus, 0, 0); | ||
245 | |||
246 | /* | ||
247 | Put the phydev pointer into the fixed pack so that bus read/write code could | ||
248 | be able to access for instance attached netdev. Well it doesn't have to do | ||
249 | so, only in case of utilizing user-specified link-update... | ||
250 | */ | ||
251 | fixed->phydev = phydev; | ||
252 | |||
253 | if(NULL == phydev) { | ||
254 | err = -ENOMEM; | ||
255 | goto device_create_fail; | ||
256 | } | ||
257 | |||
258 | phydev->irq = -1; | ||
259 | phydev->dev.bus = &mdio_bus_type; | ||
260 | |||
261 | if(number) | ||
262 | snprintf(phydev->dev.bus_id, BUS_ID_SIZE, | ||
263 | "fixed_%d@%d:%d", number, speed, duplex); | ||
264 | else | ||
265 | snprintf(phydev->dev.bus_id, BUS_ID_SIZE, | ||
266 | "fixed@%d:%d", speed, duplex); | ||
267 | phydev->bus = new_bus; | ||
268 | |||
269 | err = device_register(&phydev->dev); | ||
270 | if(err) { | ||
271 | printk(KERN_ERR "Phy %s failed to register\n", | ||
272 | phydev->dev.bus_id); | ||
273 | goto bus_register_fail; | ||
274 | } | ||
275 | |||
276 | /* | ||
277 | the mdio bus has phy_id match... In order not to do it | ||
278 | artificially, we are binding the driver here by hand; | ||
279 | it will be the same for all the fixed phys anyway. | ||
280 | */ | ||
281 | down_write(&phydev->dev.bus->subsys.rwsem); | ||
282 | |||
283 | phydev->dev.driver = &fixed_mdio_driver.driver; | ||
284 | |||
285 | err = phydev->dev.driver->probe(&phydev->dev); | ||
286 | if(err < 0) { | ||
287 | printk(KERN_ERR "Phy %s: problems with fixed driver\n",phydev->dev.bus_id); | ||
288 | up_write(&phydev->dev.bus->subsys.rwsem); | ||
289 | goto probe_fail; | ||
290 | } | ||
291 | |||
292 | device_bind_driver(&phydev->dev); | ||
293 | up_write(&phydev->dev.bus->subsys.rwsem); | ||
294 | |||
295 | return 0; | ||
296 | |||
297 | probe_fail: | ||
298 | device_unregister(&phydev->dev); | ||
299 | bus_register_fail: | ||
300 | kfree(phydev); | ||
301 | device_create_fail: | ||
302 | kfree(dev); | ||
303 | kfree(new_bus); | ||
304 | kfree(fixed); | ||
305 | |||
306 | return err; | ||
307 | } | ||
308 | |||
309 | |||
310 | MODULE_DESCRIPTION("Fixed PHY device & driver for PAL"); | ||
311 | MODULE_AUTHOR("Vitaly Bordug"); | ||
312 | MODULE_LICENSE("GPL"); | ||
313 | |||
314 | static int __init fixed_init(void) | ||
315 | { | ||
316 | int ret; | ||
317 | int duplex = 0; | ||
318 | |||
319 | /* register on the bus... Not expected to be matched with anything there... */ | ||
320 | phy_driver_register(&fixed_mdio_driver); | ||
321 | |||
322 | /* So let the fun begin... | ||
323 | We will create several mdio devices here, and will bound the upper | ||
324 | driver to them. | ||
325 | |||
326 | Then the external software can lookup the phy bus by searching | ||
327 | fixed@speed:duplex, e.g. fixed@100:1, to be connected to the | ||
328 | virtual 100M Fdx phy. | ||
329 | |||
330 | In case several virtual PHYs required, the bus_id will be in form | ||
331 | fixed_<num>@<speed>:<duplex>, which make it able even to define | ||
332 | driver-specific link control callback, if for instance PHY is completely | ||
333 | SW-driven. | ||
334 | |||
335 | */ | ||
336 | |||
337 | #ifdef CONFIG_FIXED_MII_DUPLEX | ||
338 | duplex = 1; | ||
339 | #endif | ||
340 | |||
341 | #ifdef CONFIG_FIXED_MII_100_FDX | ||
342 | fixed_mdio_register_device(0, 100, 1); | ||
343 | #endif | ||
344 | |||
345 | #ifdef CONFIX_FIXED_MII_10_FDX | ||
346 | fixed_mdio_register_device(0, 10, 1); | ||
347 | #endif | ||
348 | return 0; | ||
349 | } | ||
350 | |||
351 | static void __exit fixed_exit(void) | ||
352 | { | ||
353 | phy_driver_unregister(&fixed_mdio_driver); | ||
354 | /* :WARNING:02/18/2006 04:32:40 AM:: Cleanup all the created stuff */ | ||
355 | } | ||
356 | |||
357 | module_init(fixed_init); | ||
358 | module_exit(fixed_exit); | ||