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/ioport.h> |
17 | #include <linux/slab.h> |
18 | #include <linux/interrupt.h> |
19 | #include <linux/netdevice.h> |
20 | #include <linux/etherdevice.h> |
21 | #include <linux/mii.h> |
22 | #include <linux/platform_device.h> |
23 | #include <linux/mdio-bitbang.h> |
24 | #include <linux/of_address.h> |
25 | #include <linux/of_mdio.h> |
26 | #include <linux/of_platform.h> |
27 | |
28 | #include "fs_enet.h" |
29 | |
30 | struct bb_info { |
31 | struct mdiobb_ctrl ctrl; |
32 | u32 __iomem *dir; |
33 | u32 __iomem *dat; |
34 | u32 mdio_msk; |
35 | u32 mdc_msk; |
36 | }; |
37 | |
38 | /* FIXME: If any other users of GPIO crop up, then these will have to |
39 | * have some sort of global synchronization to avoid races with other |
40 | * pins on the same port. The ideal solution would probably be to |
41 | * bind the ports to a GPIO driver, and have this be a client of it. |
42 | */ |
43 | static inline void bb_set(u32 __iomem *p, u32 m) |
44 | { |
45 | out_be32(p, in_be32(p) | m); |
46 | } |
47 | |
48 | static inline void bb_clr(u32 __iomem *p, u32 m) |
49 | { |
50 | out_be32(p, in_be32(p) & ~m); |
51 | } |
52 | |
53 | static inline int bb_read(u32 __iomem *p, u32 m) |
54 | { |
55 | return (in_be32(p) & m) != 0; |
56 | } |
57 | |
58 | static inline void mdio_dir(struct mdiobb_ctrl *ctrl, int dir) |
59 | { |
60 | struct bb_info *bitbang = container_of(ctrl, struct bb_info, ctrl); |
61 | |
62 | if (dir) |
63 | bb_set(p: bitbang->dir, m: bitbang->mdio_msk); |
64 | else |
65 | bb_clr(p: bitbang->dir, m: bitbang->mdio_msk); |
66 | |
67 | /* Read back to flush the write. */ |
68 | in_be32(bitbang->dir); |
69 | } |
70 | |
71 | static inline int mdio_read(struct mdiobb_ctrl *ctrl) |
72 | { |
73 | struct bb_info *bitbang = container_of(ctrl, struct bb_info, ctrl); |
74 | return bb_read(p: bitbang->dat, m: bitbang->mdio_msk); |
75 | } |
76 | |
77 | static inline void mdio(struct mdiobb_ctrl *ctrl, int what) |
78 | { |
79 | struct bb_info *bitbang = container_of(ctrl, struct bb_info, ctrl); |
80 | |
81 | if (what) |
82 | bb_set(p: bitbang->dat, m: bitbang->mdio_msk); |
83 | else |
84 | bb_clr(p: bitbang->dat, m: bitbang->mdio_msk); |
85 | |
86 | /* Read back to flush the write. */ |
87 | in_be32(bitbang->dat); |
88 | } |
89 | |
90 | static inline void mdc(struct mdiobb_ctrl *ctrl, int what) |
91 | { |
92 | struct bb_info *bitbang = container_of(ctrl, struct bb_info, ctrl); |
93 | |
94 | if (what) |
95 | bb_set(p: bitbang->dat, m: bitbang->mdc_msk); |
96 | else |
97 | bb_clr(p: bitbang->dat, m: bitbang->mdc_msk); |
98 | |
99 | /* Read back to flush the write. */ |
100 | in_be32(bitbang->dat); |
101 | } |
102 | |
103 | static const struct mdiobb_ops bb_ops = { |
104 | .owner = THIS_MODULE, |
105 | .set_mdc = mdc, |
106 | .set_mdio_dir = mdio_dir, |
107 | .set_mdio_data = mdio, |
108 | .get_mdio_data = mdio_read, |
109 | }; |
110 | |
111 | static int fs_mii_bitbang_init(struct mii_bus *bus, struct device_node *np) |
112 | { |
113 | struct resource res; |
114 | const u32 *data; |
115 | int mdio_pin, mdc_pin, len; |
116 | struct bb_info *bitbang = bus->priv; |
117 | |
118 | int ret = of_address_to_resource(dev: np, index: 0, r: &res); |
119 | if (ret) |
120 | return ret; |
121 | |
122 | if (resource_size(res: &res) <= 13) |
123 | return -ENODEV; |
124 | |
125 | /* This should really encode the pin number as well, but all |
126 | * we get is an int, and the odds of multiple bitbang mdio buses |
127 | * is low enough that it's not worth going too crazy. |
128 | */ |
129 | snprintf(buf: bus->id, MII_BUS_ID_SIZE, fmt: "%x" , res.start); |
130 | |
131 | data = of_get_property(node: np, name: "fsl,mdio-pin" , lenp: &len); |
132 | if (!data || len != 4) |
133 | return -ENODEV; |
134 | mdio_pin = *data; |
135 | |
136 | data = of_get_property(node: np, name: "fsl,mdc-pin" , lenp: &len); |
137 | if (!data || len != 4) |
138 | return -ENODEV; |
139 | mdc_pin = *data; |
140 | |
141 | bitbang->dir = ioremap(offset: res.start, size: resource_size(res: &res)); |
142 | if (!bitbang->dir) |
143 | return -ENOMEM; |
144 | |
145 | bitbang->dat = bitbang->dir + 4; |
146 | bitbang->mdio_msk = 1 << (31 - mdio_pin); |
147 | bitbang->mdc_msk = 1 << (31 - mdc_pin); |
148 | |
149 | return 0; |
150 | } |
151 | |
152 | static int fs_enet_mdio_probe(struct platform_device *ofdev) |
153 | { |
154 | struct mii_bus *new_bus; |
155 | struct bb_info *bitbang; |
156 | int ret = -ENOMEM; |
157 | |
158 | bitbang = kzalloc(size: sizeof(struct bb_info), GFP_KERNEL); |
159 | if (!bitbang) |
160 | goto out; |
161 | |
162 | bitbang->ctrl.ops = &bb_ops; |
163 | |
164 | new_bus = alloc_mdio_bitbang(ctrl: &bitbang->ctrl); |
165 | if (!new_bus) |
166 | goto out_free_priv; |
167 | |
168 | new_bus->name = "CPM2 Bitbanged MII" , |
169 | |
170 | ret = fs_mii_bitbang_init(bus: new_bus, np: ofdev->dev.of_node); |
171 | if (ret) |
172 | goto out_free_bus; |
173 | |
174 | new_bus->phy_mask = ~0; |
175 | |
176 | new_bus->parent = &ofdev->dev; |
177 | platform_set_drvdata(pdev: ofdev, data: new_bus); |
178 | |
179 | ret = of_mdiobus_register(mdio: new_bus, np: ofdev->dev.of_node); |
180 | if (ret) |
181 | goto out_unmap_regs; |
182 | |
183 | return 0; |
184 | |
185 | out_unmap_regs: |
186 | iounmap(addr: bitbang->dir); |
187 | out_free_bus: |
188 | free_mdio_bitbang(bus: new_bus); |
189 | out_free_priv: |
190 | kfree(objp: bitbang); |
191 | out: |
192 | return ret; |
193 | } |
194 | |
195 | static void fs_enet_mdio_remove(struct platform_device *ofdev) |
196 | { |
197 | struct mii_bus *bus = platform_get_drvdata(pdev: ofdev); |
198 | struct bb_info *bitbang = bus->priv; |
199 | |
200 | mdiobus_unregister(bus); |
201 | free_mdio_bitbang(bus); |
202 | iounmap(addr: bitbang->dir); |
203 | kfree(objp: bitbang); |
204 | } |
205 | |
206 | static const struct of_device_id fs_enet_mdio_bb_match[] = { |
207 | { |
208 | .compatible = "fsl,cpm2-mdio-bitbang" , |
209 | }, |
210 | {}, |
211 | }; |
212 | MODULE_DEVICE_TABLE(of, fs_enet_mdio_bb_match); |
213 | |
214 | static struct platform_driver fs_enet_bb_mdio_driver = { |
215 | .driver = { |
216 | .name = "fsl-bb-mdio" , |
217 | .of_match_table = fs_enet_mdio_bb_match, |
218 | }, |
219 | .probe = fs_enet_mdio_probe, |
220 | .remove_new = fs_enet_mdio_remove, |
221 | }; |
222 | |
223 | module_platform_driver(fs_enet_bb_mdio_driver); |
224 | MODULE_LICENSE("GPL" ); |
225 | |