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