1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (C) 2018 Russell King, Deep Blue Solutions Ltd. |
4 | * |
5 | * Partly derived from CP110 comphy driver by Antoine Tenart |
6 | * <antoine.tenart@bootlin.com> |
7 | */ |
8 | #include <linux/delay.h> |
9 | #include <linux/iopoll.h> |
10 | #include <linux/module.h> |
11 | #include <linux/of.h> |
12 | #include <linux/phy/phy.h> |
13 | #include <linux/phy.h> |
14 | #include <linux/platform_device.h> |
15 | |
16 | #define MAX_A38X_COMPHY 6 |
17 | #define MAX_A38X_PORTS 3 |
18 | |
19 | #define COMPHY_CFG1 0x00 |
20 | #define COMPHY_CFG1_GEN_TX(x) ((x) << 26) |
21 | #define COMPHY_CFG1_GEN_TX_MSK COMPHY_CFG1_GEN_TX(15) |
22 | #define COMPHY_CFG1_GEN_RX(x) ((x) << 22) |
23 | #define COMPHY_CFG1_GEN_RX_MSK COMPHY_CFG1_GEN_RX(15) |
24 | #define GEN_SGMII_1_25GBPS 6 |
25 | #define GEN_SGMII_3_125GBPS 8 |
26 | |
27 | #define COMPHY_STAT1 0x18 |
28 | #define COMPHY_STAT1_PLL_RDY_TX BIT(3) |
29 | #define COMPHY_STAT1_PLL_RDY_RX BIT(2) |
30 | |
31 | #define COMPHY_SELECTOR 0xfc |
32 | |
33 | struct a38x_comphy; |
34 | |
35 | struct a38x_comphy_lane { |
36 | void __iomem *base; |
37 | struct a38x_comphy *priv; |
38 | unsigned int n; |
39 | |
40 | int port; |
41 | }; |
42 | |
43 | struct a38x_comphy { |
44 | void __iomem *base; |
45 | void __iomem *conf; |
46 | struct device *dev; |
47 | struct a38x_comphy_lane lane[MAX_A38X_COMPHY]; |
48 | }; |
49 | |
50 | /* |
51 | * Map serdes lanes and gbe ports to serdes mux configuration values: |
52 | * row index = serdes lane, |
53 | * column index = gbe port number. |
54 | */ |
55 | static const u8 gbe_mux[MAX_A38X_COMPHY][MAX_A38X_PORTS] = { |
56 | { 3, 0, 0 }, |
57 | { 4, 5, 0 }, |
58 | { 0, 4, 0 }, |
59 | { 0, 0, 4 }, |
60 | { 0, 3, 0 }, |
61 | { 0, 0, 3 }, |
62 | }; |
63 | |
64 | static void a38x_set_conf(struct a38x_comphy_lane *lane, bool enable) |
65 | { |
66 | struct a38x_comphy *priv = lane->priv; |
67 | u32 conf; |
68 | |
69 | if (priv->conf) { |
70 | conf = readl_relaxed(priv->conf); |
71 | if (enable) |
72 | conf |= BIT(lane->port); |
73 | else |
74 | conf &= ~BIT(lane->port); |
75 | writel(val: conf, addr: priv->conf); |
76 | } |
77 | } |
78 | |
79 | static void a38x_comphy_set_reg(struct a38x_comphy_lane *lane, |
80 | unsigned int offset, u32 mask, u32 value) |
81 | { |
82 | u32 val; |
83 | |
84 | val = readl_relaxed(lane->base + offset) & ~mask; |
85 | writel(val: val | value, addr: lane->base + offset); |
86 | } |
87 | |
88 | static void a38x_comphy_set_speed(struct a38x_comphy_lane *lane, |
89 | unsigned int gen_tx, unsigned int gen_rx) |
90 | { |
91 | a38x_comphy_set_reg(lane, COMPHY_CFG1, |
92 | COMPHY_CFG1_GEN_TX_MSK | COMPHY_CFG1_GEN_RX_MSK, |
93 | COMPHY_CFG1_GEN_TX(gen_tx) | |
94 | COMPHY_CFG1_GEN_RX(gen_rx)); |
95 | } |
96 | |
97 | static int a38x_comphy_poll(struct a38x_comphy_lane *lane, |
98 | unsigned int offset, u32 mask, u32 value) |
99 | { |
100 | u32 val; |
101 | int ret; |
102 | |
103 | ret = readl_relaxed_poll_timeout_atomic(lane->base + offset, val, |
104 | (val & mask) == value, |
105 | 1000, 150000); |
106 | |
107 | if (ret) |
108 | dev_err(lane->priv->dev, |
109 | "comphy%u: timed out waiting for status\n" , lane->n); |
110 | |
111 | return ret; |
112 | } |
113 | |
114 | /* |
115 | * We only support changing the speed for comphys configured for GBE. |
116 | * Since that is all we do, we only poll for PLL ready status. |
117 | */ |
118 | static int a38x_comphy_set_mode(struct phy *phy, enum phy_mode mode, int sub) |
119 | { |
120 | struct a38x_comphy_lane *lane = phy_get_drvdata(phy); |
121 | unsigned int gen; |
122 | int ret; |
123 | |
124 | if (mode != PHY_MODE_ETHERNET) |
125 | return -EINVAL; |
126 | |
127 | switch (sub) { |
128 | case PHY_INTERFACE_MODE_SGMII: |
129 | case PHY_INTERFACE_MODE_1000BASEX: |
130 | gen = GEN_SGMII_1_25GBPS; |
131 | break; |
132 | |
133 | case PHY_INTERFACE_MODE_2500BASEX: |
134 | gen = GEN_SGMII_3_125GBPS; |
135 | break; |
136 | |
137 | default: |
138 | return -EINVAL; |
139 | } |
140 | |
141 | a38x_set_conf(lane, enable: false); |
142 | |
143 | a38x_comphy_set_speed(lane, gen_tx: gen, gen_rx: gen); |
144 | |
145 | ret = a38x_comphy_poll(lane, COMPHY_STAT1, |
146 | COMPHY_STAT1_PLL_RDY_TX | |
147 | COMPHY_STAT1_PLL_RDY_RX, |
148 | COMPHY_STAT1_PLL_RDY_TX | |
149 | COMPHY_STAT1_PLL_RDY_RX); |
150 | |
151 | if (ret == 0) |
152 | a38x_set_conf(lane, enable: true); |
153 | |
154 | return ret; |
155 | } |
156 | |
157 | static const struct phy_ops a38x_comphy_ops = { |
158 | .set_mode = a38x_comphy_set_mode, |
159 | .owner = THIS_MODULE, |
160 | }; |
161 | |
162 | static struct phy *a38x_comphy_xlate(struct device *dev, |
163 | const struct of_phandle_args *args) |
164 | { |
165 | struct a38x_comphy_lane *lane; |
166 | struct phy *phy; |
167 | u32 val; |
168 | |
169 | if (WARN_ON(args->args[0] >= MAX_A38X_PORTS)) |
170 | return ERR_PTR(error: -EINVAL); |
171 | |
172 | phy = of_phy_simple_xlate(dev, args); |
173 | if (IS_ERR(ptr: phy)) |
174 | return phy; |
175 | |
176 | lane = phy_get_drvdata(phy); |
177 | if (lane->port >= 0) |
178 | return ERR_PTR(error: -EBUSY); |
179 | |
180 | lane->port = args->args[0]; |
181 | |
182 | val = readl_relaxed(lane->priv->base + COMPHY_SELECTOR); |
183 | val = (val >> (4 * lane->n)) & 0xf; |
184 | |
185 | if (!gbe_mux[lane->n][lane->port] || |
186 | val != gbe_mux[lane->n][lane->port]) { |
187 | dev_warn(lane->priv->dev, |
188 | "comphy%u: not configured for GBE\n" , lane->n); |
189 | phy = ERR_PTR(error: -EINVAL); |
190 | } |
191 | |
192 | return phy; |
193 | } |
194 | |
195 | static int a38x_comphy_probe(struct platform_device *pdev) |
196 | { |
197 | struct phy_provider *provider; |
198 | struct device_node *child; |
199 | struct a38x_comphy *priv; |
200 | struct resource *res; |
201 | void __iomem *base; |
202 | |
203 | priv = devm_kzalloc(dev: &pdev->dev, size: sizeof(*priv), GFP_KERNEL); |
204 | if (!priv) |
205 | return -ENOMEM; |
206 | |
207 | base = devm_platform_ioremap_resource(pdev, index: 0); |
208 | if (IS_ERR(ptr: base)) |
209 | return PTR_ERR(ptr: base); |
210 | |
211 | priv->dev = &pdev->dev; |
212 | priv->base = base; |
213 | |
214 | /* Optional */ |
215 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "conf" ); |
216 | if (res) { |
217 | priv->conf = devm_ioremap_resource(dev: &pdev->dev, res); |
218 | if (IS_ERR(ptr: priv->conf)) |
219 | return PTR_ERR(ptr: priv->conf); |
220 | } |
221 | |
222 | for_each_available_child_of_node(pdev->dev.of_node, child) { |
223 | struct phy *phy; |
224 | int ret; |
225 | u32 val; |
226 | |
227 | ret = of_property_read_u32(np: child, propname: "reg" , out_value: &val); |
228 | if (ret < 0) { |
229 | dev_err(&pdev->dev, "missing 'reg' property (%d)\n" , |
230 | ret); |
231 | continue; |
232 | } |
233 | |
234 | if (val >= MAX_A38X_COMPHY || priv->lane[val].base) { |
235 | dev_err(&pdev->dev, "invalid 'reg' property\n" ); |
236 | continue; |
237 | } |
238 | |
239 | phy = devm_phy_create(dev: &pdev->dev, node: child, ops: &a38x_comphy_ops); |
240 | if (IS_ERR(ptr: phy)) { |
241 | of_node_put(node: child); |
242 | return PTR_ERR(ptr: phy); |
243 | } |
244 | |
245 | priv->lane[val].base = base + 0x28 * val; |
246 | priv->lane[val].priv = priv; |
247 | priv->lane[val].n = val; |
248 | priv->lane[val].port = -1; |
249 | phy_set_drvdata(phy, data: &priv->lane[val]); |
250 | } |
251 | |
252 | dev_set_drvdata(dev: &pdev->dev, data: priv); |
253 | |
254 | provider = devm_of_phy_provider_register(&pdev->dev, a38x_comphy_xlate); |
255 | |
256 | return PTR_ERR_OR_ZERO(ptr: provider); |
257 | } |
258 | |
259 | static const struct of_device_id a38x_comphy_of_match_table[] = { |
260 | { .compatible = "marvell,armada-380-comphy" }, |
261 | { }, |
262 | }; |
263 | MODULE_DEVICE_TABLE(of, a38x_comphy_of_match_table); |
264 | |
265 | static struct platform_driver a38x_comphy_driver = { |
266 | .probe = a38x_comphy_probe, |
267 | .driver = { |
268 | .name = "armada-38x-comphy" , |
269 | .of_match_table = a38x_comphy_of_match_table, |
270 | }, |
271 | }; |
272 | module_platform_driver(a38x_comphy_driver); |
273 | |
274 | MODULE_AUTHOR("Russell King <rmk+kernel@armlinux.org.uk>" ); |
275 | MODULE_DESCRIPTION("Common PHY driver for Armada 38x SoCs" ); |
276 | MODULE_LICENSE("GPL v2" ); |
277 | |