Commit | Line | Data |
---|---|---|
92b58b34 GS |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Texas Instruments CPSW Port's PHY Interface Mode selection Driver | |
4 | * | |
5 | * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/ | |
6 | * | |
7 | * Based on cpsw-phy-sel.c driver created by Mugunthan V N <mugunthanvnm@ti.com> | |
8 | */ | |
9 | ||
10 | #include <linux/platform_device.h> | |
11 | #include <linux/module.h> | |
12 | #include <linux/mfd/syscon.h> | |
13 | #include <linux/of.h> | |
14 | #include <linux/of_net.h> | |
15 | #include <linux/phy.h> | |
16 | #include <linux/phy/phy.h> | |
17 | #include <linux/regmap.h> | |
18 | ||
19 | /* AM33xx SoC specific definitions for the CONTROL port */ | |
20 | #define AM33XX_GMII_SEL_MODE_MII 0 | |
21 | #define AM33XX_GMII_SEL_MODE_RMII 1 | |
22 | #define AM33XX_GMII_SEL_MODE_RGMII 2 | |
23 | ||
24 | enum { | |
25 | PHY_GMII_SEL_PORT_MODE, | |
26 | PHY_GMII_SEL_RGMII_ID_MODE, | |
27 | PHY_GMII_SEL_RMII_IO_CLK_EN, | |
28 | PHY_GMII_SEL_LAST, | |
29 | }; | |
30 | ||
31 | struct phy_gmii_sel_phy_priv { | |
32 | struct phy_gmii_sel_priv *priv; | |
33 | u32 id; | |
34 | struct phy *if_phy; | |
35 | int rmii_clock_external; | |
36 | int phy_if_mode; | |
37 | struct regmap_field *fields[PHY_GMII_SEL_LAST]; | |
38 | }; | |
39 | ||
40 | struct phy_gmii_sel_soc_data { | |
41 | u32 num_ports; | |
42 | u32 features; | |
43 | const struct reg_field (*regfields)[PHY_GMII_SEL_LAST]; | |
44 | }; | |
45 | ||
46 | struct phy_gmii_sel_priv { | |
47 | struct device *dev; | |
48 | const struct phy_gmii_sel_soc_data *soc_data; | |
49 | struct regmap *regmap; | |
50 | struct phy_provider *phy_provider; | |
51 | struct phy_gmii_sel_phy_priv *if_phys; | |
52 | }; | |
53 | ||
54 | static int phy_gmii_sel_mode(struct phy *phy, enum phy_mode mode, int submode) | |
55 | { | |
56 | struct phy_gmii_sel_phy_priv *if_phy = phy_get_drvdata(phy); | |
57 | const struct phy_gmii_sel_soc_data *soc_data = if_phy->priv->soc_data; | |
58 | struct device *dev = if_phy->priv->dev; | |
59 | struct regmap_field *regfield; | |
60 | int ret, rgmii_id = 0; | |
61 | u32 gmii_sel_mode = 0; | |
62 | ||
63 | if (mode != PHY_MODE_ETHERNET) | |
64 | return -EINVAL; | |
65 | ||
66 | switch (submode) { | |
67 | case PHY_INTERFACE_MODE_RMII: | |
68 | gmii_sel_mode = AM33XX_GMII_SEL_MODE_RMII; | |
69 | break; | |
70 | ||
71 | case PHY_INTERFACE_MODE_RGMII: | |
316b4294 | 72 | case PHY_INTERFACE_MODE_RGMII_RXID: |
92b58b34 GS |
73 | gmii_sel_mode = AM33XX_GMII_SEL_MODE_RGMII; |
74 | break; | |
75 | ||
76 | case PHY_INTERFACE_MODE_RGMII_ID: | |
92b58b34 GS |
77 | case PHY_INTERFACE_MODE_RGMII_TXID: |
78 | gmii_sel_mode = AM33XX_GMII_SEL_MODE_RGMII; | |
79 | rgmii_id = 1; | |
80 | break; | |
81 | ||
82 | case PHY_INTERFACE_MODE_MII: | |
58aa7729 | 83 | case PHY_INTERFACE_MODE_GMII: |
eefed634 | 84 | gmii_sel_mode = AM33XX_GMII_SEL_MODE_MII; |
92b58b34 GS |
85 | break; |
86 | ||
87 | default: | |
eefed634 GS |
88 | dev_warn(dev, "port%u: unsupported mode: \"%s\"\n", |
89 | if_phy->id, phy_modes(submode)); | |
92b58b34 | 90 | return -EINVAL; |
1a3a0927 | 91 | } |
92b58b34 GS |
92 | |
93 | if_phy->phy_if_mode = submode; | |
94 | ||
95 | dev_dbg(dev, "%s id:%u mode:%u rgmii_id:%d rmii_clk_ext:%d\n", | |
eefed634 | 96 | __func__, if_phy->id, submode, rgmii_id, |
92b58b34 GS |
97 | if_phy->rmii_clock_external); |
98 | ||
99 | regfield = if_phy->fields[PHY_GMII_SEL_PORT_MODE]; | |
100 | ret = regmap_field_write(regfield, gmii_sel_mode); | |
101 | if (ret) { | |
102 | dev_err(dev, "port%u: set mode fail %d", if_phy->id, ret); | |
103 | return ret; | |
104 | } | |
105 | ||
106 | if (soc_data->features & BIT(PHY_GMII_SEL_RGMII_ID_MODE) && | |
107 | if_phy->fields[PHY_GMII_SEL_RGMII_ID_MODE]) { | |
108 | regfield = if_phy->fields[PHY_GMII_SEL_RGMII_ID_MODE]; | |
109 | ret = regmap_field_write(regfield, rgmii_id); | |
110 | if (ret) | |
111 | return ret; | |
112 | } | |
113 | ||
114 | if (soc_data->features & BIT(PHY_GMII_SEL_RMII_IO_CLK_EN) && | |
115 | if_phy->fields[PHY_GMII_SEL_RMII_IO_CLK_EN]) { | |
116 | regfield = if_phy->fields[PHY_GMII_SEL_RMII_IO_CLK_EN]; | |
117 | ret = regmap_field_write(regfield, | |
118 | if_phy->rmii_clock_external); | |
119 | } | |
120 | ||
121 | return 0; | |
122 | } | |
123 | ||
124 | static const | |
125 | struct reg_field phy_gmii_sel_fields_am33xx[][PHY_GMII_SEL_LAST] = { | |
126 | { | |
127 | [PHY_GMII_SEL_PORT_MODE] = REG_FIELD(0x650, 0, 1), | |
128 | [PHY_GMII_SEL_RGMII_ID_MODE] = REG_FIELD(0x650, 4, 4), | |
129 | [PHY_GMII_SEL_RMII_IO_CLK_EN] = REG_FIELD(0x650, 6, 6), | |
130 | }, | |
131 | { | |
132 | [PHY_GMII_SEL_PORT_MODE] = REG_FIELD(0x650, 2, 3), | |
133 | [PHY_GMII_SEL_RGMII_ID_MODE] = REG_FIELD(0x650, 5, 5), | |
134 | [PHY_GMII_SEL_RMII_IO_CLK_EN] = REG_FIELD(0x650, 7, 7), | |
135 | }, | |
136 | }; | |
137 | ||
138 | static const | |
139 | struct phy_gmii_sel_soc_data phy_gmii_sel_soc_am33xx = { | |
140 | .num_ports = 2, | |
141 | .features = BIT(PHY_GMII_SEL_RGMII_ID_MODE) | | |
142 | BIT(PHY_GMII_SEL_RMII_IO_CLK_EN), | |
143 | .regfields = phy_gmii_sel_fields_am33xx, | |
144 | }; | |
145 | ||
146 | static const | |
147 | struct reg_field phy_gmii_sel_fields_dra7[][PHY_GMII_SEL_LAST] = { | |
148 | { | |
149 | [PHY_GMII_SEL_PORT_MODE] = REG_FIELD(0x554, 0, 1), | |
150 | [PHY_GMII_SEL_RGMII_ID_MODE] = REG_FIELD((~0), 0, 0), | |
151 | [PHY_GMII_SEL_RMII_IO_CLK_EN] = REG_FIELD((~0), 0, 0), | |
152 | }, | |
153 | { | |
154 | [PHY_GMII_SEL_PORT_MODE] = REG_FIELD(0x554, 4, 5), | |
155 | [PHY_GMII_SEL_RGMII_ID_MODE] = REG_FIELD((~0), 0, 0), | |
156 | [PHY_GMII_SEL_RMII_IO_CLK_EN] = REG_FIELD((~0), 0, 0), | |
157 | }, | |
158 | }; | |
159 | ||
160 | static const | |
161 | struct phy_gmii_sel_soc_data phy_gmii_sel_soc_dra7 = { | |
162 | .num_ports = 2, | |
163 | .regfields = phy_gmii_sel_fields_dra7, | |
164 | }; | |
165 | ||
166 | static const | |
167 | struct phy_gmii_sel_soc_data phy_gmii_sel_soc_dm814 = { | |
168 | .num_ports = 2, | |
169 | .features = BIT(PHY_GMII_SEL_RGMII_ID_MODE), | |
170 | .regfields = phy_gmii_sel_fields_am33xx, | |
171 | }; | |
172 | ||
d9aa91df GS |
173 | static const |
174 | struct reg_field phy_gmii_sel_fields_am654[][PHY_GMII_SEL_LAST] = { | |
175 | { | |
176 | [PHY_GMII_SEL_PORT_MODE] = REG_FIELD(0x4040, 0, 1), | |
177 | [PHY_GMII_SEL_RGMII_ID_MODE] = REG_FIELD((~0), 0, 0), | |
178 | [PHY_GMII_SEL_RMII_IO_CLK_EN] = REG_FIELD((~0), 0, 0), | |
179 | }, | |
180 | }; | |
181 | ||
182 | static const | |
183 | struct phy_gmii_sel_soc_data phy_gmii_sel_soc_am654 = { | |
184 | .num_ports = 1, | |
185 | .regfields = phy_gmii_sel_fields_am654, | |
186 | }; | |
187 | ||
92b58b34 GS |
188 | static const struct of_device_id phy_gmii_sel_id_table[] = { |
189 | { | |
190 | .compatible = "ti,am3352-phy-gmii-sel", | |
191 | .data = &phy_gmii_sel_soc_am33xx, | |
192 | }, | |
193 | { | |
194 | .compatible = "ti,dra7xx-phy-gmii-sel", | |
195 | .data = &phy_gmii_sel_soc_dra7, | |
196 | }, | |
197 | { | |
198 | .compatible = "ti,am43xx-phy-gmii-sel", | |
199 | .data = &phy_gmii_sel_soc_am33xx, | |
200 | }, | |
201 | { | |
202 | .compatible = "ti,dm814-phy-gmii-sel", | |
203 | .data = &phy_gmii_sel_soc_dm814, | |
204 | }, | |
d9aa91df GS |
205 | { |
206 | .compatible = "ti,am654-phy-gmii-sel", | |
207 | .data = &phy_gmii_sel_soc_am654, | |
208 | }, | |
92b58b34 GS |
209 | {} |
210 | }; | |
211 | MODULE_DEVICE_TABLE(of, phy_gmii_sel_id_table); | |
212 | ||
213 | static const struct phy_ops phy_gmii_sel_ops = { | |
214 | .set_mode = phy_gmii_sel_mode, | |
215 | .owner = THIS_MODULE, | |
216 | }; | |
217 | ||
218 | static struct phy *phy_gmii_sel_of_xlate(struct device *dev, | |
219 | struct of_phandle_args *args) | |
220 | { | |
221 | struct phy_gmii_sel_priv *priv = dev_get_drvdata(dev); | |
222 | int phy_id = args->args[0]; | |
223 | ||
224 | if (args->args_count < 1) | |
225 | return ERR_PTR(-EINVAL); | |
1138a442 CIK |
226 | if (!priv || !priv->if_phys) |
227 | return ERR_PTR(-ENODEV); | |
92b58b34 GS |
228 | if (priv->soc_data->features & BIT(PHY_GMII_SEL_RMII_IO_CLK_EN) && |
229 | args->args_count < 2) | |
230 | return ERR_PTR(-EINVAL); | |
92b58b34 GS |
231 | if (phy_id > priv->soc_data->num_ports) |
232 | return ERR_PTR(-EINVAL); | |
233 | if (phy_id != priv->if_phys[phy_id - 1].id) | |
234 | return ERR_PTR(-EINVAL); | |
235 | ||
236 | phy_id--; | |
237 | if (priv->soc_data->features & BIT(PHY_GMII_SEL_RMII_IO_CLK_EN)) | |
238 | priv->if_phys[phy_id].rmii_clock_external = args->args[1]; | |
239 | dev_dbg(dev, "%s id:%u ext:%d\n", __func__, | |
240 | priv->if_phys[phy_id].id, args->args[1]); | |
241 | ||
242 | return priv->if_phys[phy_id].if_phy; | |
243 | } | |
244 | ||
245 | static int phy_gmii_sel_init_ports(struct phy_gmii_sel_priv *priv) | |
246 | { | |
247 | const struct phy_gmii_sel_soc_data *soc_data = priv->soc_data; | |
248 | struct device *dev = priv->dev; | |
249 | struct phy_gmii_sel_phy_priv *if_phys; | |
250 | int i, num_ports, ret; | |
251 | ||
252 | num_ports = priv->soc_data->num_ports; | |
253 | ||
254 | if_phys = devm_kcalloc(priv->dev, num_ports, | |
255 | sizeof(*if_phys), GFP_KERNEL); | |
256 | if (!if_phys) | |
257 | return -ENOMEM; | |
258 | dev_dbg(dev, "%s %d\n", __func__, num_ports); | |
259 | ||
260 | for (i = 0; i < num_ports; i++) { | |
261 | const struct reg_field *field; | |
262 | struct regmap_field *regfield; | |
263 | ||
264 | if_phys[i].id = i + 1; | |
265 | if_phys[i].priv = priv; | |
266 | ||
267 | field = &soc_data->regfields[i][PHY_GMII_SEL_PORT_MODE]; | |
268 | dev_dbg(dev, "%s field %x %d %d\n", __func__, | |
269 | field->reg, field->msb, field->lsb); | |
270 | ||
271 | regfield = devm_regmap_field_alloc(dev, priv->regmap, *field); | |
272 | if (IS_ERR(regfield)) | |
273 | return PTR_ERR(regfield); | |
274 | if_phys[i].fields[PHY_GMII_SEL_PORT_MODE] = regfield; | |
275 | ||
276 | field = &soc_data->regfields[i][PHY_GMII_SEL_RGMII_ID_MODE]; | |
277 | if (field->reg != (~0)) { | |
278 | regfield = devm_regmap_field_alloc(dev, | |
279 | priv->regmap, | |
280 | *field); | |
281 | if (IS_ERR(regfield)) | |
282 | return PTR_ERR(regfield); | |
283 | if_phys[i].fields[PHY_GMII_SEL_RGMII_ID_MODE] = | |
284 | regfield; | |
285 | } | |
286 | ||
287 | field = &soc_data->regfields[i][PHY_GMII_SEL_RMII_IO_CLK_EN]; | |
288 | if (field->reg != (~0)) { | |
289 | regfield = devm_regmap_field_alloc(dev, | |
290 | priv->regmap, | |
291 | *field); | |
292 | if (IS_ERR(regfield)) | |
293 | return PTR_ERR(regfield); | |
294 | if_phys[i].fields[PHY_GMII_SEL_RMII_IO_CLK_EN] = | |
295 | regfield; | |
296 | } | |
297 | ||
298 | if_phys[i].if_phy = devm_phy_create(dev, | |
299 | priv->dev->of_node, | |
300 | &phy_gmii_sel_ops); | |
301 | if (IS_ERR(if_phys[i].if_phy)) { | |
302 | ret = PTR_ERR(if_phys[i].if_phy); | |
303 | dev_err(dev, "Failed to create phy%d %d\n", i, ret); | |
304 | return ret; | |
305 | } | |
306 | phy_set_drvdata(if_phys[i].if_phy, &if_phys[i]); | |
307 | } | |
308 | ||
309 | priv->if_phys = if_phys; | |
310 | return 0; | |
311 | } | |
312 | ||
313 | static int phy_gmii_sel_probe(struct platform_device *pdev) | |
314 | { | |
315 | struct device *dev = &pdev->dev; | |
316 | struct device_node *node = dev->of_node; | |
317 | const struct of_device_id *of_id; | |
318 | struct phy_gmii_sel_priv *priv; | |
319 | int ret; | |
320 | ||
321 | of_id = of_match_node(phy_gmii_sel_id_table, pdev->dev.of_node); | |
322 | if (!of_id) | |
323 | return -EINVAL; | |
324 | ||
325 | priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); | |
326 | if (!priv) | |
327 | return -ENOMEM; | |
328 | ||
329 | priv->dev = &pdev->dev; | |
330 | priv->soc_data = of_id->data; | |
331 | ||
332 | priv->regmap = syscon_node_to_regmap(node->parent); | |
333 | if (IS_ERR(priv->regmap)) { | |
334 | ret = PTR_ERR(priv->regmap); | |
335 | dev_err(dev, "Failed to get syscon %d\n", ret); | |
336 | return ret; | |
337 | } | |
338 | ||
339 | ret = phy_gmii_sel_init_ports(priv); | |
340 | if (ret) | |
341 | return ret; | |
342 | ||
343 | dev_set_drvdata(&pdev->dev, priv); | |
344 | ||
345 | priv->phy_provider = | |
346 | devm_of_phy_provider_register(dev, | |
347 | phy_gmii_sel_of_xlate); | |
348 | if (IS_ERR(priv->phy_provider)) { | |
349 | ret = PTR_ERR(priv->phy_provider); | |
350 | dev_err(dev, "Failed to create phy provider %d\n", ret); | |
351 | return ret; | |
352 | } | |
353 | ||
354 | return 0; | |
355 | } | |
356 | ||
357 | static struct platform_driver phy_gmii_sel_driver = { | |
358 | .probe = phy_gmii_sel_probe, | |
359 | .driver = { | |
360 | .name = "phy-gmii-sel", | |
361 | .of_match_table = phy_gmii_sel_id_table, | |
362 | }, | |
363 | }; | |
364 | module_platform_driver(phy_gmii_sel_driver); | |
365 | ||
366 | MODULE_LICENSE("GPL v2"); | |
367 | MODULE_AUTHOR("Grygorii Strashko <grygorii.strashko@ti.com>"); | |
368 | MODULE_DESCRIPTION("TI CPSW Port's PHY Interface Mode selection Driver"); |