1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* Texas Instruments Ethernet Switch Driver |
3 | * |
4 | * Copyright (C) 2013 Texas Instruments |
5 | * |
6 | * Module Author: Mugunthan V N <mugunthanvnm@ti.com> |
7 | * |
8 | */ |
9 | |
10 | #include <linux/platform_device.h> |
11 | #include <linux/init.h> |
12 | #include <linux/netdevice.h> |
13 | #include <linux/phy.h> |
14 | #include <linux/of.h> |
15 | |
16 | #include "cpsw.h" |
17 | |
18 | /* AM33xx SoC specific definitions for the CONTROL port */ |
19 | #define AM33XX_GMII_SEL_MODE_MII 0 |
20 | #define AM33XX_GMII_SEL_MODE_RMII 1 |
21 | #define AM33XX_GMII_SEL_MODE_RGMII 2 |
22 | |
23 | #define AM33XX_GMII_SEL_RMII2_IO_CLK_EN BIT(7) |
24 | #define AM33XX_GMII_SEL_RMII1_IO_CLK_EN BIT(6) |
25 | #define AM33XX_GMII_SEL_RGMII2_IDMODE BIT(5) |
26 | #define AM33XX_GMII_SEL_RGMII1_IDMODE BIT(4) |
27 | |
28 | #define GMII_SEL_MODE_MASK 0x3 |
29 | |
30 | struct cpsw_phy_sel_priv { |
31 | struct device *dev; |
32 | u32 __iomem *gmii_sel; |
33 | bool rmii_clock_external; |
34 | void (*cpsw_phy_sel)(struct cpsw_phy_sel_priv *priv, |
35 | phy_interface_t phy_mode, int slave); |
36 | }; |
37 | |
38 | |
39 | static void cpsw_gmii_sel_am3352(struct cpsw_phy_sel_priv *priv, |
40 | phy_interface_t phy_mode, int slave) |
41 | { |
42 | u32 reg; |
43 | u32 mask; |
44 | u32 mode = 0; |
45 | bool rgmii_id = false; |
46 | |
47 | reg = readl(addr: priv->gmii_sel); |
48 | |
49 | switch (phy_mode) { |
50 | case PHY_INTERFACE_MODE_RMII: |
51 | mode = AM33XX_GMII_SEL_MODE_RMII; |
52 | break; |
53 | |
54 | case PHY_INTERFACE_MODE_RGMII: |
55 | mode = AM33XX_GMII_SEL_MODE_RGMII; |
56 | break; |
57 | |
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 | rgmii_id = true; |
63 | break; |
64 | |
65 | default: |
66 | dev_warn(priv->dev, |
67 | "Unsupported PHY mode: \"%s\". Defaulting to MII.\n" , |
68 | phy_modes(phy_mode)); |
69 | fallthrough; |
70 | case PHY_INTERFACE_MODE_MII: |
71 | mode = AM33XX_GMII_SEL_MODE_MII; |
72 | break; |
73 | } |
74 | |
75 | mask = GMII_SEL_MODE_MASK << (slave * 2) | BIT(slave + 6); |
76 | mask |= BIT(slave + 4); |
77 | mode <<= slave * 2; |
78 | |
79 | if (priv->rmii_clock_external) { |
80 | if (slave == 0) |
81 | mode |= AM33XX_GMII_SEL_RMII1_IO_CLK_EN; |
82 | else |
83 | mode |= AM33XX_GMII_SEL_RMII2_IO_CLK_EN; |
84 | } |
85 | |
86 | if (rgmii_id) { |
87 | if (slave == 0) |
88 | mode |= AM33XX_GMII_SEL_RGMII1_IDMODE; |
89 | else |
90 | mode |= AM33XX_GMII_SEL_RGMII2_IDMODE; |
91 | } |
92 | |
93 | reg &= ~mask; |
94 | reg |= mode; |
95 | |
96 | writel(val: reg, addr: priv->gmii_sel); |
97 | } |
98 | |
99 | static void cpsw_gmii_sel_dra7xx(struct cpsw_phy_sel_priv *priv, |
100 | phy_interface_t phy_mode, int slave) |
101 | { |
102 | u32 reg; |
103 | u32 mask; |
104 | u32 mode = 0; |
105 | |
106 | reg = readl(addr: priv->gmii_sel); |
107 | |
108 | switch (phy_mode) { |
109 | case PHY_INTERFACE_MODE_RMII: |
110 | mode = AM33XX_GMII_SEL_MODE_RMII; |
111 | break; |
112 | |
113 | case PHY_INTERFACE_MODE_RGMII: |
114 | case PHY_INTERFACE_MODE_RGMII_ID: |
115 | case PHY_INTERFACE_MODE_RGMII_RXID: |
116 | case PHY_INTERFACE_MODE_RGMII_TXID: |
117 | mode = AM33XX_GMII_SEL_MODE_RGMII; |
118 | break; |
119 | |
120 | default: |
121 | dev_warn(priv->dev, |
122 | "Unsupported PHY mode: \"%s\". Defaulting to MII.\n" , |
123 | phy_modes(phy_mode)); |
124 | fallthrough; |
125 | case PHY_INTERFACE_MODE_MII: |
126 | mode = AM33XX_GMII_SEL_MODE_MII; |
127 | break; |
128 | } |
129 | |
130 | switch (slave) { |
131 | case 0: |
132 | mask = GMII_SEL_MODE_MASK; |
133 | break; |
134 | case 1: |
135 | mask = GMII_SEL_MODE_MASK << 4; |
136 | mode <<= 4; |
137 | break; |
138 | default: |
139 | dev_err(priv->dev, "invalid slave number...\n" ); |
140 | return; |
141 | } |
142 | |
143 | if (priv->rmii_clock_external) |
144 | dev_err(priv->dev, "RMII External clock is not supported\n" ); |
145 | |
146 | reg &= ~mask; |
147 | reg |= mode; |
148 | |
149 | writel(val: reg, addr: priv->gmii_sel); |
150 | } |
151 | |
152 | static struct platform_driver cpsw_phy_sel_driver; |
153 | static int match(struct device *dev, const void *data) |
154 | { |
155 | const struct device_node *node = (const struct device_node *)data; |
156 | return dev->of_node == node && |
157 | dev->driver == &cpsw_phy_sel_driver.driver; |
158 | } |
159 | |
160 | void cpsw_phy_sel(struct device *dev, phy_interface_t phy_mode, int slave) |
161 | { |
162 | struct device_node *node; |
163 | struct cpsw_phy_sel_priv *priv; |
164 | |
165 | node = of_parse_phandle(np: dev->of_node, phandle_name: "cpsw-phy-sel" , index: 0); |
166 | if (!node) { |
167 | node = of_get_child_by_name(node: dev->of_node, name: "cpsw-phy-sel" ); |
168 | if (!node) { |
169 | dev_err(dev, "Phy mode driver DT not found\n" ); |
170 | return; |
171 | } |
172 | } |
173 | |
174 | dev = bus_find_device(bus: &platform_bus_type, NULL, data: node, match); |
175 | if (!dev) { |
176 | dev_err(dev, "unable to find platform device for %pOF\n" , node); |
177 | goto out; |
178 | } |
179 | |
180 | priv = dev_get_drvdata(dev); |
181 | |
182 | priv->cpsw_phy_sel(priv, phy_mode, slave); |
183 | |
184 | put_device(dev); |
185 | out: |
186 | of_node_put(node); |
187 | } |
188 | EXPORT_SYMBOL_GPL(cpsw_phy_sel); |
189 | |
190 | static const struct of_device_id cpsw_phy_sel_id_table[] = { |
191 | { |
192 | .compatible = "ti,am3352-cpsw-phy-sel" , |
193 | .data = &cpsw_gmii_sel_am3352, |
194 | }, |
195 | { |
196 | .compatible = "ti,dra7xx-cpsw-phy-sel" , |
197 | .data = &cpsw_gmii_sel_dra7xx, |
198 | }, |
199 | { |
200 | .compatible = "ti,am43xx-cpsw-phy-sel" , |
201 | .data = &cpsw_gmii_sel_am3352, |
202 | }, |
203 | {} |
204 | }; |
205 | |
206 | static int cpsw_phy_sel_probe(struct platform_device *pdev) |
207 | { |
208 | const struct of_device_id *of_id; |
209 | struct cpsw_phy_sel_priv *priv; |
210 | |
211 | of_id = of_match_node(matches: cpsw_phy_sel_id_table, node: pdev->dev.of_node); |
212 | if (!of_id) |
213 | return -EINVAL; |
214 | |
215 | priv = devm_kzalloc(dev: &pdev->dev, size: sizeof(*priv), GFP_KERNEL); |
216 | if (!priv) { |
217 | dev_err(&pdev->dev, "unable to alloc memory for cpsw phy sel\n" ); |
218 | return -ENOMEM; |
219 | } |
220 | |
221 | priv->dev = &pdev->dev; |
222 | priv->cpsw_phy_sel = of_id->data; |
223 | |
224 | priv->gmii_sel = devm_platform_ioremap_resource_byname(pdev, name: "gmii-sel" ); |
225 | if (IS_ERR(ptr: priv->gmii_sel)) |
226 | return PTR_ERR(ptr: priv->gmii_sel); |
227 | |
228 | priv->rmii_clock_external = of_property_read_bool(np: pdev->dev.of_node, propname: "rmii-clock-ext" ); |
229 | |
230 | dev_set_drvdata(dev: &pdev->dev, data: priv); |
231 | |
232 | return 0; |
233 | } |
234 | |
235 | static struct platform_driver cpsw_phy_sel_driver = { |
236 | .probe = cpsw_phy_sel_probe, |
237 | .driver = { |
238 | .name = "cpsw-phy-sel" , |
239 | .of_match_table = cpsw_phy_sel_id_table, |
240 | }, |
241 | }; |
242 | builtin_platform_driver(cpsw_phy_sel_driver); |
243 | |