1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright 2016 Broadcom |
4 | */ |
5 | #include <linux/align.h> |
6 | #include <linux/clk.h> |
7 | #include <linux/delay.h> |
8 | #include <linux/device.h> |
9 | #include <linux/iopoll.h> |
10 | #include <linux/mdio-mux.h> |
11 | #include <linux/module.h> |
12 | #include <linux/of_mdio.h> |
13 | #include <linux/phy.h> |
14 | #include <linux/platform_device.h> |
15 | #include <linux/sizes.h> |
16 | |
17 | #define MDIO_RATE_ADJ_EXT_OFFSET 0x000 |
18 | #define MDIO_RATE_ADJ_INT_OFFSET 0x004 |
19 | #define MDIO_RATE_ADJ_DIVIDENT_SHIFT 16 |
20 | |
21 | #define MDIO_SCAN_CTRL_OFFSET 0x008 |
22 | #define MDIO_SCAN_CTRL_OVRIDE_EXT_MSTR 28 |
23 | |
24 | #define MDIO_PARAM_OFFSET 0x23c |
25 | #define MDIO_PARAM_MIIM_CYCLE 29 |
26 | #define MDIO_PARAM_INTERNAL_SEL 25 |
27 | #define MDIO_PARAM_BUS_ID 22 |
28 | #define MDIO_PARAM_C45_SEL 21 |
29 | #define MDIO_PARAM_PHY_ID 16 |
30 | #define MDIO_PARAM_PHY_DATA 0 |
31 | |
32 | #define MDIO_READ_OFFSET 0x240 |
33 | #define MDIO_READ_DATA_MASK 0xffff |
34 | #define MDIO_ADDR_OFFSET 0x244 |
35 | |
36 | #define MDIO_CTRL_OFFSET 0x248 |
37 | #define MDIO_CTRL_WRITE_OP 0x1 |
38 | #define MDIO_CTRL_READ_OP 0x2 |
39 | |
40 | #define MDIO_STAT_OFFSET 0x24c |
41 | #define MDIO_STAT_DONE 1 |
42 | |
43 | #define BUS_MAX_ADDR 32 |
44 | #define EXT_BUS_START_ADDR 16 |
45 | |
46 | #define MDIO_REG_ADDR_SPACE_SIZE 0x250 |
47 | |
48 | #define MDIO_OPERATING_FREQUENCY 11000000 |
49 | #define MDIO_RATE_ADJ_DIVIDENT 1 |
50 | |
51 | struct iproc_mdiomux_desc { |
52 | void *mux_handle; |
53 | void __iomem *base; |
54 | struct device *dev; |
55 | struct mii_bus *mii_bus; |
56 | struct clk *core_clk; |
57 | }; |
58 | |
59 | static void mdio_mux_iproc_config(struct iproc_mdiomux_desc *md) |
60 | { |
61 | u32 divisor; |
62 | u32 val; |
63 | |
64 | /* Disable external mdio master access */ |
65 | val = readl(addr: md->base + MDIO_SCAN_CTRL_OFFSET); |
66 | val |= BIT(MDIO_SCAN_CTRL_OVRIDE_EXT_MSTR); |
67 | writel(val, addr: md->base + MDIO_SCAN_CTRL_OFFSET); |
68 | |
69 | if (md->core_clk) { |
70 | /* use rate adjust regs to derive the mdio's operating |
71 | * frequency from the specified core clock |
72 | */ |
73 | divisor = clk_get_rate(clk: md->core_clk) / MDIO_OPERATING_FREQUENCY; |
74 | divisor = divisor / (MDIO_RATE_ADJ_DIVIDENT + 1); |
75 | val = divisor; |
76 | val |= MDIO_RATE_ADJ_DIVIDENT << MDIO_RATE_ADJ_DIVIDENT_SHIFT; |
77 | writel(val, addr: md->base + MDIO_RATE_ADJ_EXT_OFFSET); |
78 | writel(val, addr: md->base + MDIO_RATE_ADJ_INT_OFFSET); |
79 | } |
80 | } |
81 | |
82 | static int iproc_mdio_wait_for_idle(void __iomem *base, bool result) |
83 | { |
84 | u32 val; |
85 | |
86 | return readl_poll_timeout(base + MDIO_STAT_OFFSET, val, |
87 | (val & MDIO_STAT_DONE) == result, |
88 | 2000, 1000000); |
89 | } |
90 | |
91 | /* start_miim_ops- Program and start MDIO transaction over mdio bus. |
92 | * @base: Base address |
93 | * @phyid: phyid of the selected bus. |
94 | * @reg: register offset to be read/written. |
95 | * @val :0 if read op else value to be written in @reg; |
96 | * @op: Operation that need to be carried out. |
97 | * MDIO_CTRL_READ_OP: Read transaction. |
98 | * MDIO_CTRL_WRITE_OP: Write transaction. |
99 | * |
100 | * Return value: Successful Read operation returns read reg values and write |
101 | * operation returns 0. Failure operation returns negative error code. |
102 | */ |
103 | static int start_miim_ops(void __iomem *base, bool c45, |
104 | u16 phyid, u32 reg, u16 val, u32 op) |
105 | { |
106 | u32 param; |
107 | int ret; |
108 | |
109 | writel(val: 0, addr: base + MDIO_CTRL_OFFSET); |
110 | ret = iproc_mdio_wait_for_idle(base, result: 0); |
111 | if (ret) |
112 | goto err; |
113 | |
114 | param = readl(addr: base + MDIO_PARAM_OFFSET); |
115 | param |= phyid << MDIO_PARAM_PHY_ID; |
116 | param |= val << MDIO_PARAM_PHY_DATA; |
117 | if (c45) |
118 | param |= BIT(MDIO_PARAM_C45_SEL); |
119 | |
120 | writel(val: param, addr: base + MDIO_PARAM_OFFSET); |
121 | |
122 | writel(val: reg, addr: base + MDIO_ADDR_OFFSET); |
123 | |
124 | writel(val: op, addr: base + MDIO_CTRL_OFFSET); |
125 | |
126 | ret = iproc_mdio_wait_for_idle(base, result: 1); |
127 | if (ret) |
128 | goto err; |
129 | |
130 | if (op == MDIO_CTRL_READ_OP) |
131 | ret = readl(addr: base + MDIO_READ_OFFSET) & MDIO_READ_DATA_MASK; |
132 | err: |
133 | return ret; |
134 | } |
135 | |
136 | static int iproc_mdiomux_read_c22(struct mii_bus *bus, int phyid, int reg) |
137 | { |
138 | struct iproc_mdiomux_desc *md = bus->priv; |
139 | int ret; |
140 | |
141 | ret = start_miim_ops(base: md->base, c45: false, phyid, reg, val: 0, MDIO_CTRL_READ_OP); |
142 | if (ret < 0) |
143 | dev_err(&bus->dev, "mdiomux c22 read operation failed!!!" ); |
144 | |
145 | return ret; |
146 | } |
147 | |
148 | static int iproc_mdiomux_read_c45(struct mii_bus *bus, int phyid, int devad, |
149 | int reg) |
150 | { |
151 | struct iproc_mdiomux_desc *md = bus->priv; |
152 | int ret; |
153 | |
154 | ret = start_miim_ops(base: md->base, c45: true, phyid, reg: reg | devad << 16, val: 0, |
155 | MDIO_CTRL_READ_OP); |
156 | if (ret < 0) |
157 | dev_err(&bus->dev, "mdiomux read c45 operation failed!!!" ); |
158 | |
159 | return ret; |
160 | } |
161 | |
162 | static int iproc_mdiomux_write_c22(struct mii_bus *bus, |
163 | int phyid, int reg, u16 val) |
164 | { |
165 | struct iproc_mdiomux_desc *md = bus->priv; |
166 | int ret; |
167 | |
168 | /* Write val at reg offset */ |
169 | ret = start_miim_ops(base: md->base, c45: false, phyid, reg, val, |
170 | MDIO_CTRL_WRITE_OP); |
171 | if (ret < 0) |
172 | dev_err(&bus->dev, "mdiomux write c22 operation failed!!!" ); |
173 | |
174 | return ret; |
175 | } |
176 | |
177 | static int iproc_mdiomux_write_c45(struct mii_bus *bus, |
178 | int phyid, int devad, int reg, u16 val) |
179 | { |
180 | struct iproc_mdiomux_desc *md = bus->priv; |
181 | int ret; |
182 | |
183 | /* Write val at reg offset */ |
184 | ret = start_miim_ops(base: md->base, c45: true, phyid, reg: reg | devad << 16, val, |
185 | MDIO_CTRL_WRITE_OP); |
186 | if (ret < 0) |
187 | dev_err(&bus->dev, "mdiomux write c45 operation failed!!!" ); |
188 | |
189 | return ret; |
190 | } |
191 | |
192 | static int mdio_mux_iproc_switch_fn(int current_child, int desired_child, |
193 | void *data) |
194 | { |
195 | struct iproc_mdiomux_desc *md = data; |
196 | u32 param, bus_id; |
197 | bool bus_dir; |
198 | |
199 | /* select bus and its properties */ |
200 | bus_dir = (desired_child < EXT_BUS_START_ADDR); |
201 | bus_id = bus_dir ? desired_child : (desired_child - EXT_BUS_START_ADDR); |
202 | |
203 | param = (bus_dir ? 1 : 0) << MDIO_PARAM_INTERNAL_SEL; |
204 | param |= (bus_id << MDIO_PARAM_BUS_ID); |
205 | |
206 | writel(val: param, addr: md->base + MDIO_PARAM_OFFSET); |
207 | return 0; |
208 | } |
209 | |
210 | static int mdio_mux_iproc_probe(struct platform_device *pdev) |
211 | { |
212 | struct iproc_mdiomux_desc *md; |
213 | struct mii_bus *bus; |
214 | struct resource *res; |
215 | int rc; |
216 | |
217 | md = devm_kzalloc(dev: &pdev->dev, size: sizeof(*md), GFP_KERNEL); |
218 | if (!md) |
219 | return -ENOMEM; |
220 | md->dev = &pdev->dev; |
221 | |
222 | md->base = devm_platform_get_and_ioremap_resource(pdev, index: 0, res: &res); |
223 | if (IS_ERR(ptr: md->base)) |
224 | return PTR_ERR(ptr: md->base); |
225 | if (!IS_ALIGNED(res->start, SZ_4K)) { |
226 | /* For backward compatibility in case the |
227 | * base address is specified with an offset. |
228 | */ |
229 | dev_info(&pdev->dev, "fix base address in dt-blob\n" ); |
230 | res->start = ALIGN_DOWN(res->start, SZ_4K); |
231 | res->end = res->start + MDIO_REG_ADDR_SPACE_SIZE - 1; |
232 | } |
233 | |
234 | md->mii_bus = devm_mdiobus_alloc(dev: &pdev->dev); |
235 | if (!md->mii_bus) { |
236 | dev_err(&pdev->dev, "mdiomux bus alloc failed\n" ); |
237 | return -ENOMEM; |
238 | } |
239 | |
240 | md->core_clk = devm_clk_get(dev: &pdev->dev, NULL); |
241 | if (md->core_clk == ERR_PTR(error: -ENOENT) || |
242 | md->core_clk == ERR_PTR(error: -EINVAL)) |
243 | md->core_clk = NULL; |
244 | else if (IS_ERR(ptr: md->core_clk)) |
245 | return PTR_ERR(ptr: md->core_clk); |
246 | |
247 | rc = clk_prepare_enable(clk: md->core_clk); |
248 | if (rc) { |
249 | dev_err(&pdev->dev, "failed to enable core clk\n" ); |
250 | return rc; |
251 | } |
252 | |
253 | bus = md->mii_bus; |
254 | bus->priv = md; |
255 | bus->name = "iProc MDIO mux bus" ; |
256 | snprintf(buf: bus->id, MII_BUS_ID_SIZE, fmt: "%s-%d" , pdev->name, pdev->id); |
257 | bus->parent = &pdev->dev; |
258 | bus->read = iproc_mdiomux_read_c22; |
259 | bus->write = iproc_mdiomux_write_c22; |
260 | bus->read_c45 = iproc_mdiomux_read_c45; |
261 | bus->write_c45 = iproc_mdiomux_write_c45; |
262 | |
263 | bus->phy_mask = ~0; |
264 | bus->dev.of_node = pdev->dev.of_node; |
265 | rc = mdiobus_register(bus); |
266 | if (rc) { |
267 | dev_err(&pdev->dev, "mdiomux registration failed\n" ); |
268 | goto out_clk; |
269 | } |
270 | |
271 | platform_set_drvdata(pdev, data: md); |
272 | |
273 | rc = mdio_mux_init(dev: md->dev, mux_node: md->dev->of_node, switch_fn: mdio_mux_iproc_switch_fn, |
274 | mux_handle: &md->mux_handle, data: md, mux_bus: md->mii_bus); |
275 | if (rc) { |
276 | dev_info(md->dev, "mdiomux initialization failed\n" ); |
277 | goto out_register; |
278 | } |
279 | |
280 | mdio_mux_iproc_config(md); |
281 | |
282 | dev_info(md->dev, "iProc mdiomux registered\n" ); |
283 | return 0; |
284 | |
285 | out_register: |
286 | mdiobus_unregister(bus); |
287 | out_clk: |
288 | clk_disable_unprepare(clk: md->core_clk); |
289 | return rc; |
290 | } |
291 | |
292 | static void mdio_mux_iproc_remove(struct platform_device *pdev) |
293 | { |
294 | struct iproc_mdiomux_desc *md = platform_get_drvdata(pdev); |
295 | |
296 | mdio_mux_uninit(mux_handle: md->mux_handle); |
297 | mdiobus_unregister(bus: md->mii_bus); |
298 | clk_disable_unprepare(clk: md->core_clk); |
299 | } |
300 | |
301 | #ifdef CONFIG_PM_SLEEP |
302 | static int mdio_mux_iproc_suspend(struct device *dev) |
303 | { |
304 | struct iproc_mdiomux_desc *md = dev_get_drvdata(dev); |
305 | |
306 | clk_disable_unprepare(clk: md->core_clk); |
307 | |
308 | return 0; |
309 | } |
310 | |
311 | static int mdio_mux_iproc_resume(struct device *dev) |
312 | { |
313 | struct iproc_mdiomux_desc *md = dev_get_drvdata(dev); |
314 | int rc; |
315 | |
316 | rc = clk_prepare_enable(clk: md->core_clk); |
317 | if (rc) { |
318 | dev_err(md->dev, "failed to enable core clk\n" ); |
319 | return rc; |
320 | } |
321 | mdio_mux_iproc_config(md); |
322 | |
323 | return 0; |
324 | } |
325 | #endif |
326 | |
327 | static SIMPLE_DEV_PM_OPS(mdio_mux_iproc_pm_ops, |
328 | mdio_mux_iproc_suspend, mdio_mux_iproc_resume); |
329 | |
330 | static const struct of_device_id mdio_mux_iproc_match[] = { |
331 | { |
332 | .compatible = "brcm,mdio-mux-iproc" , |
333 | }, |
334 | {}, |
335 | }; |
336 | MODULE_DEVICE_TABLE(of, mdio_mux_iproc_match); |
337 | |
338 | static struct platform_driver mdiomux_iproc_driver = { |
339 | .driver = { |
340 | .name = "mdio-mux-iproc" , |
341 | .of_match_table = mdio_mux_iproc_match, |
342 | .pm = &mdio_mux_iproc_pm_ops, |
343 | }, |
344 | .probe = mdio_mux_iproc_probe, |
345 | .remove_new = mdio_mux_iproc_remove, |
346 | }; |
347 | |
348 | module_platform_driver(mdiomux_iproc_driver); |
349 | |
350 | MODULE_DESCRIPTION("iProc MDIO Mux Bus Driver" ); |
351 | MODULE_AUTHOR("Pramod Kumar <pramod.kumar@broadcom.com>" ); |
352 | MODULE_LICENSE("GPL v2" ); |
353 | |