1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* Realtek MDIO interface driver |
3 | * |
4 | * ASICs we intend to support with this driver: |
5 | * |
6 | * RTL8366 - The original version, apparently |
7 | * RTL8369 - Similar enough to have the same datsheet as RTL8366 |
8 | * RTL8366RB - Probably reads out "RTL8366 revision B", has a quite |
9 | * different register layout from the other two |
10 | * RTL8366S - Is this "RTL8366 super"? |
11 | * RTL8367 - Has an OpenWRT driver as well |
12 | * RTL8368S - Seems to be an alternative name for RTL8366RB |
13 | * RTL8370 - Also uses SMI |
14 | * |
15 | * Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org> |
16 | * Copyright (C) 2010 Antti Seppälä <a.seppala@gmail.com> |
17 | * Copyright (C) 2010 Roman Yeryomin <roman@advem.lv> |
18 | * Copyright (C) 2011 Colin Leitner <colin.leitner@googlemail.com> |
19 | * Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org> |
20 | */ |
21 | |
22 | #include <linux/module.h> |
23 | #include <linux/of.h> |
24 | #include <linux/overflow.h> |
25 | #include <linux/regmap.h> |
26 | |
27 | #include "realtek.h" |
28 | #include "realtek-mdio.h" |
29 | #include "rtl83xx.h" |
30 | |
31 | /* Read/write via mdiobus */ |
32 | #define REALTEK_MDIO_CTRL0_REG 31 |
33 | #define REALTEK_MDIO_START_REG 29 |
34 | #define REALTEK_MDIO_CTRL1_REG 21 |
35 | #define REALTEK_MDIO_ADDRESS_REG 23 |
36 | #define REALTEK_MDIO_DATA_WRITE_REG 24 |
37 | #define REALTEK_MDIO_DATA_READ_REG 25 |
38 | |
39 | #define REALTEK_MDIO_START_OP 0xFFFF |
40 | #define REALTEK_MDIO_ADDR_OP 0x000E |
41 | #define REALTEK_MDIO_READ_OP 0x0001 |
42 | #define REALTEK_MDIO_WRITE_OP 0x0003 |
43 | |
44 | static int realtek_mdio_write(void *ctx, u32 reg, u32 val) |
45 | { |
46 | struct realtek_priv *priv = ctx; |
47 | struct mii_bus *bus = priv->bus; |
48 | int ret; |
49 | |
50 | mutex_lock(&bus->mdio_lock); |
51 | |
52 | ret = bus->write(bus, priv->mdio_addr, REALTEK_MDIO_CTRL0_REG, REALTEK_MDIO_ADDR_OP); |
53 | if (ret) |
54 | goto out_unlock; |
55 | |
56 | ret = bus->write(bus, priv->mdio_addr, REALTEK_MDIO_ADDRESS_REG, reg); |
57 | if (ret) |
58 | goto out_unlock; |
59 | |
60 | ret = bus->write(bus, priv->mdio_addr, REALTEK_MDIO_DATA_WRITE_REG, val); |
61 | if (ret) |
62 | goto out_unlock; |
63 | |
64 | ret = bus->write(bus, priv->mdio_addr, REALTEK_MDIO_CTRL1_REG, REALTEK_MDIO_WRITE_OP); |
65 | |
66 | out_unlock: |
67 | mutex_unlock(lock: &bus->mdio_lock); |
68 | |
69 | return ret; |
70 | } |
71 | |
72 | static int realtek_mdio_read(void *ctx, u32 reg, u32 *val) |
73 | { |
74 | struct realtek_priv *priv = ctx; |
75 | struct mii_bus *bus = priv->bus; |
76 | int ret; |
77 | |
78 | mutex_lock(&bus->mdio_lock); |
79 | |
80 | ret = bus->write(bus, priv->mdio_addr, REALTEK_MDIO_CTRL0_REG, REALTEK_MDIO_ADDR_OP); |
81 | if (ret) |
82 | goto out_unlock; |
83 | |
84 | ret = bus->write(bus, priv->mdio_addr, REALTEK_MDIO_ADDRESS_REG, reg); |
85 | if (ret) |
86 | goto out_unlock; |
87 | |
88 | ret = bus->write(bus, priv->mdio_addr, REALTEK_MDIO_CTRL1_REG, REALTEK_MDIO_READ_OP); |
89 | if (ret) |
90 | goto out_unlock; |
91 | |
92 | ret = bus->read(bus, priv->mdio_addr, REALTEK_MDIO_DATA_READ_REG); |
93 | if (ret >= 0) { |
94 | *val = ret; |
95 | ret = 0; |
96 | } |
97 | |
98 | out_unlock: |
99 | mutex_unlock(lock: &bus->mdio_lock); |
100 | |
101 | return ret; |
102 | } |
103 | |
104 | static const struct realtek_interface_info realtek_mdio_info = { |
105 | .reg_read = realtek_mdio_read, |
106 | .reg_write = realtek_mdio_write, |
107 | }; |
108 | |
109 | /** |
110 | * realtek_mdio_probe() - Probe a platform device for an MDIO-connected switch |
111 | * @mdiodev: mdio_device to probe on. |
112 | * |
113 | * This function should be used as the .probe in an mdio_driver. After |
114 | * calling the common probe function for both interfaces, it initializes the |
115 | * values specific for MDIO-connected devices. Finally, it calls a common |
116 | * function to register the DSA switch. |
117 | * |
118 | * Context: Can sleep. Takes and releases priv->map_lock. |
119 | * Return: Returns 0 on success, a negative error on failure. |
120 | */ |
121 | int realtek_mdio_probe(struct mdio_device *mdiodev) |
122 | { |
123 | struct device *dev = &mdiodev->dev; |
124 | struct realtek_priv *priv; |
125 | int ret; |
126 | |
127 | priv = rtl83xx_probe(dev, interface_info: &realtek_mdio_info); |
128 | if (IS_ERR(ptr: priv)) |
129 | return PTR_ERR(ptr: priv); |
130 | |
131 | priv->bus = mdiodev->bus; |
132 | priv->mdio_addr = mdiodev->addr; |
133 | priv->write_reg_noack = realtek_mdio_write; |
134 | |
135 | ret = rtl83xx_register_switch(priv); |
136 | if (ret) { |
137 | rtl83xx_remove(priv); |
138 | return ret; |
139 | } |
140 | |
141 | return 0; |
142 | } |
143 | EXPORT_SYMBOL_NS_GPL(realtek_mdio_probe, REALTEK_DSA); |
144 | |
145 | /** |
146 | * realtek_mdio_remove() - Remove the driver of an MDIO-connected switch |
147 | * @mdiodev: mdio_device to be removed. |
148 | * |
149 | * This function should be used as the .remove_new in an mdio_driver. First |
150 | * it unregisters the DSA switch and then it calls the common remove function. |
151 | * |
152 | * Context: Can sleep. |
153 | * Return: Nothing. |
154 | */ |
155 | void realtek_mdio_remove(struct mdio_device *mdiodev) |
156 | { |
157 | struct realtek_priv *priv = dev_get_drvdata(dev: &mdiodev->dev); |
158 | |
159 | if (!priv) |
160 | return; |
161 | |
162 | rtl83xx_unregister_switch(priv); |
163 | |
164 | rtl83xx_remove(priv); |
165 | } |
166 | EXPORT_SYMBOL_NS_GPL(realtek_mdio_remove, REALTEK_DSA); |
167 | |
168 | /** |
169 | * realtek_mdio_shutdown() - Shutdown the driver of a MDIO-connected switch |
170 | * @mdiodev: mdio_device shutting down. |
171 | * |
172 | * This function should be used as the .shutdown in a platform_driver. It calls |
173 | * the common shutdown function. |
174 | * |
175 | * Context: Can sleep. |
176 | * Return: Nothing. |
177 | */ |
178 | void realtek_mdio_shutdown(struct mdio_device *mdiodev) |
179 | { |
180 | struct realtek_priv *priv = dev_get_drvdata(dev: &mdiodev->dev); |
181 | |
182 | if (!priv) |
183 | return; |
184 | |
185 | rtl83xx_shutdown(priv); |
186 | } |
187 | EXPORT_SYMBOL_NS_GPL(realtek_mdio_shutdown, REALTEK_DSA); |
188 | |