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