1/* Texas Instruments Ethernet Switch Driver
2 *
3 * Copyright (C) 2013 Texas Instruments
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * version 2 as published by the Free Software Foundation.
8 *
9 * This program is distributed "as is" WITHOUT ANY WARRANTY of any
10 * kind, whether express or implied; without even the implied warranty
11 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 */
14
15#include <linux/platform_device.h>
16#include <linux/module.h>
17#include <linux/netdevice.h>
18#include <linux/phy.h>
19#include <linux/of.h>
20#include <linux/of_device.h>
21
22#include "cpsw.h"
23
24/* AM33xx SoC specific definitions for the CONTROL port */
25#define AM33XX_GMII_SEL_MODE_MII	0
26#define AM33XX_GMII_SEL_MODE_RMII	1
27#define AM33XX_GMII_SEL_MODE_RGMII	2
28
29#define AM33XX_GMII_SEL_RMII2_IO_CLK_EN	BIT(7)
30#define AM33XX_GMII_SEL_RMII1_IO_CLK_EN	BIT(6)
31
32#define GMII_SEL_MODE_MASK		0x3
33
34struct cpsw_phy_sel_priv {
35	struct device	*dev;
36	u32 __iomem	*gmii_sel;
37	bool		rmii_clock_external;
38	void (*cpsw_phy_sel)(struct cpsw_phy_sel_priv *priv,
39			     phy_interface_t phy_mode, int slave);
40};
41
42
43static void cpsw_gmii_sel_am3352(struct cpsw_phy_sel_priv *priv,
44				 phy_interface_t phy_mode, int slave)
45{
46	u32 reg;
47	u32 mask;
48	u32 mode = 0;
49
50	reg = readl(priv->gmii_sel);
51
52	switch (phy_mode) {
53	case PHY_INTERFACE_MODE_RMII:
54		mode = AM33XX_GMII_SEL_MODE_RMII;
55		break;
56
57	case PHY_INTERFACE_MODE_RGMII:
58	case PHY_INTERFACE_MODE_RGMII_ID:
59	case PHY_INTERFACE_MODE_RGMII_RXID:
60	case PHY_INTERFACE_MODE_RGMII_TXID:
61		mode = AM33XX_GMII_SEL_MODE_RGMII;
62		break;
63
64	case PHY_INTERFACE_MODE_MII:
65	default:
66		mode = AM33XX_GMII_SEL_MODE_MII;
67		break;
68	};
69
70	mask = GMII_SEL_MODE_MASK << (slave * 2) | BIT(slave + 6);
71	mode <<= slave * 2;
72
73	if (priv->rmii_clock_external) {
74		if (slave == 0)
75			mode |= AM33XX_GMII_SEL_RMII1_IO_CLK_EN;
76		else
77			mode |= AM33XX_GMII_SEL_RMII2_IO_CLK_EN;
78	}
79
80	reg &= ~mask;
81	reg |= mode;
82
83	writel(reg, priv->gmii_sel);
84}
85
86static void cpsw_gmii_sel_dra7xx(struct cpsw_phy_sel_priv *priv,
87				 phy_interface_t phy_mode, int slave)
88{
89	u32 reg;
90	u32 mask;
91	u32 mode = 0;
92
93	reg = readl(priv->gmii_sel);
94
95	switch (phy_mode) {
96	case PHY_INTERFACE_MODE_RMII:
97		mode = AM33XX_GMII_SEL_MODE_RMII;
98		break;
99
100	case PHY_INTERFACE_MODE_RGMII:
101	case PHY_INTERFACE_MODE_RGMII_ID:
102	case PHY_INTERFACE_MODE_RGMII_RXID:
103	case PHY_INTERFACE_MODE_RGMII_TXID:
104		mode = AM33XX_GMII_SEL_MODE_RGMII;
105		break;
106
107	case PHY_INTERFACE_MODE_MII:
108	default:
109		mode = AM33XX_GMII_SEL_MODE_MII;
110		break;
111	};
112
113	switch (slave) {
114	case 0:
115		mask = GMII_SEL_MODE_MASK;
116		break;
117	case 1:
118		mask = GMII_SEL_MODE_MASK << 4;
119		mode <<= 4;
120		break;
121	default:
122		dev_err(priv->dev, "invalid slave number...\n");
123		return;
124	}
125
126	if (priv->rmii_clock_external)
127		dev_err(priv->dev, "RMII External clock is not supported\n");
128
129	reg &= ~mask;
130	reg |= mode;
131
132	writel(reg, priv->gmii_sel);
133}
134
135static struct platform_driver cpsw_phy_sel_driver;
136static int match(struct device *dev, void *data)
137{
138	struct device_node *node = (struct device_node *)data;
139	return dev->of_node == node &&
140		dev->driver == &cpsw_phy_sel_driver.driver;
141}
142
143void cpsw_phy_sel(struct device *dev, phy_interface_t phy_mode, int slave)
144{
145	struct device_node *node;
146	struct cpsw_phy_sel_priv *priv;
147
148	node = of_get_child_by_name(dev->of_node, "cpsw-phy-sel");
149	if (!node) {
150		dev_err(dev, "Phy mode driver DT not found\n");
151		return;
152	}
153
154	dev = bus_find_device(&platform_bus_type, NULL, node, match);
155	priv = dev_get_drvdata(dev);
156
157	priv->cpsw_phy_sel(priv, phy_mode, slave);
158}
159EXPORT_SYMBOL_GPL(cpsw_phy_sel);
160
161static const struct of_device_id cpsw_phy_sel_id_table[] = {
162	{
163		.compatible	= "ti,am3352-cpsw-phy-sel",
164		.data		= &cpsw_gmii_sel_am3352,
165	},
166	{
167		.compatible	= "ti,dra7xx-cpsw-phy-sel",
168		.data		= &cpsw_gmii_sel_dra7xx,
169	},
170	{
171		.compatible	= "ti,am43xx-cpsw-phy-sel",
172		.data		= &cpsw_gmii_sel_am3352,
173	},
174	{}
175};
176MODULE_DEVICE_TABLE(of, cpsw_phy_sel_id_table);
177
178static int cpsw_phy_sel_probe(struct platform_device *pdev)
179{
180	struct resource	*res;
181	const struct of_device_id *of_id;
182	struct cpsw_phy_sel_priv *priv;
183
184	of_id = of_match_node(cpsw_phy_sel_id_table, pdev->dev.of_node);
185	if (!of_id)
186		return -EINVAL;
187
188	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
189	if (!priv) {
190		dev_err(&pdev->dev, "unable to alloc memory for cpsw phy sel\n");
191		return -ENOMEM;
192	}
193
194	priv->dev = &pdev->dev;
195	priv->cpsw_phy_sel = of_id->data;
196
197	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "gmii-sel");
198	priv->gmii_sel = devm_ioremap_resource(&pdev->dev, res);
199	if (IS_ERR(priv->gmii_sel))
200		return PTR_ERR(priv->gmii_sel);
201
202	if (of_find_property(pdev->dev.of_node, "rmii-clock-ext", NULL))
203		priv->rmii_clock_external = true;
204
205	dev_set_drvdata(&pdev->dev, priv);
206
207	return 0;
208}
209
210static struct platform_driver cpsw_phy_sel_driver = {
211	.probe		= cpsw_phy_sel_probe,
212	.driver		= {
213		.name	= "cpsw-phy-sel",
214		.of_match_table = cpsw_phy_sel_id_table,
215	},
216};
217
218module_platform_driver(cpsw_phy_sel_driver);
219MODULE_AUTHOR("Mugunthan V N <mugunthanvnm@ti.com>");
220MODULE_LICENSE("GPL v2");
221