Commit | Line | Data |
---|---|---|
cb675afc DG |
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(mdio_lock, MDIO_MUTEX_NESTED); | |
70 | } | |
71 | ||
72 | static void | |
73 | mt7530_mdio_regmap_unlock(void *mdio_lock) | |
74 | { | |
75 | mutex_unlock(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 | |
91daa4f6 | 84 | mt7531_create_sgmii(struct mt7530_priv *priv, bool dual_sgmii) |
cb675afc | 85 | { |
91daa4f6 | 86 | struct regmap_config *mt7531_pcs_config[2] = {}; |
cb675afc DG |
87 | struct phylink_pcs *pcs; |
88 | struct regmap *regmap; | |
89 | int i, ret = 0; | |
90 | ||
91daa4f6 DG |
91 | /* MT7531AE has two SGMII units for port 5 and port 6 |
92 | * MT7531BE has only one SGMII unit for port 6 | |
93 | */ | |
94 | for (i = dual_sgmii ? 0 : 1; i < 2; i++) { | |
cb675afc DG |
95 | mt7531_pcs_config[i] = devm_kzalloc(priv->dev, |
96 | sizeof(struct regmap_config), | |
97 | GFP_KERNEL); | |
98 | if (!mt7531_pcs_config[i]) { | |
99 | ret = -ENOMEM; | |
100 | break; | |
101 | } | |
102 | ||
103 | mt7531_pcs_config[i]->name = i ? "port6" : "port5"; | |
104 | mt7531_pcs_config[i]->reg_bits = 16; | |
105 | mt7531_pcs_config[i]->val_bits = 32; | |
106 | mt7531_pcs_config[i]->reg_stride = 4; | |
107 | mt7531_pcs_config[i]->reg_base = MT7531_SGMII_REG_BASE(5 + i); | |
108 | mt7531_pcs_config[i]->max_register = 0x17c; | |
109 | mt7531_pcs_config[i]->lock = mt7530_mdio_regmap_lock; | |
110 | mt7531_pcs_config[i]->unlock = mt7530_mdio_regmap_unlock; | |
111 | mt7531_pcs_config[i]->lock_arg = &priv->bus->mdio_lock; | |
112 | ||
113 | regmap = devm_regmap_init(priv->dev, | |
114 | &mt7530_regmap_bus, priv->bus, | |
115 | mt7531_pcs_config[i]); | |
116 | if (IS_ERR(regmap)) { | |
117 | ret = PTR_ERR(regmap); | |
118 | break; | |
119 | } | |
120 | pcs = mtk_pcs_lynxi_create(priv->dev, regmap, | |
121 | MT7531_PHYA_CTRL_SIGNAL3, 0); | |
122 | if (!pcs) { | |
123 | ret = -ENXIO; | |
124 | break; | |
125 | } | |
126 | priv->ports[5 + i].sgmii_pcs = pcs; | |
127 | } | |
128 | ||
129 | if (ret && i) | |
130 | mtk_pcs_lynxi_destroy(priv->ports[5].sgmii_pcs); | |
131 | ||
132 | return ret; | |
133 | } | |
134 | ||
135 | static const struct of_device_id mt7530_of_match[] = { | |
136 | { .compatible = "mediatek,mt7621", .data = &mt753x_table[ID_MT7621], }, | |
137 | { .compatible = "mediatek,mt7530", .data = &mt753x_table[ID_MT7530], }, | |
138 | { .compatible = "mediatek,mt7531", .data = &mt753x_table[ID_MT7531], }, | |
139 | { /* sentinel */ }, | |
140 | }; | |
141 | MODULE_DEVICE_TABLE(of, mt7530_of_match); | |
142 | ||
143 | static int | |
144 | mt7530_probe(struct mdio_device *mdiodev) | |
145 | { | |
146 | static struct regmap_config *regmap_config; | |
147 | struct mt7530_priv *priv; | |
148 | struct device_node *dn; | |
149 | int ret; | |
150 | ||
151 | dn = mdiodev->dev.of_node; | |
152 | ||
153 | priv = devm_kzalloc(&mdiodev->dev, sizeof(*priv), GFP_KERNEL); | |
154 | if (!priv) | |
155 | return -ENOMEM; | |
156 | ||
157 | priv->bus = mdiodev->bus; | |
158 | priv->dev = &mdiodev->dev; | |
159 | ||
160 | ret = mt7530_probe_common(priv); | |
161 | if (ret) | |
162 | return ret; | |
163 | ||
164 | /* Use medatek,mcm property to distinguish hardware type that would | |
165 | * cause a little bit differences on power-on sequence. | |
166 | * Not MCM that indicates switch works as the remote standalone | |
167 | * integrated circuit so the GPIO pin would be used to complete | |
168 | * the reset, otherwise memory-mapped register accessing used | |
169 | * through syscon provides in the case of MCM. | |
170 | */ | |
171 | priv->mcm = of_property_read_bool(dn, "mediatek,mcm"); | |
172 | if (priv->mcm) { | |
173 | dev_info(&mdiodev->dev, "MT7530 adapts as multi-chip module\n"); | |
174 | ||
175 | priv->rstc = devm_reset_control_get(&mdiodev->dev, "mcm"); | |
176 | if (IS_ERR(priv->rstc)) { | |
177 | dev_err(&mdiodev->dev, "Couldn't get our reset line\n"); | |
178 | return PTR_ERR(priv->rstc); | |
179 | } | |
180 | } else { | |
181 | priv->reset = devm_gpiod_get_optional(&mdiodev->dev, "reset", | |
182 | GPIOD_OUT_LOW); | |
183 | if (IS_ERR(priv->reset)) { | |
184 | dev_err(&mdiodev->dev, "Couldn't get our reset line\n"); | |
185 | return PTR_ERR(priv->reset); | |
186 | } | |
187 | } | |
188 | ||
189 | if (priv->id == ID_MT7530) { | |
190 | priv->core_pwr = devm_regulator_get(&mdiodev->dev, "core"); | |
191 | if (IS_ERR(priv->core_pwr)) | |
192 | return PTR_ERR(priv->core_pwr); | |
193 | ||
194 | priv->io_pwr = devm_regulator_get(&mdiodev->dev, "io"); | |
195 | if (IS_ERR(priv->io_pwr)) | |
196 | return PTR_ERR(priv->io_pwr); | |
197 | } | |
198 | ||
199 | regmap_config = devm_kzalloc(&mdiodev->dev, sizeof(*regmap_config), | |
200 | GFP_KERNEL); | |
201 | if (!regmap_config) | |
202 | return -ENOMEM; | |
203 | ||
204 | regmap_config->reg_bits = 16; | |
205 | regmap_config->val_bits = 32; | |
206 | regmap_config->reg_stride = 4; | |
207 | regmap_config->max_register = MT7530_CREV; | |
208 | regmap_config->disable_locking = true; | |
209 | priv->regmap = devm_regmap_init(priv->dev, &mt7530_regmap_bus, | |
210 | priv->bus, regmap_config); | |
211 | if (IS_ERR(priv->regmap)) | |
212 | return PTR_ERR(priv->regmap); | |
213 | ||
91daa4f6 DG |
214 | if (priv->id == ID_MT7531) |
215 | priv->create_sgmii = mt7531_create_sgmii; | |
cb675afc DG |
216 | |
217 | return dsa_register_switch(priv->ds); | |
218 | } | |
219 | ||
220 | static void | |
221 | mt7530_remove(struct mdio_device *mdiodev) | |
222 | { | |
223 | struct mt7530_priv *priv = dev_get_drvdata(&mdiodev->dev); | |
224 | int ret = 0, i; | |
225 | ||
226 | if (!priv) | |
227 | return; | |
228 | ||
229 | ret = regulator_disable(priv->core_pwr); | |
230 | if (ret < 0) | |
231 | dev_err(priv->dev, | |
232 | "Failed to disable core power: %d\n", ret); | |
233 | ||
234 | ret = regulator_disable(priv->io_pwr); | |
235 | if (ret < 0) | |
236 | dev_err(priv->dev, "Failed to disable io pwr: %d\n", | |
237 | ret); | |
238 | ||
239 | mt7530_remove_common(priv); | |
240 | ||
241 | for (i = 0; i < 2; ++i) | |
242 | mtk_pcs_lynxi_destroy(priv->ports[5 + i].sgmii_pcs); | |
243 | } | |
244 | ||
245 | static void mt7530_shutdown(struct mdio_device *mdiodev) | |
246 | { | |
247 | struct mt7530_priv *priv = dev_get_drvdata(&mdiodev->dev); | |
248 | ||
249 | if (!priv) | |
250 | return; | |
251 | ||
252 | dsa_switch_shutdown(priv->ds); | |
253 | ||
254 | dev_set_drvdata(&mdiodev->dev, NULL); | |
255 | } | |
256 | ||
257 | static struct mdio_driver mt7530_mdio_driver = { | |
258 | .probe = mt7530_probe, | |
259 | .remove = mt7530_remove, | |
260 | .shutdown = mt7530_shutdown, | |
261 | .mdiodrv.driver = { | |
262 | .name = "mt7530-mdio", | |
263 | .of_match_table = mt7530_of_match, | |
264 | }, | |
265 | }; | |
266 | ||
267 | mdio_module_driver(mt7530_mdio_driver); | |
268 | ||
269 | MODULE_AUTHOR("Sean Wang <sean.wang@mediatek.com>"); | |
270 | MODULE_DESCRIPTION("Driver for Mediatek MT7530 Switch (MDIO)"); | |
271 | MODULE_LICENSE("GPL"); |