summaryrefslogtreecommitdiffstats
path: root/drivers/phy
diff options
context:
space:
mode:
authorGrygorii Strashko <grygorii.strashko@ti.com>2018-11-25 19:15:23 -0500
committerKishon Vijay Abraham I <kishon@ti.com>2018-12-11 23:31:40 -0500
commit92b58b34741ff5b9efa583add6e63ca4103f8e29 (patch)
tree11baabd36ee0f63e8c18d184459ef2d86ccf6703 /drivers/phy
parent5b9bf512ca93a46d370215966d21208ca2e7dc64 (diff)
phy: ti: introduce phy-gmii-sel driver
TI am335x/am437x/dra7(am5)/dm814x CPSW3G Ethernet Subsystem supports two 10/100/1000 Ethernet ports with selectable G/MII, RMII, and RGMII interfaces. The interface mode is selected by configuring the MII mode selection register(s) (GMII_SEL) in the System Control Module chapter (SCM). GMII_SEL register(s) and bit fields placement in SCM are different between SoCs while fields meaning is the same. Historically CPSW external Port's interface mode selection configuration was introduced using custom API and driver cpsw-phy-sel.c. This leads to unnecessary driver, DT binding and custom API support effort. This patch introduces CPSW Port's PHY Interface Mode selection Driver (phy-gmii-sel) which implements standard Linux PHY interface and used as a replacement for TI's specific driver cpsw-phy-sel.c and corresponding custom API. Cc: Kishon Vijay Abraham I <kishon@ti.com> Cc: Tony Lindgren <tony@atomide.com> Signed-off-by: Grygorii Strashko <grygorii.strashko@ti.com> Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com>
Diffstat (limited to 'drivers/phy')
-rw-r--r--drivers/phy/ti/Kconfig10
-rw-r--r--drivers/phy/ti/Makefile1
-rw-r--r--drivers/phy/ti/phy-gmii-sel.c349
3 files changed, 360 insertions, 0 deletions
diff --git a/drivers/phy/ti/Kconfig b/drivers/phy/ti/Kconfig
index 20503562666c..f137e0107764 100644
--- a/drivers/phy/ti/Kconfig
+++ b/drivers/phy/ti/Kconfig
@@ -76,3 +76,13 @@ config TWL4030_USB
76 family chips (including the TWL5030 and TPS659x0 devices). 76 family chips (including the TWL5030 and TPS659x0 devices).
77 This transceiver supports high and full speed devices plus, 77 This transceiver supports high and full speed devices plus,
78 in host mode, low speed. 78 in host mode, low speed.
79
80config PHY_TI_GMII_SEL
81 tristate
82 default y if TI_CPSW=y
83 depends on TI_CPSW || COMPILE_TEST
84 select GENERIC_PHY
85 default m
86 help
87 This driver supports configuring of the TI CPSW Port mode depending on
88 the Ethernet PHY connected to the CPSW Port.
diff --git a/drivers/phy/ti/Makefile b/drivers/phy/ti/Makefile
index 9f361756eaf2..bea8f25a137a 100644
--- a/drivers/phy/ti/Makefile
+++ b/drivers/phy/ti/Makefile
@@ -6,3 +6,4 @@ obj-$(CONFIG_OMAP_USB2) += phy-omap-usb2.o
6obj-$(CONFIG_TI_PIPE3) += phy-ti-pipe3.o 6obj-$(CONFIG_TI_PIPE3) += phy-ti-pipe3.o
7obj-$(CONFIG_PHY_TUSB1210) += phy-tusb1210.o 7obj-$(CONFIG_PHY_TUSB1210) += phy-tusb1210.o
8obj-$(CONFIG_TWL4030_USB) += phy-twl4030-usb.o 8obj-$(CONFIG_TWL4030_USB) += phy-twl4030-usb.o
9obj-$(CONFIG_PHY_TI_GMII_SEL) += phy-gmii-sel.o
diff --git a/drivers/phy/ti/phy-gmii-sel.c b/drivers/phy/ti/phy-gmii-sel.c
new file mode 100644
index 000000000000..04ebf53d2b32
--- /dev/null
+++ b/drivers/phy/ti/phy-gmii-sel.c
@@ -0,0 +1,349 @@
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
24enum {
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
31struct 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
40struct phy_gmii_sel_soc_data {
41 u32 num_ports;
42 u32 features;
43 const struct reg_field (*regfields)[PHY_GMII_SEL_LAST];
44};
45
46struct 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
54static 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:
72 gmii_sel_mode = AM33XX_GMII_SEL_MODE_RGMII;
73 break;
74
75 case PHY_INTERFACE_MODE_RGMII_ID:
76 case PHY_INTERFACE_MODE_RGMII_RXID:
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:
83 mode = AM33XX_GMII_SEL_MODE_MII;
84 break;
85
86 default:
87 dev_warn(dev,
88 "port%u: unsupported mode: \"%s\". Defaulting to MII.\n",
89 if_phy->id, phy_modes(rgmii_id));
90 return -EINVAL;
91 };
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",
96 __func__, if_phy->id, mode, rgmii_id,
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
124static const
125struct 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
138static const
139struct 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
146static const
147struct 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
160static const
161struct phy_gmii_sel_soc_data phy_gmii_sel_soc_dra7 = {
162 .num_ports = 2,
163 .regfields = phy_gmii_sel_fields_dra7,
164};
165
166static const
167struct 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
173static const struct of_device_id phy_gmii_sel_id_table[] = {
174 {
175 .compatible = "ti,am3352-phy-gmii-sel",
176 .data = &phy_gmii_sel_soc_am33xx,
177 },
178 {
179 .compatible = "ti,dra7xx-phy-gmii-sel",
180 .data = &phy_gmii_sel_soc_dra7,
181 },
182 {
183 .compatible = "ti,am43xx-phy-gmii-sel",
184 .data = &phy_gmii_sel_soc_am33xx,
185 },
186 {
187 .compatible = "ti,dm814-phy-gmii-sel",
188 .data = &phy_gmii_sel_soc_dm814,
189 },
190 {}
191};
192MODULE_DEVICE_TABLE(of, phy_gmii_sel_id_table);
193
194static const struct phy_ops phy_gmii_sel_ops = {
195 .set_mode = phy_gmii_sel_mode,
196 .owner = THIS_MODULE,
197};
198
199static struct phy *phy_gmii_sel_of_xlate(struct device *dev,
200 struct of_phandle_args *args)
201{
202 struct phy_gmii_sel_priv *priv = dev_get_drvdata(dev);
203 int phy_id = args->args[0];
204
205 if (args->args_count < 1)
206 return ERR_PTR(-EINVAL);
207 if (priv->soc_data->features & BIT(PHY_GMII_SEL_RMII_IO_CLK_EN) &&
208 args->args_count < 2)
209 return ERR_PTR(-EINVAL);
210 if (!priv || !priv->if_phys)
211 return ERR_PTR(-ENODEV);
212 if (phy_id > priv->soc_data->num_ports)
213 return ERR_PTR(-EINVAL);
214 if (phy_id != priv->if_phys[phy_id - 1].id)
215 return ERR_PTR(-EINVAL);
216
217 phy_id--;
218 if (priv->soc_data->features & BIT(PHY_GMII_SEL_RMII_IO_CLK_EN))
219 priv->if_phys[phy_id].rmii_clock_external = args->args[1];
220 dev_dbg(dev, "%s id:%u ext:%d\n", __func__,
221 priv->if_phys[phy_id].id, args->args[1]);
222
223 return priv->if_phys[phy_id].if_phy;
224}
225
226static int phy_gmii_sel_init_ports(struct phy_gmii_sel_priv *priv)
227{
228 const struct phy_gmii_sel_soc_data *soc_data = priv->soc_data;
229 struct device *dev = priv->dev;
230 struct phy_gmii_sel_phy_priv *if_phys;
231 int i, num_ports, ret;
232
233 num_ports = priv->soc_data->num_ports;
234
235 if_phys = devm_kcalloc(priv->dev, num_ports,
236 sizeof(*if_phys), GFP_KERNEL);
237 if (!if_phys)
238 return -ENOMEM;
239 dev_dbg(dev, "%s %d\n", __func__, num_ports);
240
241 for (i = 0; i < num_ports; i++) {
242 const struct reg_field *field;
243 struct regmap_field *regfield;
244
245 if_phys[i].id = i + 1;
246 if_phys[i].priv = priv;
247
248 field = &soc_data->regfields[i][PHY_GMII_SEL_PORT_MODE];
249 dev_dbg(dev, "%s field %x %d %d\n", __func__,
250 field->reg, field->msb, field->lsb);
251
252 regfield = devm_regmap_field_alloc(dev, priv->regmap, *field);
253 if (IS_ERR(regfield))
254 return PTR_ERR(regfield);
255 if_phys[i].fields[PHY_GMII_SEL_PORT_MODE] = regfield;
256
257 field = &soc_data->regfields[i][PHY_GMII_SEL_RGMII_ID_MODE];
258 if (field->reg != (~0)) {
259 regfield = devm_regmap_field_alloc(dev,
260 priv->regmap,
261 *field);
262 if (IS_ERR(regfield))
263 return PTR_ERR(regfield);
264 if_phys[i].fields[PHY_GMII_SEL_RGMII_ID_MODE] =
265 regfield;
266 }
267
268 field = &soc_data->regfields[i][PHY_GMII_SEL_RMII_IO_CLK_EN];
269 if (field->reg != (~0)) {
270 regfield = devm_regmap_field_alloc(dev,
271 priv->regmap,
272 *field);
273 if (IS_ERR(regfield))
274 return PTR_ERR(regfield);
275 if_phys[i].fields[PHY_GMII_SEL_RMII_IO_CLK_EN] =
276 regfield;
277 }
278
279 if_phys[i].if_phy = devm_phy_create(dev,
280 priv->dev->of_node,
281 &phy_gmii_sel_ops);
282 if (IS_ERR(if_phys[i].if_phy)) {
283 ret = PTR_ERR(if_phys[i].if_phy);
284 dev_err(dev, "Failed to create phy%d %d\n", i, ret);
285 return ret;
286 }
287 phy_set_drvdata(if_phys[i].if_phy, &if_phys[i]);
288 }
289
290 priv->if_phys = if_phys;
291 return 0;
292}
293
294static int phy_gmii_sel_probe(struct platform_device *pdev)
295{
296 struct device *dev = &pdev->dev;
297 struct device_node *node = dev->of_node;
298 const struct of_device_id *of_id;
299 struct phy_gmii_sel_priv *priv;
300 int ret;
301
302 of_id = of_match_node(phy_gmii_sel_id_table, pdev->dev.of_node);
303 if (!of_id)
304 return -EINVAL;
305
306 priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
307 if (!priv)
308 return -ENOMEM;
309
310 priv->dev = &pdev->dev;
311 priv->soc_data = of_id->data;
312
313 priv->regmap = syscon_node_to_regmap(node->parent);
314 if (IS_ERR(priv->regmap)) {
315 ret = PTR_ERR(priv->regmap);
316 dev_err(dev, "Failed to get syscon %d\n", ret);
317 return ret;
318 }
319
320 ret = phy_gmii_sel_init_ports(priv);
321 if (ret)
322 return ret;
323
324 dev_set_drvdata(&pdev->dev, priv);
325
326 priv->phy_provider =
327 devm_of_phy_provider_register(dev,
328 phy_gmii_sel_of_xlate);
329 if (IS_ERR(priv->phy_provider)) {
330 ret = PTR_ERR(priv->phy_provider);
331 dev_err(dev, "Failed to create phy provider %d\n", ret);
332 return ret;
333 }
334
335 return 0;
336}
337
338static struct platform_driver phy_gmii_sel_driver = {
339 .probe = phy_gmii_sel_probe,
340 .driver = {
341 .name = "phy-gmii-sel",
342 .of_match_table = phy_gmii_sel_id_table,
343 },
344};
345module_platform_driver(phy_gmii_sel_driver);
346
347MODULE_LICENSE("GPL v2");
348MODULE_AUTHOR("Grygorii Strashko <grygorii.strashko@ti.com>");
349MODULE_DESCRIPTION("TI CPSW Port's PHY Interface Mode selection Driver");