forcedeth: realtek phy crossover detection
[linux-2.6] / drivers / net / phy / fixed.c
1 /*
2  * Fixed MDIO bus (MDIO bus emulation with fixed PHYs)
3  *
4  * Author: Vitaly Bordug <vbordug@ru.mvista.com>
5  *         Anton Vorontsov <avorontsov@ru.mvista.com>
6  *
7  * Copyright (c) 2006-2007 MontaVista Software, Inc.
8  *
9  * This program is free software; you can redistribute  it and/or modify it
10  * under  the terms of  the GNU General  Public License as published by the
11  * Free Software Foundation;  either version 2 of the  License, or (at your
12  * option) any later version.
13  */
14
15 #include <linux/kernel.h>
16 #include <linux/module.h>
17 #include <linux/platform_device.h>
18 #include <linux/list.h>
19 #include <linux/mii.h>
20 #include <linux/phy.h>
21 #include <linux/phy_fixed.h>
22
23 #define MII_REGS_NUM 29
24
25 struct fixed_mdio_bus {
26         int irqs[PHY_MAX_ADDR];
27         struct mii_bus mii_bus;
28         struct list_head phys;
29 };
30
31 struct fixed_phy {
32         int id;
33         u16 regs[MII_REGS_NUM];
34         struct phy_device *phydev;
35         struct fixed_phy_status status;
36         int (*link_update)(struct net_device *, struct fixed_phy_status *);
37         struct list_head node;
38 };
39
40 static struct platform_device *pdev;
41 static struct fixed_mdio_bus platform_fmb = {
42         .phys = LIST_HEAD_INIT(platform_fmb.phys),
43 };
44
45 static int fixed_phy_update_regs(struct fixed_phy *fp)
46 {
47         u16 bmsr = BMSR_ANEGCAPABLE;
48         u16 bmcr = 0;
49         u16 lpagb = 0;
50         u16 lpa = 0;
51
52         if (fp->status.duplex) {
53                 bmcr |= BMCR_FULLDPLX;
54
55                 switch (fp->status.speed) {
56                 case 1000:
57                         bmsr |= BMSR_ESTATEN;
58                         bmcr |= BMCR_SPEED1000;
59                         lpagb |= LPA_1000FULL;
60                         break;
61                 case 100:
62                         bmsr |= BMSR_100FULL;
63                         bmcr |= BMCR_SPEED100;
64                         lpa |= LPA_100FULL;
65                         break;
66                 case 10:
67                         bmsr |= BMSR_10FULL;
68                         lpa |= LPA_10FULL;
69                         break;
70                 default:
71                         printk(KERN_WARNING "fixed phy: unknown speed\n");
72                         return -EINVAL;
73                 }
74         } else {
75                 switch (fp->status.speed) {
76                 case 1000:
77                         bmsr |= BMSR_ESTATEN;
78                         bmcr |= BMCR_SPEED1000;
79                         lpagb |= LPA_1000HALF;
80                         break;
81                 case 100:
82                         bmsr |= BMSR_100HALF;
83                         bmcr |= BMCR_SPEED100;
84                         lpa |= LPA_100HALF;
85                         break;
86                 case 10:
87                         bmsr |= BMSR_10HALF;
88                         lpa |= LPA_10HALF;
89                         break;
90                 default:
91                         printk(KERN_WARNING "fixed phy: unknown speed\n");
92                         return -EINVAL;
93                 }
94         }
95
96         if (fp->status.link)
97                 bmsr |= BMSR_LSTATUS | BMSR_ANEGCOMPLETE;
98
99         if (fp->status.pause)
100                 lpa |= LPA_PAUSE_CAP;
101
102         if (fp->status.asym_pause)
103                 lpa |= LPA_PAUSE_ASYM;
104
105         fp->regs[MII_PHYSID1] = fp->id >> 16;
106         fp->regs[MII_PHYSID2] = fp->id;
107
108         fp->regs[MII_BMSR] = bmsr;
109         fp->regs[MII_BMCR] = bmcr;
110         fp->regs[MII_LPA] = lpa;
111         fp->regs[MII_STAT1000] = lpagb;
112
113         return 0;
114 }
115
116 static int fixed_mdio_read(struct mii_bus *bus, int phy_id, int reg_num)
117 {
118         struct fixed_mdio_bus *fmb = container_of(bus, struct fixed_mdio_bus,
119                                                   mii_bus);
120         struct fixed_phy *fp;
121
122         if (reg_num >= MII_REGS_NUM)
123                 return -1;
124
125         list_for_each_entry(fp, &fmb->phys, node) {
126                 if (fp->id == phy_id) {
127                         /* Issue callback if user registered it. */
128                         if (fp->link_update) {
129                                 fp->link_update(fp->phydev->attached_dev,
130                                                 &fp->status);
131                                 fixed_phy_update_regs(fp);
132                         }
133                         return fp->regs[reg_num];
134                 }
135         }
136
137         return 0xFFFF;
138 }
139
140 static int fixed_mdio_write(struct mii_bus *bus, int phy_id, int reg_num,
141                             u16 val)
142 {
143         return 0;
144 }
145
146 /*
147  * If something weird is required to be done with link/speed,
148  * network driver is able to assign a function to implement this.
149  * May be useful for PHY's that need to be software-driven.
150  */
151 int fixed_phy_set_link_update(struct phy_device *phydev,
152                               int (*link_update)(struct net_device *,
153                                                  struct fixed_phy_status *))
154 {
155         struct fixed_mdio_bus *fmb = &platform_fmb;
156         struct fixed_phy *fp;
157
158         if (!link_update || !phydev || !phydev->bus)
159                 return -EINVAL;
160
161         list_for_each_entry(fp, &fmb->phys, node) {
162                 if (fp->id == phydev->phy_id) {
163                         fp->link_update = link_update;
164                         fp->phydev = phydev;
165                         return 0;
166                 }
167         }
168
169         return -ENOENT;
170 }
171 EXPORT_SYMBOL_GPL(fixed_phy_set_link_update);
172
173 int fixed_phy_add(unsigned int irq, int phy_id,
174                   struct fixed_phy_status *status)
175 {
176         int ret;
177         struct fixed_mdio_bus *fmb = &platform_fmb;
178         struct fixed_phy *fp;
179
180         fp = kzalloc(sizeof(*fp), GFP_KERNEL);
181         if (!fp)
182                 return -ENOMEM;
183
184         memset(fp->regs, 0xFF,  sizeof(fp->regs[0]) * MII_REGS_NUM);
185
186         fmb->irqs[phy_id] = irq;
187
188         fp->id = phy_id;
189         fp->status = *status;
190
191         ret = fixed_phy_update_regs(fp);
192         if (ret)
193                 goto err_regs;
194
195         list_add_tail(&fp->node, &fmb->phys);
196
197         return 0;
198
199 err_regs:
200         kfree(fp);
201         return ret;
202 }
203 EXPORT_SYMBOL_GPL(fixed_phy_add);
204
205 static int __init fixed_mdio_bus_init(void)
206 {
207         struct fixed_mdio_bus *fmb = &platform_fmb;
208         int ret;
209
210         pdev = platform_device_register_simple("Fixed MDIO bus", 0, NULL, 0);
211         if (!pdev) {
212                 ret = -ENOMEM;
213                 goto err_pdev;
214         }
215
216         snprintf(fmb->mii_bus.id, MII_BUS_ID_SIZE, "0");
217         fmb->mii_bus.name = "Fixed MDIO Bus";
218         fmb->mii_bus.dev = &pdev->dev;
219         fmb->mii_bus.read = &fixed_mdio_read;
220         fmb->mii_bus.write = &fixed_mdio_write;
221         fmb->mii_bus.irq = fmb->irqs;
222
223         ret = mdiobus_register(&fmb->mii_bus);
224         if (ret)
225                 goto err_mdiobus_reg;
226
227         return 0;
228
229 err_mdiobus_reg:
230         platform_device_unregister(pdev);
231 err_pdev:
232         return ret;
233 }
234 module_init(fixed_mdio_bus_init);
235
236 static void __exit fixed_mdio_bus_exit(void)
237 {
238         struct fixed_mdio_bus *fmb = &platform_fmb;
239         struct fixed_phy *fp, *tmp;
240
241         mdiobus_unregister(&fmb->mii_bus);
242         platform_device_unregister(pdev);
243
244         list_for_each_entry_safe(fp, tmp, &fmb->phys, node) {
245                 list_del(&fp->node);
246                 kfree(fp);
247         }
248 }
249 module_exit(fixed_mdio_bus_exit);
250
251 MODULE_DESCRIPTION("Fixed MDIO bus (MDIO bus emulation with fixed PHYs)");
252 MODULE_AUTHOR("Vitaly Bordug");
253 MODULE_LICENSE("GPL");