1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | |
3 | #include <linux/gpio/consumer.h> |
4 | #include <linux/mdio.h> |
5 | #include <linux/module.h> |
6 | #include <linux/pcs/pcs-mtk-lynxi.h> |
7 | #include <linux/of_irq.h> |
8 | #include <linux/of_mdio.h> |
9 | #include <linux/of_net.h> |
10 | #include <linux/of_platform.h> |
11 | #include <linux/regmap.h> |
12 | #include <linux/reset.h> |
13 | #include <linux/regulator/consumer.h> |
14 | #include <net/dsa.h> |
15 | |
16 | #include "mt7530.h" |
17 | |
18 | static int |
19 | mt7530_regmap_write(void *context, unsigned int reg, unsigned int val) |
20 | { |
21 | struct mii_bus *bus = context; |
22 | u16 page, r, lo, hi; |
23 | int ret; |
24 | |
25 | page = (reg >> 6) & 0x3ff; |
26 | r = (reg >> 2) & 0xf; |
27 | lo = val & 0xffff; |
28 | hi = val >> 16; |
29 | |
30 | /* MT7530 uses 31 as the pseudo port */ |
31 | ret = bus->write(bus, 0x1f, 0x1f, page); |
32 | if (ret < 0) |
33 | return ret; |
34 | |
35 | ret = bus->write(bus, 0x1f, r, lo); |
36 | if (ret < 0) |
37 | return ret; |
38 | |
39 | ret = bus->write(bus, 0x1f, 0x10, hi); |
40 | return ret; |
41 | } |
42 | |
43 | static int |
44 | mt7530_regmap_read(void *context, unsigned int reg, unsigned int *val) |
45 | { |
46 | struct mii_bus *bus = context; |
47 | u16 page, r, lo, hi; |
48 | int ret; |
49 | |
50 | page = (reg >> 6) & 0x3ff; |
51 | r = (reg >> 2) & 0xf; |
52 | |
53 | /* MT7530 uses 31 as the pseudo port */ |
54 | ret = bus->write(bus, 0x1f, 0x1f, page); |
55 | if (ret < 0) |
56 | return ret; |
57 | |
58 | lo = bus->read(bus, 0x1f, r); |
59 | hi = bus->read(bus, 0x1f, 0x10); |
60 | |
61 | *val = (hi << 16) | (lo & 0xffff); |
62 | |
63 | return 0; |
64 | } |
65 | |
66 | static void |
67 | mt7530_mdio_regmap_lock(void *mdio_lock) |
68 | { |
69 | mutex_lock_nested(lock: mdio_lock, subclass: MDIO_MUTEX_NESTED); |
70 | } |
71 | |
72 | static void |
73 | mt7530_mdio_regmap_unlock(void *mdio_lock) |
74 | { |
75 | mutex_unlock(lock: mdio_lock); |
76 | } |
77 | |
78 | static const struct regmap_bus mt7530_regmap_bus = { |
79 | .reg_write = mt7530_regmap_write, |
80 | .reg_read = mt7530_regmap_read, |
81 | }; |
82 | |
83 | static int |
84 | mt7531_create_sgmii(struct mt7530_priv *priv) |
85 | { |
86 | struct regmap_config *mt7531_pcs_config[2] = {}; |
87 | struct phylink_pcs *pcs; |
88 | struct regmap *regmap; |
89 | int i, ret = 0; |
90 | |
91 | for (i = priv->p5_sgmii ? 0 : 1; i < 2; i++) { |
92 | mt7531_pcs_config[i] = devm_kzalloc(dev: priv->dev, |
93 | size: sizeof(struct regmap_config), |
94 | GFP_KERNEL); |
95 | if (!mt7531_pcs_config[i]) { |
96 | ret = -ENOMEM; |
97 | break; |
98 | } |
99 | |
100 | mt7531_pcs_config[i]->name = i ? "port6" : "port5" ; |
101 | mt7531_pcs_config[i]->reg_bits = 16; |
102 | mt7531_pcs_config[i]->val_bits = 32; |
103 | mt7531_pcs_config[i]->reg_stride = 4; |
104 | mt7531_pcs_config[i]->reg_base = MT7531_SGMII_REG_BASE(5 + i); |
105 | mt7531_pcs_config[i]->max_register = 0x17c; |
106 | mt7531_pcs_config[i]->lock = mt7530_mdio_regmap_lock; |
107 | mt7531_pcs_config[i]->unlock = mt7530_mdio_regmap_unlock; |
108 | mt7531_pcs_config[i]->lock_arg = &priv->bus->mdio_lock; |
109 | |
110 | regmap = devm_regmap_init(priv->dev, |
111 | &mt7530_regmap_bus, priv->bus, |
112 | mt7531_pcs_config[i]); |
113 | if (IS_ERR(ptr: regmap)) { |
114 | ret = PTR_ERR(ptr: regmap); |
115 | break; |
116 | } |
117 | pcs = mtk_pcs_lynxi_create(dev: priv->dev, regmap, |
118 | MT7531_PHYA_CTRL_SIGNAL3, flags: 0); |
119 | if (!pcs) { |
120 | ret = -ENXIO; |
121 | break; |
122 | } |
123 | priv->ports[5 + i].sgmii_pcs = pcs; |
124 | } |
125 | |
126 | if (ret && i) |
127 | mtk_pcs_lynxi_destroy(pcs: priv->ports[5].sgmii_pcs); |
128 | |
129 | return ret; |
130 | } |
131 | |
132 | static const struct of_device_id mt7530_of_match[] = { |
133 | { .compatible = "mediatek,mt7621" , .data = &mt753x_table[ID_MT7621], }, |
134 | { .compatible = "mediatek,mt7530" , .data = &mt753x_table[ID_MT7530], }, |
135 | { .compatible = "mediatek,mt7531" , .data = &mt753x_table[ID_MT7531], }, |
136 | { /* sentinel */ }, |
137 | }; |
138 | MODULE_DEVICE_TABLE(of, mt7530_of_match); |
139 | |
140 | static int |
141 | mt7530_probe(struct mdio_device *mdiodev) |
142 | { |
143 | static struct regmap_config *regmap_config; |
144 | struct mt7530_priv *priv; |
145 | struct device_node *dn; |
146 | int ret; |
147 | |
148 | dn = mdiodev->dev.of_node; |
149 | |
150 | priv = devm_kzalloc(dev: &mdiodev->dev, size: sizeof(*priv), GFP_KERNEL); |
151 | if (!priv) |
152 | return -ENOMEM; |
153 | |
154 | priv->bus = mdiodev->bus; |
155 | priv->dev = &mdiodev->dev; |
156 | |
157 | ret = mt7530_probe_common(priv); |
158 | if (ret) |
159 | return ret; |
160 | |
161 | /* Use medatek,mcm property to distinguish hardware type that would |
162 | * cause a little bit differences on power-on sequence. |
163 | * Not MCM that indicates switch works as the remote standalone |
164 | * integrated circuit so the GPIO pin would be used to complete |
165 | * the reset, otherwise memory-mapped register accessing used |
166 | * through syscon provides in the case of MCM. |
167 | */ |
168 | priv->mcm = of_property_read_bool(np: dn, propname: "mediatek,mcm" ); |
169 | if (priv->mcm) { |
170 | dev_info(&mdiodev->dev, "MT7530 adapts as multi-chip module\n" ); |
171 | |
172 | priv->rstc = devm_reset_control_get(dev: &mdiodev->dev, id: "mcm" ); |
173 | if (IS_ERR(ptr: priv->rstc)) { |
174 | dev_err(&mdiodev->dev, "Couldn't get our reset line\n" ); |
175 | return PTR_ERR(ptr: priv->rstc); |
176 | } |
177 | } else { |
178 | priv->reset = devm_gpiod_get_optional(dev: &mdiodev->dev, con_id: "reset" , |
179 | flags: GPIOD_OUT_LOW); |
180 | if (IS_ERR(ptr: priv->reset)) { |
181 | dev_err(&mdiodev->dev, "Couldn't get our reset line\n" ); |
182 | return PTR_ERR(ptr: priv->reset); |
183 | } |
184 | } |
185 | |
186 | if (priv->id == ID_MT7530) { |
187 | priv->core_pwr = devm_regulator_get(dev: &mdiodev->dev, id: "core" ); |
188 | if (IS_ERR(ptr: priv->core_pwr)) |
189 | return PTR_ERR(ptr: priv->core_pwr); |
190 | |
191 | priv->io_pwr = devm_regulator_get(dev: &mdiodev->dev, id: "io" ); |
192 | if (IS_ERR(ptr: priv->io_pwr)) |
193 | return PTR_ERR(ptr: priv->io_pwr); |
194 | } |
195 | |
196 | regmap_config = devm_kzalloc(dev: &mdiodev->dev, size: sizeof(*regmap_config), |
197 | GFP_KERNEL); |
198 | if (!regmap_config) |
199 | return -ENOMEM; |
200 | |
201 | regmap_config->reg_bits = 16; |
202 | regmap_config->val_bits = 32; |
203 | regmap_config->reg_stride = 4; |
204 | regmap_config->max_register = MT7530_CREV; |
205 | regmap_config->disable_locking = true; |
206 | priv->regmap = devm_regmap_init(priv->dev, &mt7530_regmap_bus, |
207 | priv->bus, regmap_config); |
208 | if (IS_ERR(ptr: priv->regmap)) |
209 | return PTR_ERR(ptr: priv->regmap); |
210 | |
211 | if (priv->id == ID_MT7531) |
212 | priv->create_sgmii = mt7531_create_sgmii; |
213 | |
214 | return dsa_register_switch(ds: priv->ds); |
215 | } |
216 | |
217 | static void |
218 | mt7530_remove(struct mdio_device *mdiodev) |
219 | { |
220 | struct mt7530_priv *priv = dev_get_drvdata(dev: &mdiodev->dev); |
221 | int ret = 0, i; |
222 | |
223 | if (!priv) |
224 | return; |
225 | |
226 | ret = regulator_disable(regulator: priv->core_pwr); |
227 | if (ret < 0) |
228 | dev_err(priv->dev, |
229 | "Failed to disable core power: %d\n" , ret); |
230 | |
231 | ret = regulator_disable(regulator: priv->io_pwr); |
232 | if (ret < 0) |
233 | dev_err(priv->dev, "Failed to disable io pwr: %d\n" , |
234 | ret); |
235 | |
236 | mt7530_remove_common(priv); |
237 | |
238 | for (i = 0; i < 2; ++i) |
239 | mtk_pcs_lynxi_destroy(pcs: priv->ports[5 + i].sgmii_pcs); |
240 | } |
241 | |
242 | static void mt7530_shutdown(struct mdio_device *mdiodev) |
243 | { |
244 | struct mt7530_priv *priv = dev_get_drvdata(dev: &mdiodev->dev); |
245 | |
246 | if (!priv) |
247 | return; |
248 | |
249 | dsa_switch_shutdown(ds: priv->ds); |
250 | |
251 | dev_set_drvdata(dev: &mdiodev->dev, NULL); |
252 | } |
253 | |
254 | static struct mdio_driver mt7530_mdio_driver = { |
255 | .probe = mt7530_probe, |
256 | .remove = mt7530_remove, |
257 | .shutdown = mt7530_shutdown, |
258 | .mdiodrv.driver = { |
259 | .name = "mt7530-mdio" , |
260 | .of_match_table = mt7530_of_match, |
261 | }, |
262 | }; |
263 | |
264 | mdio_module_driver(mt7530_mdio_driver); |
265 | |
266 | MODULE_AUTHOR("Sean Wang <sean.wang@mediatek.com>" ); |
267 | MODULE_DESCRIPTION("Driver for Mediatek MT7530 Switch (MDIO)" ); |
268 | MODULE_LICENSE("GPL" ); |
269 | |