1 | // SPDX-License-Identifier: (GPL-2.0 OR MIT) |
2 | /* |
3 | * Driver for the MDIO interface of Microsemi network switches. |
4 | * |
5 | * Author: Alexandre Belloni <alexandre.belloni@bootlin.com> |
6 | * Copyright (c) 2017 Microsemi Corporation |
7 | */ |
8 | |
9 | #include <linux/bitops.h> |
10 | #include <linux/clk.h> |
11 | #include <linux/io.h> |
12 | #include <linux/iopoll.h> |
13 | #include <linux/kernel.h> |
14 | #include <linux/mdio/mdio-mscc-miim.h> |
15 | #include <linux/mfd/ocelot.h> |
16 | #include <linux/module.h> |
17 | #include <linux/of_mdio.h> |
18 | #include <linux/phy.h> |
19 | #include <linux/platform_device.h> |
20 | #include <linux/property.h> |
21 | #include <linux/regmap.h> |
22 | |
23 | #define MSCC_MIIM_REG_STATUS 0x0 |
24 | #define MSCC_MIIM_STATUS_STAT_PENDING BIT(2) |
25 | #define MSCC_MIIM_STATUS_STAT_BUSY BIT(3) |
26 | #define MSCC_MIIM_REG_CMD 0x8 |
27 | #define MSCC_MIIM_CMD_OPR_WRITE BIT(1) |
28 | #define MSCC_MIIM_CMD_OPR_READ BIT(2) |
29 | #define MSCC_MIIM_CMD_WRDATA_SHIFT 4 |
30 | #define MSCC_MIIM_CMD_REGAD_SHIFT 20 |
31 | #define MSCC_MIIM_CMD_PHYAD_SHIFT 25 |
32 | #define MSCC_MIIM_CMD_VLD BIT(31) |
33 | #define MSCC_MIIM_REG_DATA 0xC |
34 | #define MSCC_MIIM_DATA_ERROR (BIT(16) | BIT(17)) |
35 | #define MSCC_MIIM_REG_CFG 0x10 |
36 | #define MSCC_MIIM_CFG_PRESCALE_MASK GENMASK(7, 0) |
37 | |
38 | #define MSCC_PHY_REG_PHY_CFG 0x0 |
39 | #define PHY_CFG_PHY_ENA (BIT(0) | BIT(1) | BIT(2) | BIT(3)) |
40 | #define PHY_CFG_PHY_COMMON_RESET BIT(4) |
41 | #define PHY_CFG_PHY_RESET (BIT(5) | BIT(6) | BIT(7) | BIT(8)) |
42 | #define MSCC_PHY_REG_PHY_STATUS 0x4 |
43 | |
44 | #define LAN966X_CUPHY_COMMON_CFG 0x0 |
45 | #define CUPHY_COMMON_CFG_RESET_N BIT(0) |
46 | |
47 | struct mscc_miim_info { |
48 | unsigned int phy_reset_offset; |
49 | unsigned int phy_reset_bits; |
50 | }; |
51 | |
52 | struct mscc_miim_dev { |
53 | struct regmap *regs; |
54 | int mii_status_offset; |
55 | bool ignore_read_errors; |
56 | struct regmap *phy_regs; |
57 | const struct mscc_miim_info *info; |
58 | struct clk *clk; |
59 | u32 bus_freq; |
60 | }; |
61 | |
62 | /* When high resolution timers aren't built-in: we can't use usleep_range() as |
63 | * we would sleep way too long. Use udelay() instead. |
64 | */ |
65 | #define mscc_readx_poll_timeout(op, addr, val, cond, delay_us, timeout_us)\ |
66 | ({ \ |
67 | if (!IS_ENABLED(CONFIG_HIGH_RES_TIMERS)) \ |
68 | readx_poll_timeout_atomic(op, addr, val, cond, delay_us, \ |
69 | timeout_us); \ |
70 | readx_poll_timeout(op, addr, val, cond, delay_us, timeout_us); \ |
71 | }) |
72 | |
73 | static int mscc_miim_status(struct mii_bus *bus) |
74 | { |
75 | struct mscc_miim_dev *miim = bus->priv; |
76 | int val, ret; |
77 | |
78 | ret = regmap_read(map: miim->regs, |
79 | MSCC_MIIM_REG_STATUS + miim->mii_status_offset, val: &val); |
80 | if (ret < 0) { |
81 | WARN_ONCE(1, "mscc miim status read error %d\n" , ret); |
82 | return ret; |
83 | } |
84 | |
85 | return val; |
86 | } |
87 | |
88 | static int mscc_miim_wait_ready(struct mii_bus *bus) |
89 | { |
90 | u32 val; |
91 | |
92 | return mscc_readx_poll_timeout(mscc_miim_status, bus, val, |
93 | !(val & MSCC_MIIM_STATUS_STAT_BUSY), 50, |
94 | 10000); |
95 | } |
96 | |
97 | static int mscc_miim_wait_pending(struct mii_bus *bus) |
98 | { |
99 | u32 val; |
100 | |
101 | return mscc_readx_poll_timeout(mscc_miim_status, bus, val, |
102 | !(val & MSCC_MIIM_STATUS_STAT_PENDING), |
103 | 50, 10000); |
104 | } |
105 | |
106 | static int mscc_miim_read(struct mii_bus *bus, int mii_id, int regnum) |
107 | { |
108 | struct mscc_miim_dev *miim = bus->priv; |
109 | u32 val; |
110 | int ret; |
111 | |
112 | ret = mscc_miim_wait_pending(bus); |
113 | if (ret) |
114 | goto out; |
115 | |
116 | ret = regmap_write(map: miim->regs, |
117 | MSCC_MIIM_REG_CMD + miim->mii_status_offset, |
118 | MSCC_MIIM_CMD_VLD | |
119 | (mii_id << MSCC_MIIM_CMD_PHYAD_SHIFT) | |
120 | (regnum << MSCC_MIIM_CMD_REGAD_SHIFT) | |
121 | MSCC_MIIM_CMD_OPR_READ); |
122 | |
123 | if (ret < 0) { |
124 | WARN_ONCE(1, "mscc miim write cmd reg error %d\n" , ret); |
125 | goto out; |
126 | } |
127 | |
128 | ret = mscc_miim_wait_ready(bus); |
129 | if (ret) |
130 | goto out; |
131 | |
132 | ret = regmap_read(map: miim->regs, |
133 | MSCC_MIIM_REG_DATA + miim->mii_status_offset, val: &val); |
134 | if (ret < 0) { |
135 | WARN_ONCE(1, "mscc miim read data reg error %d\n" , ret); |
136 | goto out; |
137 | } |
138 | |
139 | if (!miim->ignore_read_errors && !!(val & MSCC_MIIM_DATA_ERROR)) { |
140 | ret = -EIO; |
141 | goto out; |
142 | } |
143 | |
144 | ret = val & 0xFFFF; |
145 | out: |
146 | return ret; |
147 | } |
148 | |
149 | static int mscc_miim_write(struct mii_bus *bus, int mii_id, |
150 | int regnum, u16 value) |
151 | { |
152 | struct mscc_miim_dev *miim = bus->priv; |
153 | int ret; |
154 | |
155 | ret = mscc_miim_wait_pending(bus); |
156 | if (ret < 0) |
157 | goto out; |
158 | |
159 | ret = regmap_write(map: miim->regs, |
160 | MSCC_MIIM_REG_CMD + miim->mii_status_offset, |
161 | MSCC_MIIM_CMD_VLD | |
162 | (mii_id << MSCC_MIIM_CMD_PHYAD_SHIFT) | |
163 | (regnum << MSCC_MIIM_CMD_REGAD_SHIFT) | |
164 | (value << MSCC_MIIM_CMD_WRDATA_SHIFT) | |
165 | MSCC_MIIM_CMD_OPR_WRITE); |
166 | |
167 | if (ret < 0) |
168 | WARN_ONCE(1, "mscc miim write error %d\n" , ret); |
169 | out: |
170 | return ret; |
171 | } |
172 | |
173 | static int mscc_miim_reset(struct mii_bus *bus) |
174 | { |
175 | struct mscc_miim_dev *miim = bus->priv; |
176 | unsigned int offset, bits; |
177 | int ret; |
178 | |
179 | if (!miim->phy_regs) |
180 | return 0; |
181 | |
182 | offset = miim->info->phy_reset_offset; |
183 | bits = miim->info->phy_reset_bits; |
184 | |
185 | ret = regmap_update_bits(map: miim->phy_regs, reg: offset, mask: bits, val: 0); |
186 | if (ret < 0) { |
187 | WARN_ONCE(1, "mscc reset set error %d\n" , ret); |
188 | return ret; |
189 | } |
190 | |
191 | ret = regmap_update_bits(map: miim->phy_regs, reg: offset, mask: bits, val: bits); |
192 | if (ret < 0) { |
193 | WARN_ONCE(1, "mscc reset clear error %d\n" , ret); |
194 | return ret; |
195 | } |
196 | |
197 | mdelay(500); |
198 | |
199 | return 0; |
200 | } |
201 | |
202 | static const struct regmap_config mscc_miim_regmap_config = { |
203 | .reg_bits = 32, |
204 | .val_bits = 32, |
205 | .reg_stride = 4, |
206 | }; |
207 | |
208 | static const struct regmap_config mscc_miim_phy_regmap_config = { |
209 | .reg_bits = 32, |
210 | .val_bits = 32, |
211 | .reg_stride = 4, |
212 | .name = "phy" , |
213 | }; |
214 | |
215 | int mscc_miim_setup(struct device *dev, struct mii_bus **pbus, const char *name, |
216 | struct regmap *mii_regmap, int status_offset, |
217 | bool ignore_read_errors) |
218 | { |
219 | struct mscc_miim_dev *miim; |
220 | struct mii_bus *bus; |
221 | |
222 | bus = devm_mdiobus_alloc_size(dev, sizeof_priv: sizeof(*miim)); |
223 | if (!bus) |
224 | return -ENOMEM; |
225 | |
226 | bus->name = name; |
227 | bus->read = mscc_miim_read; |
228 | bus->write = mscc_miim_write; |
229 | bus->reset = mscc_miim_reset; |
230 | snprintf(buf: bus->id, MII_BUS_ID_SIZE, fmt: "%s-mii" , dev_name(dev)); |
231 | bus->parent = dev; |
232 | |
233 | miim = bus->priv; |
234 | |
235 | *pbus = bus; |
236 | |
237 | miim->regs = mii_regmap; |
238 | miim->mii_status_offset = status_offset; |
239 | miim->ignore_read_errors = ignore_read_errors; |
240 | |
241 | *pbus = bus; |
242 | |
243 | return 0; |
244 | } |
245 | EXPORT_SYMBOL(mscc_miim_setup); |
246 | |
247 | static int mscc_miim_clk_set(struct mii_bus *bus) |
248 | { |
249 | struct mscc_miim_dev *miim = bus->priv; |
250 | unsigned long rate; |
251 | u32 div; |
252 | |
253 | /* Keep the current settings */ |
254 | if (!miim->bus_freq) |
255 | return 0; |
256 | |
257 | rate = clk_get_rate(clk: miim->clk); |
258 | |
259 | div = DIV_ROUND_UP(rate, 2 * miim->bus_freq) - 1; |
260 | if (div == 0 || div & ~MSCC_MIIM_CFG_PRESCALE_MASK) { |
261 | dev_err(&bus->dev, "Incorrect MDIO clock frequency\n" ); |
262 | return -EINVAL; |
263 | } |
264 | |
265 | return regmap_update_bits(map: miim->regs, MSCC_MIIM_REG_CFG, |
266 | MSCC_MIIM_CFG_PRESCALE_MASK, val: div); |
267 | } |
268 | |
269 | static int mscc_miim_probe(struct platform_device *pdev) |
270 | { |
271 | struct device_node *np = pdev->dev.of_node; |
272 | struct regmap *mii_regmap, *phy_regmap; |
273 | struct device *dev = &pdev->dev; |
274 | struct mscc_miim_dev *miim; |
275 | struct mii_bus *bus; |
276 | int ret; |
277 | |
278 | mii_regmap = ocelot_regmap_from_resource(pdev, index: 0, |
279 | config: &mscc_miim_regmap_config); |
280 | if (IS_ERR(ptr: mii_regmap)) |
281 | return dev_err_probe(dev, err: PTR_ERR(ptr: mii_regmap), |
282 | fmt: "Unable to create MIIM regmap\n" ); |
283 | |
284 | /* This resource is optional */ |
285 | phy_regmap = ocelot_regmap_from_resource_optional(pdev, index: 1, |
286 | config: &mscc_miim_phy_regmap_config); |
287 | if (IS_ERR(ptr: phy_regmap)) |
288 | return dev_err_probe(dev, err: PTR_ERR(ptr: phy_regmap), |
289 | fmt: "Unable to create phy register regmap\n" ); |
290 | |
291 | ret = mscc_miim_setup(dev, &bus, "mscc_miim" , mii_regmap, 0, false); |
292 | if (ret < 0) { |
293 | dev_err(dev, "Unable to setup the MDIO bus\n" ); |
294 | return ret; |
295 | } |
296 | |
297 | miim = bus->priv; |
298 | miim->phy_regs = phy_regmap; |
299 | |
300 | miim->info = device_get_match_data(dev); |
301 | if (!miim->info) |
302 | return -EINVAL; |
303 | |
304 | miim->clk = devm_clk_get_optional(dev, NULL); |
305 | if (IS_ERR(ptr: miim->clk)) |
306 | return PTR_ERR(ptr: miim->clk); |
307 | |
308 | of_property_read_u32(np, propname: "clock-frequency" , out_value: &miim->bus_freq); |
309 | |
310 | if (miim->bus_freq && !miim->clk) { |
311 | dev_err(dev, "cannot use clock-frequency without a clock\n" ); |
312 | return -EINVAL; |
313 | } |
314 | |
315 | ret = clk_prepare_enable(clk: miim->clk); |
316 | if (ret) |
317 | return ret; |
318 | |
319 | ret = mscc_miim_clk_set(bus); |
320 | if (ret) |
321 | goto out_disable_clk; |
322 | |
323 | ret = of_mdiobus_register(mdio: bus, np); |
324 | if (ret < 0) { |
325 | dev_err(dev, "Cannot register MDIO bus (%d)\n" , ret); |
326 | goto out_disable_clk; |
327 | } |
328 | |
329 | platform_set_drvdata(pdev, data: bus); |
330 | |
331 | return 0; |
332 | |
333 | out_disable_clk: |
334 | clk_disable_unprepare(clk: miim->clk); |
335 | return ret; |
336 | } |
337 | |
338 | static void mscc_miim_remove(struct platform_device *pdev) |
339 | { |
340 | struct mii_bus *bus = platform_get_drvdata(pdev); |
341 | struct mscc_miim_dev *miim = bus->priv; |
342 | |
343 | clk_disable_unprepare(clk: miim->clk); |
344 | mdiobus_unregister(bus); |
345 | } |
346 | |
347 | static const struct mscc_miim_info mscc_ocelot_miim_info = { |
348 | .phy_reset_offset = MSCC_PHY_REG_PHY_CFG, |
349 | .phy_reset_bits = PHY_CFG_PHY_ENA | PHY_CFG_PHY_COMMON_RESET | |
350 | PHY_CFG_PHY_RESET, |
351 | }; |
352 | |
353 | static const struct mscc_miim_info microchip_lan966x_miim_info = { |
354 | .phy_reset_offset = LAN966X_CUPHY_COMMON_CFG, |
355 | .phy_reset_bits = CUPHY_COMMON_CFG_RESET_N, |
356 | }; |
357 | |
358 | static const struct of_device_id mscc_miim_match[] = { |
359 | { |
360 | .compatible = "mscc,ocelot-miim" , |
361 | .data = &mscc_ocelot_miim_info |
362 | }, { |
363 | .compatible = "microchip,lan966x-miim" , |
364 | .data = µchip_lan966x_miim_info |
365 | }, |
366 | { } |
367 | }; |
368 | MODULE_DEVICE_TABLE(of, mscc_miim_match); |
369 | |
370 | static struct platform_driver mscc_miim_driver = { |
371 | .probe = mscc_miim_probe, |
372 | .remove_new = mscc_miim_remove, |
373 | .driver = { |
374 | .name = "mscc-miim" , |
375 | .of_match_table = mscc_miim_match, |
376 | }, |
377 | }; |
378 | |
379 | module_platform_driver(mscc_miim_driver); |
380 | |
381 | MODULE_DESCRIPTION("Microsemi MIIM driver" ); |
382 | MODULE_AUTHOR("Alexandre Belloni <alexandre.belloni@bootlin.com>" ); |
383 | MODULE_LICENSE("Dual MIT/GPL" ); |
384 | |