Commit | Line | Data |
---|---|---|
25d967b7 DD |
1 | /* |
2 | * This file is subject to the terms and conditions of the GNU General Public | |
3 | * License. See the file "COPYING" in the main directory of this archive | |
4 | * for more details. | |
5 | * | |
775ae9b2 | 6 | * Copyright (C) 2009-2012 Cavium, Inc. |
25d967b7 DD |
7 | */ |
8 | ||
25d967b7 | 9 | #include <linux/platform_device.h> |
2fd46f47 DD |
10 | #include <linux/of_mdio.h> |
11 | #include <linux/delay.h> | |
12 | #include <linux/module.h> | |
2fd46f47 | 13 | #include <linux/gfp.h> |
25d967b7 | 14 | #include <linux/phy.h> |
2fd46f47 | 15 | #include <linux/io.h> |
25d967b7 DD |
16 | |
17 | #include <asm/octeon/octeon.h> | |
18 | #include <asm/octeon/cvmx-smix-defs.h> | |
19 | ||
20 | #define DRV_VERSION "1.0" | |
21 | #define DRV_DESCRIPTION "Cavium Networks Octeon SMI/MDIO driver" | |
22 | ||
2fd46f47 DD |
23 | #define SMI_CMD 0x0 |
24 | #define SMI_WR_DAT 0x8 | |
25 | #define SMI_RD_DAT 0x10 | |
26 | #define SMI_CLK 0x18 | |
27 | #define SMI_EN 0x20 | |
28 | ||
775ae9b2 DD |
29 | enum octeon_mdiobus_mode { |
30 | UNINIT = 0, | |
31 | C22, | |
32 | C45 | |
33 | }; | |
34 | ||
25d967b7 DD |
35 | struct octeon_mdiobus { |
36 | struct mii_bus *mii_bus; | |
2fd46f47 DD |
37 | u64 register_base; |
38 | resource_size_t mdio_phys; | |
39 | resource_size_t regsize; | |
775ae9b2 | 40 | enum octeon_mdiobus_mode mode; |
25d967b7 DD |
41 | int phy_irq[PHY_MAX_ADDR]; |
42 | }; | |
43 | ||
775ae9b2 DD |
44 | static void octeon_mdiobus_set_mode(struct octeon_mdiobus *p, |
45 | enum octeon_mdiobus_mode m) | |
46 | { | |
47 | union cvmx_smix_clk smi_clk; | |
48 | ||
49 | if (m == p->mode) | |
50 | return; | |
51 | ||
52 | smi_clk.u64 = cvmx_read_csr(p->register_base + SMI_CLK); | |
53 | smi_clk.s.mode = (m == C45) ? 1 : 0; | |
54 | smi_clk.s.preamble = 1; | |
55 | cvmx_write_csr(p->register_base + SMI_CLK, smi_clk.u64); | |
56 | p->mode = m; | |
57 | } | |
58 | ||
59 | static int octeon_mdiobus_c45_addr(struct octeon_mdiobus *p, | |
60 | int phy_id, int regnum) | |
61 | { | |
62 | union cvmx_smix_cmd smi_cmd; | |
63 | union cvmx_smix_wr_dat smi_wr; | |
64 | int timeout = 1000; | |
65 | ||
66 | octeon_mdiobus_set_mode(p, C45); | |
67 | ||
68 | smi_wr.u64 = 0; | |
69 | smi_wr.s.dat = regnum & 0xffff; | |
70 | cvmx_write_csr(p->register_base + SMI_WR_DAT, smi_wr.u64); | |
71 | ||
72 | regnum = (regnum >> 16) & 0x1f; | |
73 | ||
74 | smi_cmd.u64 = 0; | |
75 | smi_cmd.s.phy_op = 0; /* MDIO_CLAUSE_45_ADDRESS */ | |
76 | smi_cmd.s.phy_adr = phy_id; | |
77 | smi_cmd.s.reg_adr = regnum; | |
78 | cvmx_write_csr(p->register_base + SMI_CMD, smi_cmd.u64); | |
79 | ||
80 | do { | |
81 | /* Wait 1000 clocks so we don't saturate the RSL bus | |
82 | * doing reads. | |
83 | */ | |
84 | __delay(1000); | |
85 | smi_wr.u64 = cvmx_read_csr(p->register_base + SMI_WR_DAT); | |
86 | } while (smi_wr.s.pending && --timeout); | |
87 | ||
88 | if (timeout <= 0) | |
89 | return -EIO; | |
90 | return 0; | |
91 | } | |
92 | ||
25d967b7 DD |
93 | static int octeon_mdiobus_read(struct mii_bus *bus, int phy_id, int regnum) |
94 | { | |
95 | struct octeon_mdiobus *p = bus->priv; | |
96 | union cvmx_smix_cmd smi_cmd; | |
97 | union cvmx_smix_rd_dat smi_rd; | |
775ae9b2 | 98 | unsigned int op = 1; /* MDIO_CLAUSE_22_READ */ |
25d967b7 DD |
99 | int timeout = 1000; |
100 | ||
775ae9b2 DD |
101 | if (regnum & MII_ADDR_C45) { |
102 | int r = octeon_mdiobus_c45_addr(p, phy_id, regnum); | |
103 | if (r < 0) | |
104 | return r; | |
105 | ||
106 | regnum = (regnum >> 16) & 0x1f; | |
107 | op = 3; /* MDIO_CLAUSE_45_READ */ | |
108 | } else { | |
109 | octeon_mdiobus_set_mode(p, C22); | |
110 | } | |
111 | ||
112 | ||
25d967b7 | 113 | smi_cmd.u64 = 0; |
775ae9b2 | 114 | smi_cmd.s.phy_op = op; |
25d967b7 DD |
115 | smi_cmd.s.phy_adr = phy_id; |
116 | smi_cmd.s.reg_adr = regnum; | |
2fd46f47 | 117 | cvmx_write_csr(p->register_base + SMI_CMD, smi_cmd.u64); |
25d967b7 DD |
118 | |
119 | do { | |
775ae9b2 | 120 | /* Wait 1000 clocks so we don't saturate the RSL bus |
25d967b7 DD |
121 | * doing reads. |
122 | */ | |
2fd46f47 DD |
123 | __delay(1000); |
124 | smi_rd.u64 = cvmx_read_csr(p->register_base + SMI_RD_DAT); | |
25d967b7 DD |
125 | } while (smi_rd.s.pending && --timeout); |
126 | ||
127 | if (smi_rd.s.val) | |
128 | return smi_rd.s.dat; | |
129 | else | |
130 | return -EIO; | |
131 | } | |
132 | ||
133 | static int octeon_mdiobus_write(struct mii_bus *bus, int phy_id, | |
134 | int regnum, u16 val) | |
135 | { | |
136 | struct octeon_mdiobus *p = bus->priv; | |
137 | union cvmx_smix_cmd smi_cmd; | |
138 | union cvmx_smix_wr_dat smi_wr; | |
775ae9b2 | 139 | unsigned int op = 0; /* MDIO_CLAUSE_22_WRITE */ |
25d967b7 DD |
140 | int timeout = 1000; |
141 | ||
775ae9b2 DD |
142 | |
143 | if (regnum & MII_ADDR_C45) { | |
144 | int r = octeon_mdiobus_c45_addr(p, phy_id, regnum); | |
145 | if (r < 0) | |
146 | return r; | |
147 | ||
148 | regnum = (regnum >> 16) & 0x1f; | |
149 | op = 1; /* MDIO_CLAUSE_45_WRITE */ | |
150 | } else { | |
151 | octeon_mdiobus_set_mode(p, C22); | |
152 | } | |
153 | ||
25d967b7 DD |
154 | smi_wr.u64 = 0; |
155 | smi_wr.s.dat = val; | |
2fd46f47 | 156 | cvmx_write_csr(p->register_base + SMI_WR_DAT, smi_wr.u64); |
25d967b7 DD |
157 | |
158 | smi_cmd.u64 = 0; | |
775ae9b2 | 159 | smi_cmd.s.phy_op = op; |
25d967b7 DD |
160 | smi_cmd.s.phy_adr = phy_id; |
161 | smi_cmd.s.reg_adr = regnum; | |
2fd46f47 | 162 | cvmx_write_csr(p->register_base + SMI_CMD, smi_cmd.u64); |
25d967b7 DD |
163 | |
164 | do { | |
775ae9b2 | 165 | /* Wait 1000 clocks so we don't saturate the RSL bus |
25d967b7 DD |
166 | * doing reads. |
167 | */ | |
2fd46f47 DD |
168 | __delay(1000); |
169 | smi_wr.u64 = cvmx_read_csr(p->register_base + SMI_WR_DAT); | |
25d967b7 DD |
170 | } while (smi_wr.s.pending && --timeout); |
171 | ||
172 | if (timeout <= 0) | |
173 | return -EIO; | |
174 | ||
175 | return 0; | |
176 | } | |
177 | ||
633d1594 | 178 | static int octeon_mdiobus_probe(struct platform_device *pdev) |
25d967b7 DD |
179 | { |
180 | struct octeon_mdiobus *bus; | |
2fd46f47 | 181 | struct resource *res_mem; |
6c17812d | 182 | union cvmx_smix_en smi_en; |
25d967b7 DD |
183 | int err = -ENOENT; |
184 | ||
185 | bus = devm_kzalloc(&pdev->dev, sizeof(*bus), GFP_KERNEL); | |
186 | if (!bus) | |
187 | return -ENOMEM; | |
188 | ||
2fd46f47 DD |
189 | res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
190 | ||
191 | if (res_mem == NULL) { | |
192 | dev_err(&pdev->dev, "found no memory resource\n"); | |
193 | err = -ENXIO; | |
194 | goto fail; | |
195 | } | |
196 | bus->mdio_phys = res_mem->start; | |
197 | bus->regsize = resource_size(res_mem); | |
198 | if (!devm_request_mem_region(&pdev->dev, bus->mdio_phys, bus->regsize, | |
199 | res_mem->name)) { | |
200 | dev_err(&pdev->dev, "request_mem_region failed\n"); | |
201 | goto fail; | |
202 | } | |
203 | bus->register_base = | |
204 | (u64)devm_ioremap(&pdev->dev, bus->mdio_phys, bus->regsize); | |
25d967b7 DD |
205 | |
206 | bus->mii_bus = mdiobus_alloc(); | |
207 | ||
208 | if (!bus->mii_bus) | |
2fd46f47 | 209 | goto fail; |
25d967b7 | 210 | |
6c17812d DD |
211 | smi_en.u64 = 0; |
212 | smi_en.s.en = 1; | |
2fd46f47 | 213 | cvmx_write_csr(bus->register_base + SMI_EN, smi_en.u64); |
25d967b7 DD |
214 | |
215 | bus->mii_bus->priv = bus; | |
216 | bus->mii_bus->irq = bus->phy_irq; | |
217 | bus->mii_bus->name = "mdio-octeon"; | |
2fd46f47 | 218 | snprintf(bus->mii_bus->id, MII_BUS_ID_SIZE, "%llx", bus->register_base); |
25d967b7 DD |
219 | bus->mii_bus->parent = &pdev->dev; |
220 | ||
221 | bus->mii_bus->read = octeon_mdiobus_read; | |
222 | bus->mii_bus->write = octeon_mdiobus_write; | |
223 | ||
f8825669 | 224 | platform_set_drvdata(pdev, bus); |
25d967b7 | 225 | |
2fd46f47 | 226 | err = of_mdiobus_register(bus->mii_bus, pdev->dev.of_node); |
25d967b7 | 227 | if (err) |
2fd46f47 | 228 | goto fail_register; |
25d967b7 DD |
229 | |
230 | dev_info(&pdev->dev, "Version " DRV_VERSION "\n"); | |
231 | ||
232 | return 0; | |
2fd46f47 | 233 | fail_register: |
25d967b7 | 234 | mdiobus_free(bus->mii_bus); |
2fd46f47 | 235 | fail: |
6c17812d | 236 | smi_en.u64 = 0; |
2fd46f47 | 237 | cvmx_write_csr(bus->register_base + SMI_EN, smi_en.u64); |
25d967b7 DD |
238 | return err; |
239 | } | |
240 | ||
633d1594 | 241 | static int octeon_mdiobus_remove(struct platform_device *pdev) |
25d967b7 DD |
242 | { |
243 | struct octeon_mdiobus *bus; | |
6c17812d | 244 | union cvmx_smix_en smi_en; |
25d967b7 | 245 | |
2c0c4fbe | 246 | bus = platform_get_drvdata(pdev); |
25d967b7 DD |
247 | |
248 | mdiobus_unregister(bus->mii_bus); | |
249 | mdiobus_free(bus->mii_bus); | |
6c17812d | 250 | smi_en.u64 = 0; |
2fd46f47 | 251 | cvmx_write_csr(bus->register_base + SMI_EN, smi_en.u64); |
25d967b7 DD |
252 | return 0; |
253 | } | |
254 | ||
2fd46f47 DD |
255 | static struct of_device_id octeon_mdiobus_match[] = { |
256 | { | |
257 | .compatible = "cavium,octeon-3860-mdio", | |
258 | }, | |
259 | {}, | |
260 | }; | |
261 | MODULE_DEVICE_TABLE(of, octeon_mdiobus_match); | |
262 | ||
25d967b7 DD |
263 | static struct platform_driver octeon_mdiobus_driver = { |
264 | .driver = { | |
265 | .name = "mdio-octeon", | |
266 | .owner = THIS_MODULE, | |
2fd46f47 | 267 | .of_match_table = octeon_mdiobus_match, |
25d967b7 DD |
268 | }, |
269 | .probe = octeon_mdiobus_probe, | |
633d1594 | 270 | .remove = octeon_mdiobus_remove, |
25d967b7 DD |
271 | }; |
272 | ||
273 | void octeon_mdiobus_force_mod_depencency(void) | |
274 | { | |
275 | /* Let ethernet drivers force us to be loaded. */ | |
276 | } | |
277 | EXPORT_SYMBOL(octeon_mdiobus_force_mod_depencency); | |
278 | ||
9fad0c94 | 279 | module_platform_driver(octeon_mdiobus_driver); |
25d967b7 DD |
280 | |
281 | MODULE_DESCRIPTION(DRV_DESCRIPTION); | |
282 | MODULE_VERSION(DRV_VERSION); | |
283 | MODULE_AUTHOR("David Daney"); | |
284 | MODULE_LICENSE("GPL"); |