1 | /* |
2 | * Combined Ethernet driver for Motorola MPC8xx and MPC82xx. |
3 | * |
4 | * Copyright (c) 2003 Intracom S.A. |
5 | * by Pantelis Antoniou <panto@intracom.gr> |
6 | * |
7 | * 2005 (c) MontaVista Software, Inc. |
8 | * Vitaly Bordug <vbordug@ru.mvista.com> |
9 | * |
10 | * This file is licensed under the terms of the GNU General Public License |
11 | * version 2. This program is licensed "as is" without any warranty of any |
12 | * kind, whether express or implied. |
13 | */ |
14 | |
15 | #include <linux/module.h> |
16 | #include <linux/types.h> |
17 | #include <linux/kernel.h> |
18 | #include <linux/string.h> |
19 | #include <linux/ptrace.h> |
20 | #include <linux/errno.h> |
21 | #include <linux/ioport.h> |
22 | #include <linux/slab.h> |
23 | #include <linux/interrupt.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/mii.h> |
30 | #include <linux/ethtool.h> |
31 | #include <linux/bitops.h> |
32 | #include <linux/platform_device.h> |
33 | #include <linux/property.h> |
34 | #include <linux/of.h> |
35 | #include <linux/of_address.h> |
36 | #include <linux/of_mdio.h> |
37 | #include <linux/pgtable.h> |
38 | |
39 | #include <asm/irq.h> |
40 | #include <linux/uaccess.h> |
41 | #include <asm/mpc5xxx.h> |
42 | |
43 | #include "fs_enet.h" |
44 | #include "fec.h" |
45 | |
46 | /* Make MII read/write commands for the FEC. |
47 | */ |
48 | #define mk_mii_read(REG) (0x60020000 | ((REG & 0x1f) << 18)) |
49 | #define mk_mii_write(REG, VAL) (0x50020000 | ((REG & 0x1f) << 18) | (VAL & 0xffff)) |
50 | #define mk_mii_end 0 |
51 | |
52 | #define FEC_MII_LOOPS 10000 |
53 | |
54 | static int fs_enet_fec_mii_read(struct mii_bus *bus , int phy_id, int location) |
55 | { |
56 | struct fec_info* fec = bus->priv; |
57 | struct fec __iomem *fecp = fec->fecp; |
58 | int i, ret = -1; |
59 | |
60 | BUG_ON((in_be32(&fecp->fec_r_cntrl) & FEC_RCNTRL_MII_MODE) == 0); |
61 | |
62 | /* Add PHY address to register command. */ |
63 | out_be32(&fecp->fec_mii_data, (phy_id << 23) | mk_mii_read(location)); |
64 | |
65 | for (i = 0; i < FEC_MII_LOOPS; i++) |
66 | if ((in_be32(&fecp->fec_ievent) & FEC_ENET_MII) != 0) |
67 | break; |
68 | |
69 | if (i < FEC_MII_LOOPS) { |
70 | out_be32(&fecp->fec_ievent, FEC_ENET_MII); |
71 | ret = in_be32(&fecp->fec_mii_data) & 0xffff; |
72 | } |
73 | |
74 | return ret; |
75 | } |
76 | |
77 | static int fs_enet_fec_mii_write(struct mii_bus *bus, int phy_id, int location, u16 val) |
78 | { |
79 | struct fec_info* fec = bus->priv; |
80 | struct fec __iomem *fecp = fec->fecp; |
81 | int i; |
82 | |
83 | /* this must never happen */ |
84 | BUG_ON((in_be32(&fecp->fec_r_cntrl) & FEC_RCNTRL_MII_MODE) == 0); |
85 | |
86 | /* Add PHY address to register command. */ |
87 | out_be32(&fecp->fec_mii_data, (phy_id << 23) | mk_mii_write(location, val)); |
88 | |
89 | for (i = 0; i < FEC_MII_LOOPS; i++) |
90 | if ((in_be32(&fecp->fec_ievent) & FEC_ENET_MII) != 0) |
91 | break; |
92 | |
93 | if (i < FEC_MII_LOOPS) |
94 | out_be32(&fecp->fec_ievent, FEC_ENET_MII); |
95 | |
96 | return 0; |
97 | |
98 | } |
99 | |
100 | static int fs_enet_mdio_probe(struct platform_device *ofdev) |
101 | { |
102 | struct resource res; |
103 | struct mii_bus *new_bus; |
104 | struct fec_info *fec; |
105 | int (*get_bus_freq)(struct device *); |
106 | int ret = -ENOMEM, clock, speed; |
107 | |
108 | get_bus_freq = device_get_match_data(dev: &ofdev->dev); |
109 | |
110 | new_bus = mdiobus_alloc(); |
111 | if (!new_bus) |
112 | goto out; |
113 | |
114 | fec = kzalloc(sizeof(struct fec_info), GFP_KERNEL); |
115 | if (!fec) |
116 | goto out_mii; |
117 | |
118 | new_bus->priv = fec; |
119 | new_bus->name = "FEC MII Bus" ; |
120 | new_bus->read = &fs_enet_fec_mii_read; |
121 | new_bus->write = &fs_enet_fec_mii_write; |
122 | |
123 | ret = of_address_to_resource(dev: ofdev->dev.of_node, index: 0, r: &res); |
124 | if (ret) |
125 | goto out_res; |
126 | |
127 | snprintf(buf: new_bus->id, MII_BUS_ID_SIZE, fmt: "%pap" , &res.start); |
128 | |
129 | fec->fecp = ioremap(offset: res.start, size: resource_size(res: &res)); |
130 | if (!fec->fecp) { |
131 | ret = -ENOMEM; |
132 | goto out_fec; |
133 | } |
134 | |
135 | if (get_bus_freq) { |
136 | clock = get_bus_freq(&ofdev->dev); |
137 | if (!clock) { |
138 | /* Use maximum divider if clock is unknown */ |
139 | dev_warn(&ofdev->dev, "could not determine IPS clock\n" ); |
140 | clock = 0x3F * 5000000; |
141 | } |
142 | } else |
143 | clock = ppc_proc_freq; |
144 | |
145 | /* |
146 | * Scale for a MII clock <= 2.5 MHz |
147 | * Note that only 6 bits (25:30) are available for MII speed. |
148 | */ |
149 | speed = (clock + 4999999) / 5000000; |
150 | if (speed > 0x3F) { |
151 | speed = 0x3F; |
152 | dev_err(&ofdev->dev, |
153 | "MII clock (%d Hz) exceeds max (2.5 MHz)\n" , |
154 | clock / speed); |
155 | } |
156 | |
157 | fec->mii_speed = speed << 1; |
158 | |
159 | setbits32(&fec->fecp->fec_r_cntrl, FEC_RCNTRL_MII_MODE); |
160 | setbits32(&fec->fecp->fec_ecntrl, FEC_ECNTRL_PINMUX | |
161 | FEC_ECNTRL_ETHER_EN); |
162 | out_be32(&fec->fecp->fec_ievent, FEC_ENET_MII); |
163 | clrsetbits_be32(&fec->fecp->fec_mii_speed, 0x7E, fec->mii_speed); |
164 | |
165 | new_bus->phy_mask = ~0; |
166 | |
167 | new_bus->parent = &ofdev->dev; |
168 | platform_set_drvdata(pdev: ofdev, data: new_bus); |
169 | |
170 | ret = of_mdiobus_register(mdio: new_bus, np: ofdev->dev.of_node); |
171 | if (ret) |
172 | goto out_unmap_regs; |
173 | |
174 | return 0; |
175 | |
176 | out_unmap_regs: |
177 | iounmap(addr: fec->fecp); |
178 | out_res: |
179 | out_fec: |
180 | kfree(objp: fec); |
181 | out_mii: |
182 | mdiobus_free(bus: new_bus); |
183 | out: |
184 | return ret; |
185 | } |
186 | |
187 | static void fs_enet_mdio_remove(struct platform_device *ofdev) |
188 | { |
189 | struct mii_bus *bus = platform_get_drvdata(pdev: ofdev); |
190 | struct fec_info *fec = bus->priv; |
191 | |
192 | mdiobus_unregister(bus); |
193 | iounmap(addr: fec->fecp); |
194 | kfree(objp: fec); |
195 | mdiobus_free(bus); |
196 | } |
197 | |
198 | static const struct of_device_id fs_enet_mdio_fec_match[] = { |
199 | { |
200 | .compatible = "fsl,pq1-fec-mdio" , |
201 | }, |
202 | #if defined(CONFIG_PPC_MPC512x) |
203 | { |
204 | .compatible = "fsl,mpc5121-fec-mdio" , |
205 | .data = mpc5xxx_get_bus_frequency, |
206 | }, |
207 | #endif |
208 | {}, |
209 | }; |
210 | MODULE_DEVICE_TABLE(of, fs_enet_mdio_fec_match); |
211 | |
212 | static struct platform_driver fs_enet_fec_mdio_driver = { |
213 | .driver = { |
214 | .name = "fsl-fec-mdio" , |
215 | .of_match_table = fs_enet_mdio_fec_match, |
216 | }, |
217 | .probe = fs_enet_mdio_probe, |
218 | .remove_new = fs_enet_mdio_remove, |
219 | }; |
220 | |
221 | module_platform_driver(fs_enet_fec_mdio_driver); |
222 | MODULE_LICENSE("GPL" ); |
223 | |